# window.cpp

Go to the documentation of this file.

    00001 /* $Id: window.cpp 18620 2009-12-23 22:41:47Z 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 "openttd.h"
    00015 #include "company_func.h"
    00016 #include "gfx_func.h"
    00017 #include "console_func.h"
    00018 #include "console_gui.h"
    00019 #include "viewport_func.h"
    00020 #include "variables.h"
    00021 #include "genworld.h"
    00022 #include "blitter/factory.hpp"
    00023 #include "zoom_func.h"
    00024 #include "map_func.h"
    00025 #include "vehicle_base.h"
    00026 #include "cheat_type.h"
    00027 #include "window_func.h"
    00028 #include "tilehighlight_func.h"
    00029 #include "network/network.h"
    00030 #include "querystring_gui.h"
    00031 #include "widgets/dropdown_func.h"
    00032 #include "strings_func.h"
    00033
    00034 #include "table/sprites.h"
    00035
    00036 static Point _drag_delta;
    00037 static Window *_mouseover_last_w = NULL;
    00038
    00040 Window *_z_front_window = NULL;
    00042 Window *_z_back_window  = NULL;
    00043
    00044 /*
    00045  * Window that currently have focus. - The main purpose is to generate
    00046  * FocusLost events, not to give next window in z-order focus when a
    00047  * window is closed.
    00048  */
    00049 Window *_focused_window;
    00050
    00051 Point _cursorpos_drag_start;
    00052
    00053 int _scrollbar_start_pos;
    00054 int _scrollbar_size;
    00055 byte _scroller_click_timeout;
    00056
    00057 bool _scrolling_scrollbar;
    00058 bool _scrolling_viewport;
    00059
    00060 byte _special_mouse_mode;
    00061
    00063 WindowDesc::WindowDesc(WindowPosition def_pos, int16 def_width, int16 def_height,
    00064       WindowClass window_class, WindowClass parent_class, uint32 flags,
    00065       const NWidgetPart *nwid_parts, int16 nwid_length) :
    00066   default_pos(def_pos),
    00067   default_width(def_width),
    00068   default_height(def_height),
    00069   cls(window_class),
    00070   parent_cls(parent_class),
    00071   flags(flags),
    00072   nwid_parts(nwid_parts),
    00073   nwid_length(nwid_length)
    00074 {
    00075 }
    00076
    00077 WindowDesc::~WindowDesc()
    00078 {
    00079 }
    00080
    00088 void Scrollbar::SetCapacityFromWidget(Window *w, int widget, int padding)
    00089 {
    00090   NWidgetBase *nwid = w->GetWidget<NWidgetBase>(widget);
    00091   if (this->is_vertical) {
    00092     this->SetCapacity(((int)nwid->current_y - padding) / (int)nwid->resize_y);
    00093   } else {
    00094     this->SetCapacity(((int)nwid->current_x - padding) / (int)nwid->resize_x);
    00095   }
    00096 }
    00097
    00102 void SetFocusedWindow(Window *w)
    00103 {
    00104   if (_focused_window == w) return;
    00105
    00106   /* Invalidate focused widget */
    00107   if (_focused_window != NULL) {
    00108     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
    00109   }
    00110
    00111   /* Remember which window was previously focused */
    00112   Window *old_focused = _focused_window;
    00113   _focused_window = w;
    00114
    00115   /* So we can inform it that it lost focus */
    00116   if (old_focused != NULL) old_focused->OnFocusLost();
    00117   if (_focused_window != NULL) _focused_window->OnFocus();
    00118 }
    00119
    00125 bool EditBoxInGlobalFocus()
    00126 {
    00127   if (_focused_window == NULL) return false;
    00128
    00129   /* The console does not have an edit box so a special case is needed. */
    00130   if (_focused_window->window_class == WC_CONSOLE) return true;
    00131
    00132   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
    00133 }
    00134
    00140 bool Window::SetFocusedWidget(byte widget_index)
    00141 {
    00142   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
    00143   if (widget_index >= this->nested_array_size) return false;
    00144
    00145   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
    00146   if (this->nested_focus != NULL) {
    00147     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
    00148
    00149     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
    00150     this->nested_focus->SetDirty(this);
    00151   }
    00152   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
    00153   return true;
    00154 }
    00155
    00163 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
    00164 {
    00165   va_list wdg_list;
    00166
    00167   va_start(wdg_list, widgets);
    00168
    00169   while (widgets != WIDGET_LIST_END) {
    00170     SetWidgetDisabledState(widgets, disab_stat);
    00171     widgets = va_arg(wdg_list, int);
    00172   }
    00173
    00174   va_end(wdg_list);
    00175 }
    00176
    00182 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
    00183 {
    00184   va_list wdg_list;
    00185
    00186   va_start(wdg_list, widgets);
    00187
    00188   while (widgets != WIDGET_LIST_END) {
    00189     SetWidgetLoweredState(widgets, lowered_stat);
    00190     widgets = va_arg(wdg_list, int);
    00191   }
    00192
    00193   va_end(wdg_list);
    00194 }
    00195
    00200 void Window::RaiseButtons(bool autoraise)
    00201 {
    00202   for (uint i = 0; i < this->nested_array_size; i++) {
    00203     if (this->nested_array[i] != NULL && (this->nested_array[i]->type & ~WWB_PUSHBUTTON) < WWT_LAST &&
    00204         (!autoraise || (this->nested_array[i]->type & WWB_PUSHBUTTON)) && this->IsWidgetLowered(i)) {
    00205       this->RaiseWidget(i);
    00206       this->SetWidgetDirty(i);
    00207     }
    00208   }
    00209 }
    00210
    00215 void Window::SetWidgetDirty(byte widget_index) const
    00216 {
    00217   /* Sometimes this function is called before the window is even fully initialized */
    00218   if (this->nested_array == NULL) return;
    00219
    00220   this->nested_array[widget_index]->SetDirty(this);
    00221 }
    00222
    00228 void Window::HandleButtonClick(byte widget)
    00229 {
    00230   this->LowerWidget(widget);
    00231   this->flags4 |= WF_TIMEOUT_BEGIN;
    00232   this->SetWidgetDirty(widget);
    00233 }
    00234
    00235 static void StartWindowDrag(Window *w);
    00236 static void StartWindowSizing(Window *w, bool to_left);
    00237
    00245 static void DispatchLeftClickEvent(Window *w, int x, int y, bool double_click)
    00246 {
    00247   const NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
    00248   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
    00249
    00250   bool focused_widget_changed = false;
    00251   /* If clicked on a window that previously did dot have focus */
    00252   if (_focused_window != w &&                 // We already have focus, right?
    00253       (w->desc_flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
    00254       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
    00255     focused_widget_changed = true;
    00256     if (_focused_window != NULL) {
    00257       _focused_window->OnFocusLost();
    00258
    00259       /* The window that lost focus may have had opened a OSK, window so close it, unless the user has clicked on the OSK window. */
    00260       if (w->window_class != WC_OSK) DeleteWindowById(WC_OSK, 0);
    00261     }
    00262     SetFocusedWindow(w);
    00263     w->OnFocus();
    00264   }
    00265
    00266   if (nw == NULL) return; // exit if clicked outside of widgets
    00267
    00268   /* don't allow any interaction if the button has been disabled */
    00269   if (nw->IsDisabled()) return;
    00270
    00271   int widget_index = nw->index;
    00272
    00273   /* Clicked on a widget that is not disabled.
    00274    * So unless the clicked widget is the caption bar, change focus to this widget */
    00275   if (widget_type != WWT_CAPTION) {
    00276     /* Close the OSK window if a edit box loses focus */
    00277     if (w->nested_focus != NULL &&  w->nested_focus->type == WWT_EDITBOX && w->nested_focus != nw && w->window_class != WC_OSK) {
    00278       DeleteWindowById(WC_OSK, 0);
    00279     }
    00280
    00281     /* focused_widget_changed is 'now' only true if the window this widget
    00282      * is in gained focus. In that case it must remain true, also if the
    00283      * local widget focus did not change. As such it's the logical-or of
    00284      * both changed states.
    00285      *
    00286      * If this is not preserved, then the OSK window would be opened when
    00287      * a user has the edit box focused and then click on another window and
    00288      * then back again on the edit box (to type some text).
    00289      */
    00290     focused_widget_changed |= w->SetFocusedWidget(widget_index);
    00291   }
    00292
    00293   /* Close any child drop down menus. If the button pressed was the drop down
    00294    * list's own button, then we should not process the click any further. */
    00295   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
    00296
    00297   switch (widget_type) {
    00298     /* special widget handling for buttons*/
    00299     case WWT_PANEL   | WWB_PUSHBUTTON: // WWT_PUSHBTN
    00300     case WWT_IMGBTN  | WWB_PUSHBUTTON: // WWT_PUSHIMGBTN
    00301     case WWT_TEXTBTN | WWB_PUSHBUTTON: // WWT_PUSHTXTBTN
    00302       w->HandleButtonClick(widget_index);
    00303       break;
    00304
    00305     case WWT_SCROLLBAR:
    00306     case WWT_SCROLL2BAR:
    00307     case WWT_HSCROLLBAR:
    00308       ScrollbarClickHandler(w, nw, x, y);
    00309       break;
    00310
    00311     case WWT_EDITBOX:
    00312       if (!focused_widget_changed) { // Only open the OSK window if clicking on an already focused edit box
    00313         /* Open the OSK window if clicked on an edit box */
    00314         QueryStringBaseWindow *qs = dynamic_cast<QueryStringBaseWindow *>(w);
    00315         if (qs != NULL) {
    00316           qs->OnOpenOSKWindow(widget_index);
    00317         }
    00318       }
    00319       break;
    00320
    00321     case WWT_CLOSEBOX: // 'X'
    00322       delete w;
    00323       return;
    00324
    00325     case WWT_CAPTION: // 'Title bar'
    00326       StartWindowDrag(w);
    00327       return;
    00328
    00329     case WWT_RESIZEBOX:
    00330       /* When the resize widget is on the left size of the window
    00331        * we assume that that button is used to resize to the left. */
    00332       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
    00333       nw->SetDirty(w);
    00334       return;
    00335
    00336     case WWT_SHADEBOX:
    00337       nw->SetDirty(w);
    00338       w->SetShaded(!w->IsShaded());
    00339       return;
    00340
    00341     case WWT_STICKYBOX:
    00342       w->flags4 ^= WF_STICKY;
    00343       nw->SetDirty(w);
    00344       return;
    00345
    00346     default:
    00347       break;
    00348   }
    00349
    00350   /* Widget has no index, so the window is not interested in it. */
    00351   if (widget_index < 0) return;
    00352
    00353   Point pt = { x, y };
    00354
    00355   if (double_click) {
    00356     w->OnDoubleClick(pt, widget_index);
    00357   } else {
    00358     w->OnClick(pt, widget_index);
    00359   }
    00360 }
    00361
    00368 static void DispatchRightClickEvent(Window *w, int x, int y)
    00369 {
    00370   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
    00371
    00372   /* No widget to handle */
    00373   if (wid == NULL) return;
    00374
    00375   /* Show the tooltip if there is any */
    00376   if (wid->tool_tip != 0) {
    00377     GuiShowTooltips(wid->tool_tip);
    00378     return;
    00379   }
    00380
    00381   /* Widget has no index, so the window is not interested in it. */
    00382   if (wid->index < 0) return;
    00383
    00384   Point pt = { x, y };
    00385   w->OnRightClick(pt, wid->index);
    00386 }
    00387
    00395 static void DispatchMouseWheelEvent(Window *w, const NWidgetCore *nwid, int wheel)
    00396 {
    00397   if (nwid == NULL) return;
    00398
    00399   /* Using wheel on caption/shade-box shades or unshades the window. */
    00400   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
    00401     w->SetShaded(!w->IsShaded());
    00402     return;
    00403   }
    00404
    00405   /* Scroll the widget attached to the scrollbar. */
    00406   Scrollbar *sb = nwid->FindScrollbar(w);
    00407   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
    00408     sb->UpdatePosition(wheel);
    00409     w->SetDirty();
    00410   }
    00411 }
    00412
    00425 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
    00426 {
    00427   const Window *v;
    00428   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
    00429     if (right > v->left &&
    00430         bottom > v->top &&
    00431         left < v->left + v->width &&
    00432         top < v->top + v->height) {
    00433       /* v and rectangle intersect with eeach other */
    00434       int x;
    00435
    00436       if (left < (x = v->left)) {
    00437         DrawOverlappedWindow(w, left, top, x, bottom);
    00438         DrawOverlappedWindow(w, x, top, right, bottom);
    00439         return;
    00440       }
    00441
    00442       if (right > (x = v->left + v->width)) {
    00443         DrawOverlappedWindow(w, left, top, x, bottom);
    00444         DrawOverlappedWindow(w, x, top, right, bottom);
    00445         return;
    00446       }
    00447
    00448       if (top < (x = v->top)) {
    00449         DrawOverlappedWindow(w, left, top, right, x);
    00450         DrawOverlappedWindow(w, left, x, right, bottom);
    00451         return;
    00452       }
    00453
    00454       if (bottom > (x = v->top + v->height)) {
    00455         DrawOverlappedWindow(w, left, top, right, x);
    00456         DrawOverlappedWindow(w, left, x, right, bottom);
    00457         return;
    00458       }
    00459
    00460       return;
    00461     }
    00462   }
    00463
    00464   /* Setup blitter, and dispatch a repaint event to window *wz */
    00465   DrawPixelInfo *dp = _cur_dpi;
    00466   dp->width = right - left;
    00467   dp->height = bottom - top;
    00468   dp->left = left - w->left;
    00469   dp->top = top - w->top;
    00470   dp->pitch = _screen.pitch;
    00471   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
    00472   dp->zoom = ZOOM_LVL_NORMAL;
    00473   w->OnPaint();
    00474 }
    00475
    00484 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
    00485 {
    00486   Window *w;
    00487   DrawPixelInfo bk;
    00488   _cur_dpi = &bk;
    00489
    00490   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00491     if (right > w->left &&
    00492         bottom > w->top &&
    00493         left < w->left + w->width &&
    00494         top < w->top + w->height) {
    00495       /* Window w intersects with the rectangle => needs repaint */
    00496       DrawOverlappedWindow(w, left, top, right, bottom);
    00497     }
    00498   }
    00499 }
    00500
    00505 void Window::SetDirty() const
    00506 {
    00507   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
    00508 }
    00509
    00515 void Window::ReInit(int rx, int ry)
    00516 {
    00517   this->SetDirty(); // Mark whole current window as dirty.
    00518
    00519   /* Save current size. */
    00520   int window_width  = this->width;
    00521   int window_height = this->height;
    00522
    00523   this->OnInit();
    00524   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
    00525   this->nested_root->SetupSmallestSize(this, false);
    00526   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _dynlang.text_dir == TD_RTL);
    00527   this->width  = this->nested_root->smallest_x;
    00528   this->height = this->nested_root->smallest_y;
    00529   this->resize.step_width  = this->nested_root->resize_x;
    00530   this->resize.step_height = this->nested_root->resize_y;
    00531
    00532   /* Resize as close to the original size + requested resize as possible. */
    00533   window_width  = max(window_width  + rx, this->width);
    00534   window_height = max(window_height + ry, this->height);
    00535   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
    00536   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
    00537   /* dx and dy has to go by step.. calculate it.
    00538    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
    00539   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
    00540   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
    00541
    00542   ResizeWindow(this, dx, dy);
    00543   this->OnResize();
    00544   this->SetDirty();
    00545 }
    00546
    00551 void Window::SetShaded(bool make_shaded)
    00552 {
    00553   if (this->shade_select == NULL) return;
    00554
    00555   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
    00556   if (this->shade_select->shown_plane != desired) {
    00557     if (make_shaded) {
    00558       this->unshaded_size.width  = this->width;
    00559       this->unshaded_size.height = this->height;
    00560       this->shade_select->SetDisplayedPlane(desired);
    00561       this->ReInit(0, -this->height);
    00562     } else {
    00563       this->shade_select->SetDisplayedPlane(desired);
    00564       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
    00565       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
    00566       this->ReInit(dx, dy);
    00567     }
    00568   }
    00569 }
    00570
    00576 static Window *FindChildWindow(const Window *w, WindowClass wc)
    00577 {
    00578   Window *v;
    00579   FOR_ALL_WINDOWS_FROM_BACK(v) {
    00580     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
    00581   }
    00582
    00583   return NULL;
    00584 }
    00585
    00590 void Window::DeleteChildWindows(WindowClass wc) const
    00591 {
    00592   Window *child = FindChildWindow(this, wc);
    00593   while (child != NULL) {
    00594     delete child;
    00595     child = FindChildWindow(this, wc);
    00596   }
    00597 }
    00598
    00602 Window::~Window()
    00603 {
    00604   if (_thd.place_mode != HT_NONE &&
    00605       _thd.window_class == this->window_class &&
    00606       _thd.window_number == this->window_number) {
    00607     ResetObjectToPlace();
    00608   }
    00609
    00610   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
    00611   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
    00612
    00613   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
    00614   if (_focused_window == this) _focused_window = NULL;
    00615
    00616   this->DeleteChildWindows();
    00617
    00618   if (this->viewport != NULL) DeleteWindowViewport(this);
    00619
    00620   this->SetDirty();
    00621
    00622   free(this->nested_array); // Contents is released through deletion of #nested_root.
    00623   delete this->nested_root;
    00624
    00625   this->window_class = WC_INVALID;
    00626 }
    00627
    00634 Window *FindWindowById(WindowClass cls, WindowNumber number)
    00635 {
    00636   Window *w;
    00637   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00638     if (w->window_class == cls && w->window_number == number) return w;
    00639   }
    00640
    00641   return NULL;
    00642 }
    00643
    00650 Window *FindWindowByClass(WindowClass cls)
    00651 {
    00652   Window *w;
    00653   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00654     if (w->window_class == cls) return w;
    00655   }
    00656
    00657   return NULL;
    00658 }
    00659
    00666 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
    00667 {
    00668   Window *w = FindWindowById(cls, number);
    00669   if (force || w == NULL ||
    00670       (w->flags4 & WF_STICKY) == 0) {
    00671     delete w;
    00672   }
    00673 }
    00674
    00679 void DeleteWindowByClass(WindowClass cls)
    00680 {
    00681   Window *w;
    00682
    00683 restart_search:
    00684   /* When we find the window to delete, we need to restart the search
    00685    * as deleting this window could cascade in deleting (many) others
    00686    * anywhere in the z-array */
    00687   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00688     if (w->window_class == cls) {
    00689       delete w;
    00690       goto restart_search;
    00691     }
    00692   }
    00693 }
    00694
    00699 void DeleteCompanyWindows(CompanyID id)
    00700 {
    00701   Window *w;
    00702
    00703 restart_search:
    00704   /* When we find the window to delete, we need to restart the search
    00705    * as deleting this window could cascade in deleting (many) others
    00706    * anywhere in the z-array */
    00707   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00708     if (w->owner == id) {
    00709       delete w;
    00710       goto restart_search;
    00711     }
    00712   }
    00713
    00714   /* Also delete the company specific windows, that don't have a company-colour */
    00715   DeleteWindowById(WC_BUY_COMPANY, id);
    00716 }
    00717
    00723 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
    00724 {
    00725   Window *w;
    00726   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00727     if (w->owner != old_owner) continue;
    00728
    00729     switch (w->window_class) {
    00730       case WC_COMPANY_COLOUR:
    00731       case WC_FINANCES:
    00732       case WC_STATION_LIST:
    00733       case WC_TRAINS_LIST:
    00734       case WC_ROADVEH_LIST:
    00735       case WC_SHIPS_LIST:
    00736       case WC_AIRCRAFT_LIST:
    00737       case WC_BUY_COMPANY:
    00738       case WC_COMPANY:
    00739         continue;
    00740
    00741       default:
    00742         w->owner = new_owner;
    00743         break;
    00744     }
    00745   }
    00746 }
    00747
    00748 static void BringWindowToFront(Window *w);
    00749
    00755 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
    00756 {
    00757   Window *w = FindWindowById(cls, number);
    00758
    00759   if (w != NULL) {
    00760     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
    00761
    00762     w->flags4 |= WF_WHITE_BORDER_MASK;
    00763     BringWindowToFront(w);
    00764     w->SetDirty();
    00765   }
    00766
    00767   return w;
    00768 }
    00769
    00770 static inline bool IsVitalWindow(const Window *w)
    00771 {
    00772   switch (w->window_class) {
    00773     case WC_MAIN_TOOLBAR:
    00774     case WC_STATUS_BAR:
    00775     case WC_NEWS_WINDOW:
    00776     case WC_SEND_NETWORK_MSG:
    00777       return true;
    00778
    00779     default:
    00780       return false;
    00781   }
    00782 }
    00783
    00792 static void BringWindowToFront(Window *w)
    00793 {
    00794   Window *v = _z_front_window;
    00795
    00796   /* Bring the window just below the vital windows */
    00797   for (; v != NULL && v != w && IsVitalWindow(v); v = v->z_back) { }
    00798
    00799   if (v == NULL || w == v) return; // window is already in the right position
    00800
    00801   /* w cannot be at the top already! */
    00802   assert(w != _z_front_window);
    00803
    00804   if (w->z_back == NULL) {
    00805     _z_back_window = w->z_front;
    00806   } else {
    00807     w->z_back->z_front = w->z_front;
    00808   }
    00809   w->z_front->z_back = w->z_back;
    00810
    00811   w->z_front = v->z_front;
    00812   w->z_back = v;
    00813
    00814   if (v->z_front == NULL) {
    00815     _z_front_window = w;
    00816   } else {
    00817     v->z_front->z_back = w;
    00818   }
    00819   v->z_front = w;
    00820
    00821   w->SetDirty();
    00822 }
    00823
    00833 void Window::InitializeData(WindowClass cls, int window_number, uint32 desc_flags)
    00834 {
    00835   /* Set up window properties; some of them are needed to set up smallest size below */
    00836   this->window_class = cls;
    00837   this->flags4 |= WF_WHITE_BORDER_MASK; // just opened windows have a white border
    00838   this->owner = INVALID_OWNER;
    00839   this->nested_focus = NULL;
    00840   this->window_number = window_number;
    00841   this->desc_flags = desc_flags;
    00842
    00843   this->OnInit();
    00844   /* Initialize nested widget tree. */
    00845   if (this->nested_array == NULL) {
    00846     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
    00847     this->nested_root->SetupSmallestSize(this, true);
    00848   } else {
    00849     this->nested_root->SetupSmallestSize(this, false);
    00850   }
    00851   /* Initialize to smallest size. */
    00852   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _dynlang.text_dir == TD_RTL);
    00853
    00854   /* Further set up window properties,
    00855    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
    00856   this->resize.step_width  = this->nested_root->resize_x;
    00857   this->resize.step_height = this->nested_root->resize_y;
    00858
    00859   /* Give focus to the opened window unless it is the OSK window or a text box
    00860    * of focused window has focus (so we don't interrupt typing). But if the new
    00861    * window has a text box, then take focus anyway. */
    00862   if (this->window_class != WC_OSK && (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL)) SetFocusedWindow(this);
    00863
    00864   /* Hacky way of specifying always-on-top windows. These windows are
    00865    * always above other windows because they are moved below them.
    00866    * status-bar is above news-window because it has been created earlier.
    00867    * Also, as the chat-window is excluded from this, it will always be
    00868    * the last window, thus always on top.
    00869    * XXX - Yes, ugly, probably needs something like w->always_on_top flag
    00870    * to implement correctly, but even then you need some kind of distinction
    00871    * between on-top of chat/news and status windows, because these conflict */
    00872   Window *w = _z_front_window;
    00873   if (w != NULL && this->window_class != WC_SEND_NETWORK_MSG && this->window_class != WC_HIGHSCORE && this->window_class != WC_ENDSCREEN) {
    00874     if (FindWindowById(WC_MAIN_TOOLBAR, 0)     != NULL) w = w->z_back;
    00875     if (FindWindowById(WC_STATUS_BAR, 0)       != NULL) w = w->z_back;
    00876     if (FindWindowById(WC_NEWS_WINDOW, 0)      != NULL) w = w->z_back;
    00877     if (FindWindowByClass(WC_SEND_NETWORK_MSG) != NULL) w = w->z_back;
    00878
    00879     if (w == NULL) {
    00880       _z_back_window->z_front = this;
    00881       this->z_back = _z_back_window;
    00882       _z_back_window = this;
    00883     } else {
    00884       if (w->z_front == NULL) {
    00885         _z_front_window = this;
    00886       } else {
    00887         this->z_front = w->z_front;
    00888         w->z_front->z_back = this;
    00889       }
    00890
    00891       this->z_back = w;
    00892       w->z_front = this;
    00893     }
    00894   } else {
    00895     this->z_back = _z_front_window;
    00896     if (_z_front_window != NULL) {
    00897       _z_front_window->z_front = this;
    00898     } else {
    00899       _z_back_window = this;
    00900     }
    00901     _z_front_window = this;
    00902   }
    00903 }
    00904
    00912 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
    00913 {
    00914   this->left = x;
    00915   this->top = y;
    00916   this->width = sm_width;
    00917   this->height = sm_height;
    00918 }
    00919
    00930 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
    00931 {
    00932   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
    00933   def_height = max(def_height, this->height);
    00934   /* Try to make windows smaller when our window is too small.
    00935    * w->(width|height) is normally the same as min_(width|height),
    00936    * but this way the GUIs can be made a little more dynamic;
    00937    * one can use the same spec for multiple windows and those
    00938    * can then determine the real minimum size of the window. */
    00939   if (this->width != def_width || this->height != def_height) {
    00940     /* Think about the overlapping toolbars when determining the minimum window size */
    00941     int free_height = _screen.height;
    00942     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
    00943     if (wt != NULL) free_height -= wt->height;
    00944     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
    00945     if (wt != NULL) free_height -= wt->height;
    00946
    00947     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
    00948     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
    00949
    00950     /* X and Y has to go by step.. calculate it.
    00951      * The cast to int is necessary else x/y are implicitly casted to
    00952      * unsigned int, which won't work. */
    00953     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
    00954     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
    00955
    00956     ResizeWindow(this, enlarge_x, enlarge_y);
    00957   }
    00958
    00959   /* Always call OnResize; that way the scrollbars and matrices get initialized */
    00960   this->OnResize();
    00961
    00962   int nx = this->left;
    00963   int ny = this->top;
    00964
    00965   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
    00966
    00967   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
    00968   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
    00969   nx = max(nx, 0);
    00970
    00971   if (this->viewport != NULL) {
    00972     this->viewport->left += nx - this->left;
    00973     this->viewport->top  += ny - this->top;
    00974   }
    00975   this->left = nx;
    00976   this->top = ny;
    00977
    00978   this->SetDirty();
    00979 }
    00980
    00992 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
    00993 {
    00994   int right  = width + left;
    00995   int bottom = height + top;
    00996
    00997   if (left < 0 || top < 22 || right > _screen.width || bottom > _screen.height) return false;
    00998
    00999   /* Make sure it is not obscured by any window. */
    01000   const Window *w;
    01001   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01002     if (w->window_class == WC_MAIN_WINDOW) continue;
    01003
    01004     if (right > w->left &&
    01005         w->left + w->width > left &&
    01006         bottom > w->top &&
    01007         w->top + w->height > top) {
    01008       return false;
    01009     }
    01010   }
    01011
    01012   pos.x = left;
    01013   pos.y = top;
    01014   return true;
    01015 }
    01016
    01028 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
    01029 {
    01030   /* Left part of the rectangle may be at most 1/4 off-screen,
    01031    * right part of the rectangle may be at most 1/2 off-screen
    01032    */
    01033   if (left < -(width>>2) || left > _screen.width - (width>>1)) return false;
    01034   /* Bottom part of the rectangle may be at most 1/4 off-screen */
    01035   if (top < 22 || top > _screen.height - (height>>2)) return false;
    01036
    01037   /* Make sure it is not obscured by any window. */
    01038   const Window *w;
    01039   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01040     if (w->window_class == WC_MAIN_WINDOW) continue;
    01041
    01042     if (left + width > w->left &&
    01043         w->left + w->width > left &&
    01044         top + height > w->top &&
    01045         w->top + w->height > top) {
    01046       return false;
    01047     }
    01048   }
    01049
    01050   pos.x = left;
    01051   pos.y = top;
    01052   return true;
    01053 }
    01054
    01061 static Point GetAutoPlacePosition(int width, int height)
    01062 {
    01063   Point pt;
    01064
    01065   /* First attempt, try top-left of the screen */
    01066   if (IsGoodAutoPlace1(0, 24, width, height, pt)) return pt;
    01067
    01068   /* Second attempt, try around all existing windows with a distance of 2 pixels.
    01069    * The new window must be entirely on-screen, and not overlap with an existing window.
    01070    * Eight starting points are tried, two at each corner.
    01071    */
    01072   const Window *w;
    01073   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01074     if (w->window_class == WC_MAIN_WINDOW) continue;
    01075
    01076     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
    01077     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
    01078     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
    01079     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
    01080     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
    01081     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
    01082     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
    01083     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
    01084   }
    01085
    01086   /* Third attempt, try around all existing windows with a distance of 2 pixels.
    01087    * The new window may be partly off-screen, and must not overlap with an existing window.
    01088    * Only four starting points are tried.
    01089    */
    01090   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01091     if (w->window_class == WC_MAIN_WINDOW) continue;
    01092
    01093     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
    01094     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
    01095     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
    01096     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
    01097   }
    01098
    01099   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
    01100    * of (+5, +5)
    01101    */
    01102   int left = 0, top = 24;
    01103
    01104 restart:
    01105   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01106     if (w->left == left && w->top == top) {
    01107       left += 5;
    01108       top += 5;
    01109       goto restart;
    01110     }
    01111   }
    01112
    01113   pt.x = left;
    01114   pt.y = top;
    01115   return pt;
    01116 }
    01117
    01124 Point GetToolbarAlignedWindowPosition(int window_width)
    01125 {
    01126   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01127   assert(w != NULL);
    01128   Point pt = { _dynlang.text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
    01129   return pt;
    01130 }
    01131
    01149 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
    01150 {
    01151   Point pt;
    01152   const Window *w;
    01153
    01154   int16 default_width  = max(desc->default_width,  sm_width);
    01155   int16 default_height = max(desc->default_height, sm_height);
    01156
    01157   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
    01158       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
    01159       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
    01160
    01161     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
    01162     if (pt.x > _screen.width + 10 - default_width) {
    01163       pt.x = (_screen.width + 10 - default_width) - 20;
    01164     }
    01165     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
    01166     return pt;
    01167   }
    01168
    01169   switch (desc->default_pos) {
    01170     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
    01171       return GetToolbarAlignedWindowPosition(default_width);
    01172
    01173     case WDP_AUTO: // Find a good automatic position for the window
    01174       return GetAutoPlacePosition(default_width, default_height);
    01175
    01176     case WDP_CENTER: // Centre the window horizontally
    01177       pt.x = (_screen.width - default_width) / 2;
    01178       pt.y = (_screen.height - default_height) / 2;
    01179       break;
    01180
    01181     case WDP_MANUAL:
    01182       pt.x = 0;
    01183       pt.y = 0;
    01184       break;
    01185
    01186     default:
    01187       NOT_REACHED();
    01188   }
    01189
    01190   return pt;
    01191 }
    01192
    01193 /* virtual */ Point Window::OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
    01194 {
    01195   return LocalGetWindowPlacement(desc, sm_width, sm_height, window_number);
    01196 }
    01197
    01206 void Window::CreateNestedTree(const WindowDesc *desc, bool fill_nested)
    01207 {
    01208   int biggest_index = -1;
    01209   this->nested_root = MakeWindowNWidgetTree(desc->nwid_parts, desc->nwid_length, &biggest_index, &this->shade_select);
    01210   this->nested_array_size = (uint)(biggest_index + 1);
    01211
    01212   if (fill_nested) {
    01213     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
    01214     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
    01215   }
    01216 }
    01217
    01223 void Window::FinishInitNested(const WindowDesc *desc, WindowNumber window_number)
    01224 {
    01225   this->InitializeData(desc->cls, window_number, desc->flags);
    01226   Point pt = this->OnInitialPosition(desc, this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
    01227   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
    01228   this->FindWindowPlacementAndResize(desc->default_width, desc->default_height);
    01229 }
    01230
    01236 void Window::InitNested(const WindowDesc *desc, WindowNumber window_number)
    01237 {
    01238   this->CreateNestedTree(desc, false);
    01239   this->FinishInitNested(desc, window_number);
    01240 }
    01241
    01243 Window::Window() : hscroll(false), vscroll(true), vscroll2(true)
    01244 {
    01245 }
    01246
    01252 Window *FindWindowFromPt(int x, int y)
    01253 {
    01254   Window *w;
    01255   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01256     if (IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
    01257       return w;
    01258     }
    01259   }
    01260
    01261   return NULL;
    01262 }
    01263
    01267 void InitWindowSystem()
    01268 {
    01269   IConsoleClose();
    01270
    01271   _z_back_window = NULL;
    01272   _z_front_window = NULL;
    01273   _focused_window = NULL;
    01274   _mouseover_last_w = NULL;
    01275   _scrolling_viewport = 0;
    01276
    01277   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
    01278 }
    01279
    01283 void UnInitWindowSystem()
    01284 {
    01285   Window *w;
    01286   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
    01287
    01288   for (w = _z_front_window; w != NULL; /* nothing */) {
    01289     Window *to_del = w;
    01290     w = w->z_back;
    01291     free(to_del);
    01292   }
    01293
    01294   _z_front_window = NULL;
    01295   _z_back_window = NULL;
    01296 }
    01297
    01301 void ResetWindowSystem()
    01302 {
    01303   UnInitWindowSystem();
    01304   InitWindowSystem();
    01305   _thd.pos.x = 0;
    01306   _thd.pos.y = 0;
    01307   _thd.new_pos.x = 0;
    01308   _thd.new_pos.y = 0;
    01309 }
    01310
    01311 static void DecreaseWindowCounters()
    01312 {
    01313   Window *w;
    01314   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01315     /* Unclick scrollbar buttons if they are pressed. */
    01316     if (w->flags4 & (WF_SCROLL_DOWN | WF_SCROLL_UP)) {
    01317       w->flags4 &= ~(WF_SCROLL_DOWN | WF_SCROLL_UP);
    01318       w->SetDirty();
    01319     }
    01320     w->OnMouseLoop();
    01321   }
    01322
    01323   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01324     if ((w->flags4 & WF_TIMEOUT_MASK) && !(--w->flags4 & WF_TIMEOUT_MASK)) {
    01325       w->OnTimeout();
    01326       if (w->desc_flags & WDF_UNCLICK_BUTTONS) w->RaiseButtons(true);
    01327     }
    01328   }
    01329 }
    01330
    01331 Window *GetCallbackWnd()
    01332 {
    01333   return FindWindowById(_thd.window_class, _thd.window_number);
    01334 }
    01335
    01336 static void HandlePlacePresize()
    01337 {
    01338   if (_special_mouse_mode != WSM_PRESIZE) return;
    01339
    01340   Window *w = GetCallbackWnd();
    01341   if (w == NULL) return;
    01342
    01343   Point pt = GetTileBelowCursor();
    01344   if (pt.x == -1) {
    01345     _thd.selend.x = -1;
    01346     return;
    01347   }
    01348
    01349   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
    01350 }
    01351
    01352 static bool HandleDragDrop()
    01353 {
    01354   if (_special_mouse_mode != WSM_DRAGDROP) return true;
    01355   if (_left_button_down) return false;
    01356
    01357   Window *w = GetCallbackWnd();
    01358
    01359   if (w != NULL) {
    01360     /* send an event in client coordinates. */
    01361     Point pt;
    01362     pt.x = _cursor.pos.x - w->left;
    01363     pt.y = _cursor.pos.y - w->top;
    01364     w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
    01365   }
    01366
    01367   ResetObjectToPlace();
    01368
    01369   return false;
    01370 }
    01371
    01372 static bool HandleMouseOver()
    01373 {
    01374   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
    01375
    01376   /* We changed window, put a MOUSEOVER event to the last window */
    01377   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
    01378     /* Reset mouse-over coordinates of previous window */
    01379     Point pt = { -1, -1 };
    01380     _mouseover_last_w->OnMouseOver(pt, 0);
    01381   }
    01382
    01383   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
    01384   _mouseover_last_w = w;
    01385
    01386   if (w != NULL) {
    01387     /* send an event in client coordinates. */
    01388     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
    01389     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
    01390     if (widget != NULL) w->OnMouseOver(pt, widget->index);
    01391   }
    01392
    01393   /* Mouseover never stops execution */
    01394   return true;
    01395 }
    01396
    01406 void ResizeWindow(Window *w, int delta_x, int delta_y)
    01407 {
    01408   if (delta_x == 0 && delta_y == 0) return;
    01409
    01410   w->SetDirty();
    01411
    01412   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);
    01413   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);
    01414   assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
    01415   assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
    01416
    01417   w->nested_root->AssignSizePosition(ST_RESIZE, 0, 0, w->nested_root->smallest_x + new_xinc, w->nested_root->smallest_y + new_yinc, _dynlang.text_dir == TD_RTL);
    01418   w->width  = w->nested_root->current_x;
    01419   w->height = w->nested_root->current_y;
    01420   w->SetDirty();
    01421 }
    01422
    01427 int GetMainViewTop()
    01428 {
    01429   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01430   return (w == NULL) ? 0 : w->top + w->height;
    01431 }
    01432
    01437 int GetMainViewBottom()
    01438 {
    01439   Window *w = FindWindowById(WC_STATUS_BAR, 0);
    01440   return (w == NULL) ? _screen.height : w->top;
    01441 }
    01442
    01444 static const int MIN_VISIBLE_TITLE_BAR = 13;
    01445
    01447 enum PreventHideDirection {
    01448   PHD_UP,
    01449   PHD_DOWN,
    01450 };
    01451
    01462 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
    01463 {
    01464   if (v == NULL) return;
    01465
    01466   int v_bottom = v->top + v->height;
    01467   int v_right = v->left + v->width;
    01468   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.
    01469
    01470   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
    01471   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
    01472
    01473   /* Vertically, the rectangle is hidden behind v. */
    01474   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
    01475     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
    01476     return;
    01477   }
    01478   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
    01479     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
    01480     return;
    01481   }
    01482
    01483   /* Horizontally also hidden, force movement to a safe area. */
    01484   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
    01485     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
    01486   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
    01487     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
    01488   } else {
    01489     *ny = safe_y;
    01490   }
    01491 }
    01492
    01493 static bool _dragging_window;
    01494
    01495 static bool HandleWindowDragging()
    01496 {
    01497   /* Get out immediately if no window is being dragged at all. */
    01498   if (!_dragging_window) return true;
    01499
    01500   /* Otherwise find the window... */
    01501   Window *w;
    01502   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01503     if (w->flags4 & WF_DRAGGING) {
    01504       /* Stop the dragging if the left mouse button was released */
    01505       if (!_left_button_down) {
    01506         w->flags4 &= ~WF_DRAGGING;
    01507         break;
    01508       }
    01509
    01510       w->SetDirty();
    01511
    01512       int x = _cursor.pos.x + _drag_delta.x;
    01513       int y = _cursor.pos.y + _drag_delta.y;
    01514       int nx = x;
    01515       int ny = y;
    01516
    01517       if (_settings_client.gui.window_snap_radius != 0) {
    01518         const Window *v;
    01519
    01520         int hsnap = _settings_client.gui.window_snap_radius;
    01521         int vsnap = _settings_client.gui.window_snap_radius;
    01522         int delta;
    01523
    01524         FOR_ALL_WINDOWS_FROM_BACK(v) {
    01525           if (v == w) continue; // Don't snap at yourself
    01526
    01527           if (y + w->height > v->top && y < v->top + v->height) {
    01528             /* Your left border <-> other right border */
    01529             delta = abs(v->left + v->width - x);
    01530             if (delta <= hsnap) {
    01531               nx = v->left + v->width;
    01532               hsnap = delta;
    01533             }
    01534
    01535             /* Your right border <-> other left border */
    01536             delta = abs(v->left - x - w->width);
    01537             if (delta <= hsnap) {
    01538               nx = v->left - w->width;
    01539               hsnap = delta;
    01540             }
    01541           }
    01542
    01543           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
    01544             /* Your left border <-> other left border */
    01545             delta = abs(v->left - x);
    01546             if (delta <= hsnap) {
    01547               nx = v->left;
    01548               hsnap = delta;
    01549             }
    01550
    01551             /* Your right border <-> other right border */
    01552             delta = abs(v->left + v->width - x - w->width);
    01553             if (delta <= hsnap) {
    01554               nx = v->left + v->width - w->width;
    01555               hsnap = delta;
    01556             }
    01557           }
    01558
    01559           if (x + w->width > v->left && x < v->left + v->width) {
    01560             /* Your top border <-> other bottom border */
    01561             delta = abs(v->top + v->height - y);
    01562             if (delta <= vsnap) {
    01563               ny = v->top + v->height;
    01564               vsnap = delta;
    01565             }
    01566
    01567             /* Your bottom border <-> other top border */
    01568             delta = abs(v->top - y - w->height);
    01569             if (delta <= vsnap) {
    01570               ny = v->top - w->height;
    01571               vsnap = delta;
    01572             }
    01573           }
    01574
    01575           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
    01576             /* Your top border <-> other top border */
    01577             delta = abs(v->top - y);
    01578             if (delta <= vsnap) {
    01579               ny = v->top;
    01580               vsnap = delta;
    01581             }
    01582
    01583             /* Your bottom border <-> other bottom border */
    01584             delta = abs(v->top + v->height - y - w->height);
    01585             if (delta <= vsnap) {
    01586               ny = v->top + v->height - w->height;
    01587               vsnap = delta;
    01588             }
    01589           }
    01590         }
    01591       }
    01592
    01593       /* Search for the title bar rectangle. */
    01594       Rect caption_rect;
    01595       const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
    01596       assert(caption != NULL);
    01597       caption_rect.left   = caption->pos_x;
    01598       caption_rect.right  = caption->pos_x + caption->current_x;
    01599       caption_rect.top    = caption->pos_y;
    01600       caption_rect.bottom = caption->pos_y + caption->current_y;
    01601
    01602       /* Make sure the window doesn't leave the screen */
    01603       nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
    01604       ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
    01605
    01606       /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
    01607       PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
    01608       PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
    01609
    01610       if (w->viewport != NULL) {
    01611         w->viewport->left += nx - w->left;
    01612         w->viewport->top  += ny - w->top;
    01613       }
    01614       w->left = nx;
    01615       w->top  = ny;
    01616
    01617       w->SetDirty();
    01618       return false;
    01619     } else if (w->flags4 & WF_SIZING) {
    01620       /* Stop the sizing if the left mouse button was released */
    01621       if (!_left_button_down) {
    01622         w->flags4 &= ~WF_SIZING;
    01623         w->SetDirty();
    01624         break;
    01625       }
    01626
    01627       /* Compute difference in pixels between cursor position and reference point in the window.
    01628        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
    01629        */
    01630       int x, y = _cursor.pos.y - _drag_delta.y;
    01631       if (w->flags4 & WF_SIZING_LEFT) {
    01632         x = _drag_delta.x - _cursor.pos.x;
    01633       } else {
    01634         x = _cursor.pos.x - _drag_delta.x;
    01635       }
    01636
    01637       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
    01638       if (w->resize.step_width  == 0) x = 0;
    01639       if (w->resize.step_height == 0) y = 0;
    01640
    01641       /* X and Y has to go by step.. calculate it.
    01642        * The cast to int is necessary else x/y are implicitly casted to
    01643        * unsigned int, which won't work. */
    01644       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
    01645       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
    01646
    01647       /* Check that we don't go below the minimum set size */
    01648       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
    01649         x = w->nested_root->smallest_x - w->width;
    01650       }
    01651       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
    01652         y = w->nested_root->smallest_y - w->height;
    01653       }
    01654
    01655       /* Window already on size */
    01656       if (x == 0 && y == 0) return false;
    01657
    01658       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
    01659       _drag_delta.y += y;
    01660       if ((w->flags4 & WF_SIZING_LEFT) && x != 0) {
    01661         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
    01662         w->SetDirty();
    01663         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
    01664         /* ResizeWindow() below ensures marking new position as dirty. */
    01665       } else {
    01666         _drag_delta.x += x;
    01667       }
    01668
    01669       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
    01670       ResizeWindow(w, x, y);
    01671       w->OnResize();
    01672       return false;
    01673     }
    01674   }
    01675
    01676   _dragging_window = false;
    01677   return false;
    01678 }
    01679
    01684 static void StartWindowDrag(Window *w)
    01685 {
    01686   w->flags4 |= WF_DRAGGING;
    01687   _dragging_window = true;
    01688
    01689   _drag_delta.x = w->left - _cursor.pos.x;
    01690   _drag_delta.y = w->top  - _cursor.pos.y;
    01691
    01692   BringWindowToFront(w);
    01693   DeleteWindowById(WC_DROPDOWN_MENU, 0);
    01694 }
    01695
    01701 static void StartWindowSizing(Window *w, bool to_left)
    01702 {
    01703   w->flags4 |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
    01704   _dragging_window = true;
    01705
    01706   _drag_delta.x = _cursor.pos.x;
    01707   _drag_delta.y = _cursor.pos.y;
    01708
    01709   BringWindowToFront(w);
    01710   DeleteWindowById(WC_DROPDOWN_MENU, 0);
    01711 }
    01712
    01713
    01714 static bool HandleScrollbarScrolling()
    01715 {
    01716   Window *w;
    01717
    01718   /* Get out quickly if no item is being scrolled */
    01719   if (!_scrolling_scrollbar) return true;
    01720
    01721   /* Find the scrolling window */
    01722   FOR_ALL_WINDOWS_FROM_BACK(w) {
    01723     if (w->flags4 & WF_SCROLL_MIDDLE) {
    01724       /* Abort if no button is clicked any more. */
    01725       if (!_left_button_down) {
    01726         w->flags4 &= ~WF_SCROLL_MIDDLE;
    01727         w->SetDirty();
    01728         break;
    01729       }
    01730
    01731       int i;
    01732       Scrollbar *sb;
    01733       bool rtl = false;
    01734
    01735       if (w->flags4 & WF_HSCROLL) {
    01736         sb = &w->hscroll;
    01737         i = _cursor.pos.x - _cursorpos_drag_start.x;
    01738         rtl = _dynlang.text_dir == TD_RTL;
    01739       } else if (w->flags4 & WF_SCROLL2) {
    01740         sb = &w->vscroll2;
    01741         i = _cursor.pos.y - _cursorpos_drag_start.y;
    01742       } else {
    01743         sb = &w->vscroll;
    01744         i = _cursor.pos.y - _cursorpos_drag_start.y;
    01745       }
    01746
    01747       /* Find the item we want to move to and make sure it's inside bounds. */
    01748       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
    01749       if (rtl) pos = sb->GetCount() - sb->GetCapacity() - pos;
    01750       if (pos != sb->GetPosition()) {
    01751         sb->SetPosition(pos);
    01752         w->SetDirty();
    01753       }
    01754       return false;
    01755     }
    01756   }
    01757
    01758   _scrolling_scrollbar = false;
    01759   return false;
    01760 }
    01761
    01762 static bool HandleViewportScroll()
    01763 {
    01764   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
    01765
    01766   if (!_scrolling_viewport) return true;
    01767
    01768   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
    01769
    01770   if (!(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) || w == NULL) {
    01771     _cursor.fix_at = false;
    01772     _scrolling_viewport = false;
    01773     return true;
    01774   }
    01775
    01776   if (w == FindWindowById(WC_MAIN_WINDOW, 0) && w->viewport->follow_vehicle != INVALID_VEHICLE) {
    01777     /* If the main window is following a vehicle, then first let go of it! */
    01778     const Vehicle *veh = Vehicle::Get(w->viewport->follow_vehicle);
    01779     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
    01780     return true;
    01781   }
    01782
    01783   Point delta;
    01784   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
    01785     delta.x = -_cursor.delta.x;
    01786     delta.y = -_cursor.delta.y;
    01787   } else {
    01788     delta.x = _cursor.delta.x;
    01789     delta.y = _cursor.delta.y;
    01790   }
    01791
    01792   if (scrollwheel_scrolling) {
    01793     /* We are using scrollwheels for scrolling */
    01794     delta.x = _cursor.h_wheel;
    01795     delta.y = _cursor.v_wheel;
    01796     _cursor.v_wheel = 0;
    01797     _cursor.h_wheel = 0;
    01798   }
    01799
    01800   /* Create a scroll-event and send it to the window */
    01801   if (delta.x != 0 || delta.y != 0) w->OnScroll(delta);
    01802
    01803   _cursor.delta.x = 0;
    01804   _cursor.delta.y = 0;
    01805   return false;
    01806 }
    01807
    01816 static bool MaybeBringWindowToFront(Window *w)
    01817 {
    01818   bool bring_to_front = false;
    01819
    01820   if (w->window_class == WC_MAIN_WINDOW ||
    01821       IsVitalWindow(w) ||
    01822       w->window_class == WC_TOOLTIPS ||
    01823       w->window_class == WC_DROPDOWN_MENU) {
    01824     return true;
    01825   }
    01826
    01827   /* Use unshaded window size rather than current size for shaded windows. */
    01828   int w_width  = w->width;
    01829   int w_height = w->height;
    01830   if (w->IsShaded()) {
    01831     w_width  = w->unshaded_size.width;
    01832     w_height = w->unshaded_size.height;
    01833   }
    01834
    01835   Window *u;
    01836   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
    01837     /* A modal child will prevent the activation of the parent window */
    01838     if (u->parent == w && (u->desc_flags & WDF_MODAL)) {
    01839       u->flags4 |= WF_WHITE_BORDER_MASK;
    01840       u->SetDirty();
    01841       return false;
    01842     }
    01843
    01844     if (u->window_class == WC_MAIN_WINDOW ||
    01845         IsVitalWindow(u) ||
    01846         u->window_class == WC_TOOLTIPS ||
    01847         u->window_class == WC_DROPDOWN_MENU) {
    01848       continue;
    01849     }
    01850
    01851     /* Window sizes don't interfere, leave z-order alone */
    01852     if (w->left + w_width <= u->left ||
    01853         u->left + u->width <= w->left ||
    01854         w->top  + w_height <= u->top ||
    01855         u->top + u->height <= w->top) {
    01856       continue;
    01857     }
    01858
    01859     bring_to_front = true;
    01860   }
    01861
    01862   if (bring_to_front) BringWindowToFront(w);
    01863   return true;
    01864 }
    01865
    01869 void HandleKeypress(uint32 raw_key)
    01870 {
    01871   /*
    01872    * During the generation of the world, there might be
    01873    * another thread that is currently building for example
    01874    * a road. To not interfere with those tasks, we should
    01875    * NOT change the _current_company here.
    01876    *
    01877    * This is not necessary either, as the only events that
    01878    * can be handled are the 'close application' events
    01879    */
    01880   if (!IsGeneratingWorld()) _current_company = _local_company;
    01881
    01882   /* Setup event */
    01883   uint16 key     = GB(raw_key,  0, 16);
    01884   uint16 keycode = GB(raw_key, 16, 16);
    01885
    01886   /*
    01887    * The Unicode standard defines an area called the private use area. Code points in this
    01888    * area are reserved for private use and thus not portable between systems. For instance,
    01889    * Apple defines code points for the arrow keys in this area, but these are only printable
    01890    * on a system running OS X. We don't want these keys to show up in text fields and such,
    01891    * and thus we have to clear the unicode character when we encounter such a key.
    01892    */
    01893   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
    01894
    01895   /*
    01896    * If both key and keycode is zero, we don't bother to process the event.
    01897    */
    01898   if (key == 0 && keycode == 0) return;
    01899
    01900   /* Check if the focused window has a focused editbox */
    01901   if (EditBoxInGlobalFocus()) {
    01902     /* All input will in this case go to the focused window */
    01903     if (_focused_window->OnKeyPress(key, keycode) == Window::ES_HANDLED) return;
    01904   }
    01905
    01906   /* Call the event, start with the uppermost window, but ignore the toolbar. */
    01907   Window *w;
    01908   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01909     if (w->window_class == WC_MAIN_TOOLBAR) continue;
    01910     if (w->OnKeyPress(key, keycode) == Window::ES_HANDLED) return;
    01911   }
    01912
    01913   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    01914   /* When there is no toolbar w is null, check for that */
    01915   if (w != NULL) w->OnKeyPress(key, keycode);
    01916 }
    01917
    01921 void HandleCtrlChanged()
    01922 {
    01923   /* Call the event, start with the uppermost window. */
    01924   Window *w;
    01925   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    01926     if (w->OnCTRLStateChange() == Window::ES_HANDLED) return;
    01927   }
    01928 }
    01929
    01936 static int _input_events_this_tick = 0;
    01937
    01942 static void HandleAutoscroll()
    01943 {
    01944   if (_settings_client.gui.autoscroll && _game_mode != GM_MENU && !IsGeneratingWorld()) {
    01945     int x = _cursor.pos.x;
    01946     int y = _cursor.pos.y;
    01947     Window *w = FindWindowFromPt(x, y);
    01948     if (w == NULL || w->flags4 & WF_DISABLE_VP_SCROLL) return;
    01949     ViewPort *vp = IsPtInWindowViewport(w, x, y);
    01950     if (vp != NULL) {
    01951       x -= vp->left;
    01952       y -= vp->top;
    01953
    01954       /* here allows scrolling in both x and y axis */
    01955 #define scrollspeed 3
    01956       if (x - 15 < 0) {
    01957         w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
    01958       } else if (15 - (vp->width - x) > 0) {
    01959         w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
    01960       }
    01961       if (y - 15 < 0) {
    01962         w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
    01963       } else if (15 - (vp->height - y) > 0) {
    01964         w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
    01965       }
    01966 #undef scrollspeed
    01967     }
    01968   }
    01969 }
    01970
    01971 enum MouseClick {
    01972   MC_NONE = 0,
    01973   MC_LEFT,
    01974   MC_RIGHT,
    01975   MC_DOUBLE_LEFT,
    01976
    01977   MAX_OFFSET_DOUBLE_CLICK = 5,
    01978   TIME_BETWEEN_DOUBLE_CLICK = 500,
    01979 };
    01980
    01981 extern bool VpHandlePlaceSizingDrag();
    01982
    01983 static void ScrollMainViewport(int x, int y)
    01984 {
    01985   if (_game_mode != GM_MENU) {
    01986     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
    01987     assert(w);
    01988
    01989     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
    01990     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
    01991   }
    01992 }
    01993
    02003 static const int8 scrollamt[16][2] = {
    02004   { 0,  0},
    02005   {-2,  0},
    02006   { 0, -2},
    02007   {-2, -1},
    02008   { 2,  0},
    02009   { 0,  0},
    02010   { 2, -1},
    02011   { 0, -2},
    02012   { 0,  2},
    02013   {-2,  1},
    02014   { 0,  0},
    02015   {-2,  0},
    02016   { 2,  1},
    02017   { 0,  2},
    02018   { 2,  0},
    02019   { 0,  0},
    02020 };
    02021
    02022 static void HandleKeyScrolling()
    02023 {
    02024   /*
    02025    * Check that any of the dirkeys is pressed and that the focused window
    02026    * dont has an edit-box as focused widget.
    02027    */
    02028   if (_dirkeys && !EditBoxInGlobalFocus()) {
    02029     int factor = _shift_pressed ? 50 : 10;
    02030     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
    02031   }
    02032 }
    02033
    02034 static void MouseLoop(MouseClick click, int mousewheel)
    02035 {
    02036   HandlePlacePresize();
    02037   UpdateTileSelection();
    02038
    02039   if (!VpHandlePlaceSizingDrag())  return;
    02040   if (!HandleDragDrop())           return;
    02041   if (!HandleWindowDragging())     return;
    02042   if (!HandleScrollbarScrolling()) return;
    02043   if (!HandleViewportScroll())     return;
    02044   if (!HandleMouseOver())          return;
    02045
    02046   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
    02047   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
    02048
    02049   int x = _cursor.pos.x;
    02050   int y = _cursor.pos.y;
    02051   Window *w = FindWindowFromPt(x, y);
    02052   if (w == NULL) return;
    02053
    02054   if (!MaybeBringWindowToFront(w)) return;
    02055   ViewPort *vp = IsPtInWindowViewport(w, x, y);
    02056
    02057   /* Don't allow any action in a viewport if either in menu of in generating world */
    02058   if (vp != NULL && (_game_mode == GM_MENU || IsGeneratingWorld())) return;
    02059
    02060   if (mousewheel != 0) {
    02061     if (_settings_client.gui.scrollwheel_scrolling == 0) {
    02062       /* Send mousewheel event to window */
    02063       w->OnMouseWheel(mousewheel);
    02064     }
    02065
    02066     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
    02067     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
    02068   }
    02069
    02070   if (vp != NULL) {
    02071     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
    02072     switch (click) {
    02073       case MC_DOUBLE_LEFT:
    02074       case MC_LEFT:
    02075         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
    02076         if (_thd.place_mode != HT_NONE &&
    02077             /* query button and place sign button work in pause mode */
    02078             _cursor.sprite != SPR_CURSOR_QUERY &&
    02079             _cursor.sprite != SPR_CURSOR_SIGN &&
    02080             _pause_mode != PM_UNPAUSED &&
    02081             !_cheats.build_in_pause.value) {
    02082           return;
    02083         }
    02084
    02085         if (_thd.place_mode == HT_NONE) {
    02086           if (!HandleViewportClicked(vp, x, y) &&
    02087               !(w->flags4 & WF_DISABLE_VP_SCROLL) &&
    02088               _settings_client.gui.left_mouse_btn_scrolling) {
    02089             _scrolling_viewport = true;
    02090             _cursor.fix_at = false;
    02091           }
    02092         } else {
    02093           PlaceObject();
    02094         }
    02095         break;
    02096
    02097       case MC_RIGHT:
    02098         if (!(w->flags4 & WF_DISABLE_VP_SCROLL)) {
    02099           _scrolling_viewport = true;
    02100           _cursor.fix_at = true;
    02101         }
    02102         break;
    02103
    02104       default:
    02105         break;
    02106     }
    02107   } else {
    02108     switch (click) {
    02109       case MC_DOUBLE_LEFT:
    02110         DispatchLeftClickEvent(w, x - w->left, y - w->top, true);
    02111         if (_mouseover_last_w == NULL) break; // The window got removed.
    02112         /* fallthough, and also give a single-click for backwards compatibility */
    02113       case MC_LEFT:
    02114         DispatchLeftClickEvent(w, x - w->left, y - w->top, false);
    02115         break;
    02116
    02117       default:
    02118         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
    02119         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
    02120          * Simulate a right button click so we can get started. */
    02121
    02122         /* fallthough */
    02123       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
    02124     }
    02125   }
    02126 }
    02127
    02131 void HandleMouseEvents()
    02132 {
    02133   static int double_click_time = 0;
    02134   static int double_click_x = 0;
    02135   static int double_click_y = 0;
    02136
    02137   /*
    02138    * During the generation of the world, there might be
    02139    * another thread that is currently building for example
    02140    * a road. To not interfere with those tasks, we should
    02141    * NOT change the _current_company here.
    02142    *
    02143    * This is not necessary either, as the only events that
    02144    * can be handled are the 'close application' events
    02145    */
    02146   if (!IsGeneratingWorld()) _current_company = _local_company;
    02147
    02148   /* Mouse event? */
    02149   MouseClick click = MC_NONE;
    02150   if (_left_button_down && !_left_button_clicked) {
    02151     click = MC_LEFT;
    02152     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
    02153         double_click_x != 0    && abs(_cursor.pos.x - double_click_x) < MAX_OFFSET_DOUBLE_CLICK  &&
    02154         double_click_y != 0    && abs(_cursor.pos.y - double_click_y) < MAX_OFFSET_DOUBLE_CLICK) {
    02155       click = MC_DOUBLE_LEFT;
    02156     }
    02157     double_click_time = _realtime_tick;
    02158     double_click_x = _cursor.pos.x;
    02159     double_click_y = _cursor.pos.y;
    02160     _left_button_clicked = true;
    02161     _input_events_this_tick++;
    02162   } else if (_right_button_clicked) {
    02163     _right_button_clicked = false;
    02164     click = MC_RIGHT;
    02165     _input_events_this_tick++;
    02166   }
    02167
    02168   int mousewheel = 0;
    02169   if (_cursor.wheel) {
    02170     mousewheel = _cursor.wheel;
    02171     _cursor.wheel = 0;
    02172     _input_events_this_tick++;
    02173   }
    02174
    02175   MouseLoop(click, mousewheel);
    02176
    02177   /* We have moved the mouse the required distance,
    02178    * no need to move it at any later time. */
    02179   _cursor.delta.x = 0;
    02180   _cursor.delta.y = 0;
    02181 }
    02182
    02186 static void CheckSoftLimit()
    02187 {
    02188   if (_settings_client.gui.window_soft_limit == 0) return;
    02189
    02190   for (;;) {
    02191     uint deletable_count = 0;
    02192     Window *w, *last_deletable = NULL;
    02193     FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02194       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags4 & WF_STICKY)) continue;
    02195
    02196       last_deletable = w;
    02197       deletable_count++;
    02198     }
    02199
    02200     /* We've ot reached the soft limit yet */
    02201     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
    02202
    02203     assert(last_deletable != NULL);
    02204     delete last_deletable;
    02205   }
    02206 }
    02207
    02211 void InputLoop()
    02212 {
    02213   CheckSoftLimit();
    02214   HandleKeyScrolling();
    02215
    02216   /* Do the actual free of the deleted windows. */
    02217   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
    02218     Window *w = v;
    02219     v = v->z_back;
    02220
    02221     if (w->window_class != WC_INVALID) continue;
    02222
    02223     /* Find the window in the z-array, and effectively remove it
    02224      * by moving all windows after it one to the left. This must be
    02225      * done before removing the child so we cannot cause recursion
    02226      * between the deletion of the parent and the child. */
    02227     if (w->z_front == NULL) {
    02228       _z_front_window = w->z_back;
    02229     } else {
    02230       w->z_front->z_back = w->z_back;
    02231     }
    02232     if (w->z_back == NULL) {
    02233       _z_back_window  = w->z_front;
    02234     } else {
    02235       w->z_back->z_front = w->z_front;
    02236     }
    02237     free(w);
    02238   }
    02239
    02240   DecreaseWindowCounters();
    02241
    02242   if (_input_events_this_tick != 0) {
    02243     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
    02244     _input_events_this_tick = 0;
    02245     /* there were some inputs this tick, don't scroll ??? */
    02246     return;
    02247   }
    02248
    02249   /* HandleMouseEvents was already called for this tick */
    02250   HandleMouseEvents();
    02251   HandleAutoscroll();
    02252 }
    02253
    02257 void UpdateWindows()
    02258 {
    02259   Window *w;
    02260   static int we4_timer = 0;
    02261   int t = we4_timer + 1;
    02262
    02263   if (t >= 100) {
    02264     FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02265       w->OnHundredthTick();
    02266     }
    02267     t = 0;
    02268   }
    02269   we4_timer = t;
    02270
    02271   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02272     if (w->flags4 & WF_WHITE_BORDER_MASK) {
    02273       w->flags4 -= WF_WHITE_BORDER_ONE;
    02274
    02275       if (!(w->flags4 & WF_WHITE_BORDER_MASK)) w->SetDirty();
    02276     }
    02277   }
    02278
    02279   DrawDirtyBlocks();
    02280
    02281   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02282     /* Update viewport only if window is not shaded. */
    02283     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
    02284   }
    02285   NetworkDrawChatMessage();
    02286   /* Redraw mouse cursor in case it was hidden */
    02287   DrawMouseCursor();
    02288 }
    02289
    02295 void SetWindowDirty(WindowClass cls, WindowNumber number)
    02296 {
    02297   const Window *w;
    02298   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02299     if (w->window_class == cls && w->window_number == number) w->SetDirty();
    02300   }
    02301 }
    02302
    02309 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
    02310 {
    02311   const Window *w;
    02312   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02313     if (w->window_class == cls && w->window_number == number) {
    02314       w->SetWidgetDirty(widget_index);
    02315     }
    02316   }
    02317 }
    02318
    02323 void SetWindowClassesDirty(WindowClass cls)
    02324 {
    02325   Window *w;
    02326   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02327     if (w->window_class == cls) w->SetDirty();
    02328   }
    02329 }
    02330
    02337 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data)
    02338 {
    02339   Window *w;
    02340   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02341     if (w->window_class == cls && w->window_number == number) w->InvalidateData(data);
    02342   }
    02343 }
    02344
    02350 void InvalidateWindowClassesData(WindowClass cls, int data)
    02351 {
    02352   Window *w;
    02353
    02354   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02355     if (w->window_class == cls) w->InvalidateData(data);
    02356   }
    02357 }
    02358
    02362 void CallWindowTickEvent()
    02363 {
    02364   if (_scroller_click_timeout > 3) {
    02365     _scroller_click_timeout -= 3;
    02366   } else {
    02367     _scroller_click_timeout = 0;
    02368   }
    02369
    02370   Window *w;
    02371   FOR_ALL_WINDOWS_FROM_FRONT(w) {
    02372     w->OnTick();
    02373   }
    02374 }
    02375
    02382 void DeleteNonVitalWindows()
    02383 {
    02384   Window *w;
    02385
    02386 restart_search:
    02387   /* When we find the window to delete, we need to restart the search
    02388    * as deleting this window could cascade in deleting (many) others
    02389    * anywhere in the z-array */
    02390   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02391     if (w->window_class != WC_MAIN_WINDOW &&
    02392         w->window_class != WC_SELECT_GAME &&
    02393         w->window_class != WC_MAIN_TOOLBAR &&
    02394         w->window_class != WC_STATUS_BAR &&
    02395         w->window_class != WC_TOOLBAR_MENU &&
    02396         w->window_class != WC_TOOLTIPS &&
    02397         (w->flags4 & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
    02398
    02399       delete w;
    02400       goto restart_search;
    02401     }
    02402   }
    02403 }
    02404
    02410 void DeleteAllNonVitalWindows()
    02411 {
    02412   Window *w;
    02413
    02414   /* Delete every window except for stickied ones, then sticky ones as well */
    02415   DeleteNonVitalWindows();
    02416
    02417 restart_search:
    02418   /* When we find the window to delete, we need to restart the search
    02419    * as deleting this window could cascade in deleting (many) others
    02420    * anywhere in the z-array */
    02421   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02422     if (w->flags4 & WF_STICKY) {
    02423       delete w;
    02424       goto restart_search;
    02425     }
    02426   }
    02427 }
    02428
    02433 void DeleteConstructionWindows()
    02434 {
    02435   Window *w;
    02436
    02437 restart_search:
    02438   /* When we find the window to delete, we need to restart the search
    02439    * as deleting this window could cascade in deleting (many) others
    02440    * anywhere in the z-array */
    02441   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02442     if (w->desc_flags & WDF_CONSTRUCTION) {
    02443       delete w;
    02444       goto restart_search;
    02445     }
    02446   }
    02447
    02448   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
    02449 }
    02450
    02452 void HideVitalWindows()
    02453 {
    02454   DeleteWindowById(WC_TOOLBAR_MENU, 0);
    02455   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
    02456   DeleteWindowById(WC_STATUS_BAR, 0);
    02457 }
    02458
    02460 void ReInitAllWindows()
    02461 {
    02462   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
    02463
    02464   Window *w;
    02465   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02466     w->ReInit();
    02467   }
    02468
    02469   /* Make sure essential parts of all windows are visible */
    02470   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
    02471   MarkWholeScreenDirty();
    02472 }
    02473
    02479 int PositionMainToolbar(Window *w)
    02480 {
    02481   DEBUG(misc, 5, "Repositioning Main Toolbar...");
    02482
    02483   if (w == NULL || w->window_class != WC_MAIN_TOOLBAR) {
    02484     w = FindWindowById(WC_MAIN_TOOLBAR, 0);
    02485   }
    02486
    02487   switch (_settings_client.gui.toolbar_pos) {
    02488     case 1:  w->left = (_screen.width - w->width) / 2; break;
    02489     case 2:  w->left = _screen.width - w->width; break;
    02490     default: w->left = 0;
    02491   }
    02492   SetDirtyBlocks(0, 0, _screen.width, w->height); // invalidate the whole top part
    02493   return w->left;
    02494 }
    02495
    02496
    02502 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
    02503 {
    02504   Window *w;
    02505   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02506     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
    02507       w->viewport->follow_vehicle = to_index;
    02508       w->SetDirty();
    02509     }
    02510   }
    02511 }
    02512
    02513
    02519 void RelocateAllWindows(int neww, int newh)
    02520 {
    02521   Window *w;
    02522
    02523   FOR_ALL_WINDOWS_FROM_BACK(w) {
    02524     int left, top;
    02525
    02526     if (w->window_class == WC_MAIN_WINDOW) {
    02527       ViewPort *vp = w->viewport;
    02528       vp->width = w->width = neww;
    02529       vp->height = w->height = newh;
    02530       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
    02531       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
    02532       continue; // don't modify top,left
    02533     }
    02534
    02535     /* XXX - this probably needs something more sane. For example specying
    02536      * in a 'backup'-desc that the window should always be centred. */
    02537     switch (w->window_class) {
    02538       case WC_MAIN_TOOLBAR:
    02539         if (neww - w->width != 0) {
    02540           ResizeWindow(w, min(neww, 640) - w->width, 0);
    02541           w->OnResize();
    02542         }
    02543
    02544         top = w->top;
    02545         left = PositionMainToolbar(w); // changes toolbar orientation
    02546         break;
    02547
    02548       case WC_SELECT_GAME:
    02549       case WC_GAME_OPTIONS:
    02550       case WC_NETWORK_WINDOW:
    02551         top = (newh - w->height) >> 1;
    02552         left = (neww - w->width) >> 1;
    02553         break;
    02554
    02555       case WC_NEWS_WINDOW:
    02556         top = newh - w->height;
    02557         left = (neww - w->width) >> 1;
    02558         break;
    02559
    02560       case WC_STATUS_BAR:
    02561         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0);
    02562         top = newh - w->height;
    02563         left = (neww - w->width) >> 1;
    02564         break;
    02565
    02566       case WC_SEND_NETWORK_MSG:
    02567         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0);
    02568         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
    02569         left = (neww - w->width) >> 1;
    02570         break;
    02571
    02572       case WC_CONSOLE:
    02573         IConsoleResize(w);
    02574         continue;
    02575
    02576       default: {
    02577         left = w->left;
    02578         if (left + (w->width >> 1) >= neww) left = neww - w->width;
    02579         if (left < 0) left = 0;
    02580
    02581         top = w->top;
    02582         if (top + (w->height >> 1) >= newh) top = newh - w->height;
    02583
    02584         const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
    02585         if (wt != NULL) {
    02586           if (top < wt->height && wt->left < (w->left + w->width) && (wt->left + wt->width) > w->left) top = wt->height;
    02587           if (top >= newh) top = newh - 1;
    02588         } else {
    02589           if (top < 0) top = 0;
    02590         }
    02591       } break;
    02592     }
    02593
    02594     if (w->viewport != NULL) {
    02595       w->viewport->left += left - w->left;
    02596       w->viewport->top += top - w->top;
    02597     }
    02598
    02599     w->left = left;
    02600     w->top = top;
    02601   }
    02602 }
    02603
    02608 PickerWindowBase::~PickerWindowBase()
    02609 {
    02610   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
    02611   ResetObjectToPlace();
    02612 }