window.cpp

Go to the documentation of this file.
00001 /* $Id$ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include <stdarg.h>
00014 #include "company_func.h"
00015 #include "gfx_func.h"
00016 #include "console_func.h"
00017 #include "console_gui.h"
00018 #include "viewport_func.h"
00019 #include "genworld.h"
00020 #include "progress.h"
00021 #include "blitter/factory.hpp"
00022 #include "zoom_func.h"
00023 #include "vehicle_base.h"
00024 #include "window_func.h"
00025 #include "tilehighlight_func.h"
00026 #include "network/network.h"
00027 #include "querystring_gui.h"
00028 #include "widgets/dropdown_func.h"
00029 #include "strings_func.h"
00030 #include "settings_type.h"
00031 #include "newgrf_debug.h"
00032 #include "hotkeys.h"
00033 #include "toolbar_gui.h"
00034 #include "statusbar_gui.h"
00035 #include "error.h"
00036 
00037 
00038 static Point _drag_delta; 
00039 static Window *_mouseover_last_w = NULL; 
00040 static Window *_last_scroll_window = NULL; 
00041 
00043 Window *_z_front_window = NULL;
00045 Window *_z_back_window  = NULL;
00046 
00047 /*
00048  * Window that currently has focus. - The main purpose is to generate
00049  * #FocusLost events, not to give next window in z-order focus when a
00050  * window is closed.
00051  */
00052 Window *_focused_window;
00053 
00054 Point _cursorpos_drag_start;
00055 
00056 int _scrollbar_start_pos;
00057 int _scrollbar_size;
00058 byte _scroller_click_timeout = 0;
00059 
00060 bool _scrolling_viewport;  
00061 bool _mouse_hovering;      
00062 
00063 SpecialMouseMode _special_mouse_mode; 
00064 
00066 WindowDesc::WindowDesc(WindowPosition def_pos, int16 def_width, int16 def_height,
00067       WindowClass window_class, WindowClass parent_class, uint32 flags,
00068       const NWidgetPart *nwid_parts, int16 nwid_length) :
00069   default_pos(def_pos),
00070   default_width(def_width),
00071   default_height(def_height),
00072   cls(window_class),
00073   parent_cls(parent_class),
00074   flags(flags),
00075   nwid_parts(nwid_parts),
00076   nwid_length(nwid_length)
00077 {
00078 }
00079 
00080 WindowDesc::~WindowDesc()
00081 {
00082 }
00083 
00093 int Window::GetRowFromWidget(int clickpos, int widget, int padding, int line_height) const
00094 {
00095   const NWidgetBase *wid = this->GetWidget<NWidgetBase>(widget);
00096   if (line_height < 0) line_height = wid->resize_y;
00097   if (clickpos < (int)wid->pos_y + padding) return INT_MAX;
00098   return (clickpos - (int)wid->pos_y - padding) / line_height;
00099 }
00100 
00106 const Scrollbar *Window::GetScrollbar(uint widnum) const
00107 {
00108   return this->GetWidget<NWidgetScrollbar>(widnum);
00109 }
00110 
00116 Scrollbar *Window::GetScrollbar(uint widnum)
00117 {
00118   return this->GetWidget<NWidgetScrollbar>(widnum);
00119 }
00120 
00121 
00126 void SetFocusedWindow(Window *w)
00127 {
00128   if (_focused_window == w) return;
00129 
00130   /* Invalidate focused widget */
00131   if (_focused_window != NULL) {
00132     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
00133   }
00134 
00135   /* Remember which window was previously focused */
00136   Window *old_focused = _focused_window;
00137   _focused_window = w;
00138 
00139   /* So we can inform it that it lost focus */
00140   if (old_focused != NULL) old_focused->OnFocusLost();
00141   if (_focused_window != NULL) _focused_window->OnFocus();
00142 }
00143 
00149 static bool EditBoxInGlobalFocus()
00150 {
00151   if (_focused_window == NULL) return false;
00152 
00153   /* The console does not have an edit box so a special case is needed. */
00154   if (_focused_window->window_class == WC_CONSOLE) return true;
00155 
00156   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
00157 }
00158 
00162 void Window::UnfocusFocusedWidget()
00163 {
00164   if (this->nested_focus != NULL) {
00165     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00166     this->nested_focus->SetDirty(this);
00167     this->nested_focus = NULL;
00168   }
00169 }
00170 
00176 bool Window::SetFocusedWidget(byte widget_index)
00177 {
00178   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
00179   if (widget_index >= this->nested_array_size) return false;
00180 
00181   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
00182   if (this->nested_focus != NULL) {
00183     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
00184 
00185     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00186     this->nested_focus->SetDirty(this);
00187   }
00188   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
00189   return true;
00190 }
00191 
00199 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
00200 {
00201   va_list wdg_list;
00202 
00203   va_start(wdg_list, widgets);
00204 
00205   while (widgets != WIDGET_LIST_END) {
00206     SetWidgetDisabledState(widgets, disab_stat);
00207     widgets = va_arg(wdg_list, int);
00208   }
00209 
00210   va_end(wdg_list);
00211 }
00212 
00218 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
00219 {
00220   va_list wdg_list;
00221 
00222   va_start(wdg_list, widgets);
00223 
00224   while (widgets != WIDGET_LIST_END) {
00225     SetWidgetLoweredState(widgets, lowered_stat);
00226     widgets = va_arg(wdg_list, int);
00227   }
00228 
00229   va_end(wdg_list);
00230 }
00231 
00236 void Window::RaiseButtons(bool autoraise)
00237 {
00238   for (uint i = 0; i < this->nested_array_size; i++) {
00239     if (this->nested_array[i] != NULL && (this->nested_array[i]->type & ~WWB_PUSHBUTTON) < WWT_LAST &&
00240         (!autoraise || (this->nested_array[i]->type & WWB_PUSHBUTTON)) && this->IsWidgetLowered(i)) {
00241       this->RaiseWidget(i);
00242       this->SetWidgetDirty(i);
00243     }
00244   }
00245 }
00246 
00251 void Window::SetWidgetDirty(byte widget_index) const
00252 {
00253   /* Sometimes this function is called before the window is even fully initialized */
00254   if (this->nested_array == NULL) return;
00255 
00256   this->nested_array[widget_index]->SetDirty(this);
00257 }
00258 
00264 void Window::HandleButtonClick(byte widget)
00265 {
00266   this->LowerWidget(widget);
00267   this->flags4 |= WF_TIMEOUT_BEGIN;
00268   this->SetWidgetDirty(widget);
00269 }
00270 
00271 static void StartWindowDrag(Window *w);
00272 static void StartWindowSizing(Window *w, bool to_left);
00273 
00281 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
00282 {
00283   NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
00284   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
00285 
00286   bool focused_widget_changed = false;
00287   /* If clicked on a window that previously did dot have focus */
00288   if (_focused_window != w &&                 // We already have focus, right?
00289       (w->desc_flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
00290       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
00291     focused_widget_changed = true;
00292     if (_focused_window != NULL) {
00293       _focused_window->OnFocusLost();
00294 
00295       /* The window that lost focus may have had opened a OSK, window so close it, unless the user has clicked on the OSK window. */
00296       if (w->window_class != WC_OSK) DeleteWindowById(WC_OSK, 0);
00297     }
00298     SetFocusedWindow(w);
00299     w->OnFocus();
00300   }
00301 
00302   if (nw == NULL) return; // exit if clicked outside of widgets
00303 
00304   /* don't allow any interaction if the button has been disabled */
00305   if (nw->IsDisabled()) return;
00306 
00307   int widget_index = nw->index; 
00308 
00309   /* Clicked on a widget that is not disabled.
00310    * So unless the clicked widget is the caption bar, change focus to this widget */
00311   if (widget_type != WWT_CAPTION) {
00312     /* Close the OSK window if a edit box loses focus */
00313     if (w->nested_focus != NULL &&  w->nested_focus->type == WWT_EDITBOX && w->nested_focus != nw && w->window_class != WC_OSK) {
00314       DeleteWindowById(WC_OSK, 0);
00315     }
00316 
00317     /* focused_widget_changed is 'now' only true if the window this widget
00318      * is in gained focus. In that case it must remain true, also if the
00319      * local widget focus did not change. As such it's the logical-or of
00320      * both changed states.
00321      *
00322      * If this is not preserved, then the OSK window would be opened when
00323      * a user has the edit box focused and then click on another window and
00324      * then back again on the edit box (to type some text).
00325      */
00326     focused_widget_changed |= w->SetFocusedWidget(widget_index);
00327   }
00328 
00329   /* Close any child drop down menus. If the button pressed was the drop down
00330    * list's own button, then we should not process the click any further. */
00331   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
00332 
00333   if ((widget_type & ~WWB_PUSHBUTTON) < WWT_LAST && (widget_type & WWB_PUSHBUTTON)) w->HandleButtonClick(widget_index);
00334 
00335   switch (widget_type) {
00336     case NWID_VSCROLLBAR:
00337     case NWID_HSCROLLBAR:
00338       ScrollbarClickHandler(w, nw, x, y);
00339       break;
00340 
00341     case WWT_EDITBOX:
00342       if (!focused_widget_changed) { // Only open the OSK window if clicking on an already focused edit box
00343         /* Open the OSK window if clicked on an edit box */
00344         QueryStringBaseWindow *qs = dynamic_cast<QueryStringBaseWindow *>(w);
00345         if (qs != NULL) {
00346           qs->OnOpenOSKWindow(widget_index);
00347         }
00348       }
00349       break;
00350 
00351     case WWT_CLOSEBOX: // 'X'
00352       delete w;
00353       return;
00354 
00355     case WWT_CAPTION: // 'Title bar'
00356       StartWindowDrag(w);
00357       return;
00358 
00359     case WWT_RESIZEBOX:
00360       /* When the resize widget is on the left size of the window
00361        * we assume that that button is used to resize to the left. */
00362       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
00363       nw->SetDirty(w);
00364       return;
00365 
00366     case WWT_DEBUGBOX:
00367       w->ShowNewGRFInspectWindow();
00368       break;
00369 
00370     case WWT_SHADEBOX:
00371       nw->SetDirty(w);
00372       w->SetShaded(!w->IsShaded());
00373       return;
00374 
00375     case WWT_STICKYBOX:
00376       w->flags4 ^= WF_STICKY;
00377       nw->SetDirty(w);
00378       return;
00379 
00380     default:
00381       break;
00382   }
00383 
00384   /* Widget has no index, so the window is not interested in it. */
00385   if (widget_index < 0) return;
00386 
00387   Point pt = { x, y };
00388   w->OnClick(pt, widget_index, click_count);
00389 }
00390 
00397 static void DispatchRightClickEvent(Window *w, int x, int y)
00398 {
00399   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00400   if (wid == NULL) return;
00401 
00402   /* No widget to handle, or the window is not interested in it. */
00403   if (wid->index >= 0) {
00404     Point pt = { x, y };
00405     if (w->OnRightClick(pt, wid->index)) return;
00406   }
00407 
00408   if (_settings_client.gui.hover_delay == 0 && wid->tool_tip != 0) GuiShowTooltips(w, wid->tool_tip, 0, NULL, TCC_RIGHT_CLICK);
00409 }
00410 
00417 static void DispatchHoverEvent(Window *w, int x, int y)
00418 {
00419   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00420 
00421   /* No widget to handle */
00422   if (wid == NULL) return;
00423 
00424   /* Show the tooltip if there is any */
00425   if (wid->tool_tip != 0) {
00426     GuiShowTooltips(w, wid->tool_tip);
00427     return;
00428   }
00429 
00430   /* Widget has no index, so the window is not interested in it. */
00431   if (wid->index < 0) return;
00432 
00433   Point pt = { x, y };
00434   w->OnHover(pt, wid->index);
00435 }
00436 
00444 static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel)
00445 {
00446   if (nwid == NULL) return;
00447 
00448   /* Using wheel on caption/shade-box shades or unshades the window. */
00449   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
00450     w->SetShaded(wheel < 0);
00451     return;
00452   }
00453 
00454   /* Wheeling a vertical scrollbar. */
00455   if (nwid->type == NWID_VSCROLLBAR) {
00456     NWidgetScrollbar *sb = static_cast<NWidgetScrollbar *>(nwid);
00457     if (sb->GetCount() > sb->GetCapacity()) {
00458       sb->UpdatePosition(wheel);
00459       w->SetDirty();
00460     }
00461     return;
00462   }
00463 
00464   /* Scroll the widget attached to the scrollbar. */
00465   Scrollbar *sb = (nwid->scrollbar_index >= 0 ? w->GetScrollbar(nwid->scrollbar_index) : NULL);
00466   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
00467     sb->UpdatePosition(wheel);
00468     w->SetDirty();
00469   }
00470 }
00471 
00477 static bool MayBeShown(const Window *w)
00478 {
00479   /* If we're not modal, everything is okay. */
00480   if (!HasModalProgress()) return true;
00481 
00482   switch (w->window_class) {
00483     case WC_MAIN_WINDOW:    
00484     case WC_MODAL_PROGRESS: 
00485     case WC_QUERY_STRING:   
00486       return true;
00487 
00488     default:
00489       return false;
00490   }
00491 }
00492 
00505 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
00506 {
00507   const Window *v;
00508   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
00509     if (MayBeShown(v) &&
00510         right > v->left &&
00511         bottom > v->top &&
00512         left < v->left + v->width &&
00513         top < v->top + v->height) {
00514       /* v and rectangle intersect with each other */
00515       int x;
00516 
00517       if (left < (x = v->left)) {
00518         DrawOverlappedWindow(w, left, top, x, bottom);
00519         DrawOverlappedWindow(w, x, top, right, bottom);
00520         return;
00521       }
00522 
00523       if (right > (x = v->left + v->width)) {
00524         DrawOverlappedWindow(w, left, top, x, bottom);
00525         DrawOverlappedWindow(w, x, top, right, bottom);
00526         return;
00527       }
00528 
00529       if (top < (x = v->top)) {
00530         DrawOverlappedWindow(w, left, top, right, x);
00531         DrawOverlappedWindow(w, left, x, right, bottom);
00532         return;
00533       }
00534 
00535       if (bottom > (x = v->top + v->height)) {
00536         DrawOverlappedWindow(w, left, top, right, x);
00537         DrawOverlappedWindow(w, left, x, right, bottom);
00538         return;
00539       }
00540 
00541       return;
00542     }
00543   }
00544 
00545   /* Setup blitter, and dispatch a repaint event to window *wz */
00546   DrawPixelInfo *dp = _cur_dpi;
00547   dp->width = right - left;
00548   dp->height = bottom - top;
00549   dp->left = left - w->left;
00550   dp->top = top - w->top;
00551   dp->pitch = _screen.pitch;
00552   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
00553   dp->zoom = ZOOM_LVL_NORMAL;
00554   w->OnPaint();
00555 }
00556 
00565 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
00566 {
00567   Window *w;
00568   DrawPixelInfo bk;
00569   _cur_dpi = &bk;
00570 
00571   FOR_ALL_WINDOWS_FROM_BACK(w) {
00572     if (MayBeShown(w) &&
00573         right > w->left &&
00574         bottom > w->top &&
00575         left < w->left + w->width &&
00576         top < w->top + w->height) {
00577       /* Window w intersects with the rectangle => needs repaint */
00578       DrawOverlappedWindow(w, left, top, right, bottom);
00579     }
00580   }
00581 }
00582 
00587 void Window::SetDirty() const
00588 {
00589   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
00590 }
00591 
00598 void Window::ReInit(int rx, int ry)
00599 {
00600   this->SetDirty(); // Mark whole current window as dirty.
00601 
00602   /* Save current size. */
00603   int window_width  = this->width;
00604   int window_height = this->height;
00605 
00606   this->OnInit();
00607   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
00608   this->nested_root->SetupSmallestSize(this, false);
00609   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
00610   this->width  = this->nested_root->smallest_x;
00611   this->height = this->nested_root->smallest_y;
00612   this->resize.step_width  = this->nested_root->resize_x;
00613   this->resize.step_height = this->nested_root->resize_y;
00614 
00615   /* Resize as close to the original size + requested resize as possible. */
00616   window_width  = max(window_width  + rx, this->width);
00617   window_height = max(window_height + ry, this->height);
00618   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
00619   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
00620   /* dx and dy has to go by step.. calculate it.
00621    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
00622   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
00623   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
00624 
00625   ResizeWindow(this, dx, dy);
00626   /* ResizeWindow() does this->SetDirty() already, no need to do it again here. */
00627 }
00628 
00634 void Window::SetShaded(bool make_shaded)
00635 {
00636   if (this->shade_select == NULL) return;
00637 
00638   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
00639   if (this->shade_select->shown_plane != desired) {
00640     if (make_shaded) {
00641       this->unshaded_size.width  = this->width;
00642       this->unshaded_size.height = this->height;
00643       this->shade_select->SetDisplayedPlane(desired);
00644       this->ReInit(0, -this->height);
00645     } else {
00646       this->shade_select->SetDisplayedPlane(desired);
00647       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
00648       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
00649       this->ReInit(dx, dy);
00650     }
00651   }
00652 }
00653 
00660 static Window *FindChildWindow(const Window *w, WindowClass wc)
00661 {
00662   Window *v;
00663   FOR_ALL_WINDOWS_FROM_BACK(v) {
00664     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
00665   }
00666 
00667   return NULL;
00668 }
00669 
00674 void Window::DeleteChildWindows(WindowClass wc) const
00675 {
00676   Window *child = FindChildWindow(this, wc);
00677   while (child != NULL) {
00678     delete child;
00679     child = FindChildWindow(this, wc);
00680   }
00681 }
00682 
00686 Window::~Window()
00687 {
00688   if (_thd.window_class == this->window_class &&
00689       _thd.window_number == this->window_number) {
00690     ResetObjectToPlace();
00691   }
00692 
00693   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
00694   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
00695 
00696   /* We can't scroll the window when it's closed. */
00697   if (_last_scroll_window == this) _last_scroll_window = NULL;
00698 
00699   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
00700   if (_focused_window == this) _focused_window = NULL;
00701 
00702   this->DeleteChildWindows();
00703 
00704   if (this->viewport != NULL) DeleteWindowViewport(this);
00705 
00706   this->SetDirty();
00707 
00708   free(this->nested_array); // Contents is released through deletion of #nested_root.
00709   delete this->nested_root;
00710 
00711   this->window_class = WC_INVALID;
00712 }
00713 
00720 Window *FindWindowById(WindowClass cls, WindowNumber number)
00721 {
00722   Window *w;
00723   FOR_ALL_WINDOWS_FROM_BACK(w) {
00724     if (w->window_class == cls && w->window_number == number) return w;
00725   }
00726 
00727   return NULL;
00728 }
00729 
00736 Window *FindWindowByClass(WindowClass cls)
00737 {
00738   Window *w;
00739   FOR_ALL_WINDOWS_FROM_BACK(w) {
00740     if (w->window_class == cls) return w;
00741   }
00742 
00743   return NULL;
00744 }
00745 
00752 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
00753 {
00754   Window *w = FindWindowById(cls, number);
00755   if (force || w == NULL ||
00756       (w->flags4 & WF_STICKY) == 0) {
00757     delete w;
00758   }
00759 }
00760 
00765 void DeleteWindowByClass(WindowClass cls)
00766 {
00767   Window *w;
00768 
00769 restart_search:
00770   /* When we find the window to delete, we need to restart the search
00771    * as deleting this window could cascade in deleting (many) others
00772    * anywhere in the z-array */
00773   FOR_ALL_WINDOWS_FROM_BACK(w) {
00774     if (w->window_class == cls) {
00775       delete w;
00776       goto restart_search;
00777     }
00778   }
00779 }
00780 
00787 void DeleteCompanyWindows(CompanyID id)
00788 {
00789   Window *w;
00790 
00791 restart_search:
00792   /* When we find the window to delete, we need to restart the search
00793    * as deleting this window could cascade in deleting (many) others
00794    * anywhere in the z-array */
00795   FOR_ALL_WINDOWS_FROM_BACK(w) {
00796     if (w->owner == id) {
00797       delete w;
00798       goto restart_search;
00799     }
00800   }
00801 
00802   /* Also delete the company specific windows that don't have a company-colour. */
00803   DeleteWindowById(WC_BUY_COMPANY, id);
00804 }
00805 
00813 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
00814 {
00815   Window *w;
00816   FOR_ALL_WINDOWS_FROM_BACK(w) {
00817     if (w->owner != old_owner) continue;
00818 
00819     switch (w->window_class) {
00820       case WC_COMPANY_COLOUR:
00821       case WC_FINANCES:
00822       case WC_STATION_LIST:
00823       case WC_TRAINS_LIST:
00824       case WC_ROADVEH_LIST:
00825       case WC_SHIPS_LIST:
00826       case WC_AIRCRAFT_LIST:
00827       case WC_BUY_COMPANY:
00828       case WC_COMPANY:
00829       case WC_COMPANY_INFRASTRUCTURE:
00830         continue;
00831 
00832       default:
00833         w->owner = new_owner;
00834         break;
00835     }
00836   }
00837 }
00838 
00839 static void BringWindowToFront(Window *w);
00840 
00848 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
00849 {
00850   Window *w = FindWindowById(cls, number);
00851 
00852   if (w != NULL) {
00853     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
00854 
00855     w->flags4 |= WF_WHITE_BORDER_MASK;
00856     BringWindowToFront(w);
00857     w->SetDirty();
00858   }
00859 
00860   return w;
00861 }
00862 
00863 static inline bool IsVitalWindow(const Window *w)
00864 {
00865   switch (w->window_class) {
00866     case WC_MAIN_TOOLBAR:
00867     case WC_STATUS_BAR:
00868     case WC_NEWS_WINDOW:
00869     case WC_SEND_NETWORK_MSG:
00870       return true;
00871 
00872     default:
00873       return false;
00874   }
00875 }
00876 
00885 static uint GetWindowZPriority(const Window *w)
00886 {
00887   assert(w->window_class != WC_INVALID);
00888 
00889   uint z_priority = 0;
00890 
00891   switch (w->window_class) {
00892     case WC_ENDSCREEN:
00893       ++z_priority;
00894 
00895     case WC_HIGHSCORE:
00896       ++z_priority;
00897 
00898     case WC_TOOLTIPS:
00899       ++z_priority;
00900 
00901     case WC_DROPDOWN_MENU:
00902       ++z_priority;
00903 
00904     case WC_MAIN_TOOLBAR:
00905     case WC_STATUS_BAR:
00906       ++z_priority;
00907 
00908     case WC_QUERY_STRING:
00909       ++z_priority;
00910 
00911     case WC_ERRMSG:
00912     case WC_CONFIRM_POPUP_QUERY:
00913     case WC_MODAL_PROGRESS:
00914     case WC_NETWORK_STATUS_WINDOW:
00915       ++z_priority;
00916 
00917     case WC_SAVELOAD:
00918     case WC_GAME_OPTIONS:
00919     case WC_CUSTOM_CURRENCY:
00920     case WC_NETWORK_WINDOW:
00921     case WC_GRF_PARAMETERS:
00922     case WC_NEWGRF_TEXTFILE:
00923     case WC_AI_LIST:
00924     case WC_AI_SETTINGS:
00925       ++z_priority;
00926 
00927     case WC_CONSOLE:
00928       ++z_priority;
00929 
00930     case WC_SEND_NETWORK_MSG:
00931     case WC_NEWS_WINDOW:
00932       ++z_priority;
00933 
00934     default:
00935       ++z_priority;
00936 
00937     case WC_MAIN_WINDOW:
00938       return z_priority;
00939   }
00940 }
00941 
00946 static void AddWindowToZOrdering(Window *w)
00947 {
00948   assert(w->z_front == NULL && w->z_back == NULL);
00949 
00950   if (_z_front_window == NULL) {
00951     /* It's the only window. */
00952     _z_front_window = _z_back_window = w;
00953     w->z_front = w->z_back = NULL;
00954   } else {
00955     /* Search down the z-ordering for its location. */
00956     Window *v = _z_front_window;
00957     uint last_z_priority = UINT_MAX;
00958     while (v != NULL && (v->window_class == WC_INVALID || GetWindowZPriority(v) > GetWindowZPriority(w))) {
00959       if (v->window_class != WC_INVALID) {
00960         /* Sanity check z-ordering, while we're at it. */
00961         assert(last_z_priority >= GetWindowZPriority(v));
00962         last_z_priority = GetWindowZPriority(v);
00963       }
00964 
00965       v = v->z_back;
00966     }
00967 
00968     if (v == NULL) {
00969       /* It's the new back window. */
00970       w->z_front = _z_back_window;
00971       w->z_back = NULL;
00972       _z_back_window->z_back = w;
00973       _z_back_window = w;
00974     } else if (v == _z_front_window) {
00975       /* It's the new front window. */
00976       w->z_front = NULL;
00977       w->z_back = _z_front_window;
00978       _z_front_window->z_front = w;
00979       _z_front_window = w;
00980     } else {
00981       /* It's somewhere else in the z-ordering. */
00982       w->z_front = v->z_front;
00983       w->z_back = v;
00984       v->z_front->z_back = w;
00985       v->z_front = w;
00986     }
00987   }
00988 }
00989 
00990 
00995 static void RemoveWindowFromZOrdering(Window *w)
00996 {
00997   if (w->z_front == NULL) {
00998     assert(_z_front_window == w);
00999     _z_front_window = w->z_back;
01000   } else {
01001     w->z_front->z_back = w->z_back;
01002   }
01003 
01004   if (w->z_back == NULL) {
01005     assert(_z_back_window == w);
01006     _z_back_window = w->z_front;
01007   } else {
01008     w->z_back->z_front = w->z_front;
01009   }
01010 
01011   w->z_front = w->z_back = NULL;
01012 }
01013 
01019 static void BringWindowToFront(Window *w)
01020 {
01021   RemoveWindowFromZOrdering(w);
01022   AddWindowToZOrdering(w);
01023 
01024   w->SetDirty();
01025 }
01026 
01035 void Window::InitializeData(const WindowDesc *desc, WindowNumber window_number)
01036 {
01037   /* Set up window properties; some of them are needed to set up smallest size below */
01038   this->window_class = desc->cls;
01039   this->flags4 |= WF_WHITE_BORDER_MASK; // just opened windows have a white border
01040   if (desc->default_pos == WDP_CENTER) this->flags4 |= WF_CENTERED;
01041   this->owner = INVALID_OWNER;
01042   this->nested_focus = NULL;
01043   this->window_number = window_number;
01044   this->desc_flags = desc->flags;
01045 
01046   this->OnInit();
01047   /* Initialize nested widget tree. */
01048   if (this->nested_array == NULL) {
01049     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01050     this->nested_root->SetupSmallestSize(this, true);
01051   } else {
01052     this->nested_root->SetupSmallestSize(this, false);
01053   }
01054   /* Initialize to smallest size. */
01055   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
01056 
01057   /* Further set up window properties,
01058    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
01059   this->resize.step_width  = this->nested_root->resize_x;
01060   this->resize.step_height = this->nested_root->resize_y;
01061 
01062   /* Give focus to the opened window unless it is the OSK window or a text box
01063    * of focused window has focus (so we don't interrupt typing). But if the new
01064    * window has a text box, then take focus anyway. */
01065   if (this->window_class != WC_OSK && (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL)) SetFocusedWindow(this);
01066 
01067   /* Insert the window into the correct location in the z-ordering. */
01068   AddWindowToZOrdering(this);
01069 }
01070 
01078 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
01079 {
01080   this->left = x;
01081   this->top = y;
01082   this->width = sm_width;
01083   this->height = sm_height;
01084 }
01085 
01096 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
01097 {
01098   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
01099   def_height = max(def_height, this->height);
01100   /* Try to make windows smaller when our window is too small.
01101    * w->(width|height) is normally the same as min_(width|height),
01102    * but this way the GUIs can be made a little more dynamic;
01103    * one can use the same spec for multiple windows and those
01104    * can then determine the real minimum size of the window. */
01105   if (this->width != def_width || this->height != def_height) {
01106     /* Think about the overlapping toolbars when determining the minimum window size */
01107     int free_height = _screen.height;
01108     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
01109     if (wt != NULL) free_height -= wt->height;
01110     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01111     if (wt != NULL) free_height -= wt->height;
01112 
01113     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
01114     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
01115 
01116     /* X and Y has to go by step.. calculate it.
01117      * The cast to int is necessary else x/y are implicitly casted to
01118      * unsigned int, which won't work. */
01119     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
01120     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
01121 
01122     ResizeWindow(this, enlarge_x, enlarge_y);
01123     /* ResizeWindow() calls this->OnResize(). */
01124   } else {
01125     /* Always call OnResize; that way the scrollbars and matrices get initialized. */
01126     this->OnResize();
01127   }
01128 
01129   int nx = this->left;
01130   int ny = this->top;
01131 
01132   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
01133 
01134   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01135   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
01136   nx = max(nx, 0);
01137 
01138   if (this->viewport != NULL) {
01139     this->viewport->left += nx - this->left;
01140     this->viewport->top  += ny - this->top;
01141   }
01142   this->left = nx;
01143   this->top = ny;
01144 
01145   this->SetDirty();
01146 }
01147 
01159 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
01160 {
01161   int right  = width + left;
01162   int bottom = height + top;
01163 
01164   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01165   if (left < 0 || (main_toolbar != NULL && top < main_toolbar->height) || right > _screen.width || bottom > _screen.height) return false;
01166 
01167   /* Make sure it is not obscured by any window. */
01168   const Window *w;
01169   FOR_ALL_WINDOWS_FROM_BACK(w) {
01170     if (w->window_class == WC_MAIN_WINDOW) continue;
01171 
01172     if (right > w->left &&
01173         w->left + w->width > left &&
01174         bottom > w->top &&
01175         w->top + w->height > top) {
01176       return false;
01177     }
01178   }
01179 
01180   pos.x = left;
01181   pos.y = top;
01182   return true;
01183 }
01184 
01196 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
01197 {
01198   /* Left part of the rectangle may be at most 1/4 off-screen,
01199    * right part of the rectangle may be at most 1/2 off-screen
01200    */
01201   if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
01202   /* Bottom part of the rectangle may be at most 1/4 off-screen */
01203   if (top < 22 || top > _screen.height - (height >> 2)) return false;
01204 
01205   /* Make sure it is not obscured by any window. */
01206   const Window *w;
01207   FOR_ALL_WINDOWS_FROM_BACK(w) {
01208     if (w->window_class == WC_MAIN_WINDOW) continue;
01209 
01210     if (left + width > w->left &&
01211         w->left + w->width > left &&
01212         top + height > w->top &&
01213         w->top + w->height > top) {
01214       return false;
01215     }
01216   }
01217 
01218   pos.x = left;
01219   pos.y = top;
01220   return true;
01221 }
01222 
01229 static Point GetAutoPlacePosition(int width, int height)
01230 {
01231   Point pt;
01232 
01233   /* First attempt, try top-left of the screen */
01234   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01235   if (IsGoodAutoPlace1(0, main_toolbar != NULL ? main_toolbar->height + 2 : 2, width, height, pt)) return pt;
01236 
01237   /* Second attempt, try around all existing windows with a distance of 2 pixels.
01238    * The new window must be entirely on-screen, and not overlap with an existing window.
01239    * Eight starting points are tried, two at each corner.
01240    */
01241   const Window *w;
01242   FOR_ALL_WINDOWS_FROM_BACK(w) {
01243     if (w->window_class == WC_MAIN_WINDOW) continue;
01244 
01245     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01246     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
01247     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01248     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
01249     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
01250     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
01251     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
01252     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
01253   }
01254 
01255   /* Third attempt, try around all existing windows with a distance of 2 pixels.
01256    * The new window may be partly off-screen, and must not overlap with an existing window.
01257    * Only four starting points are tried.
01258    */
01259   FOR_ALL_WINDOWS_FROM_BACK(w) {
01260     if (w->window_class == WC_MAIN_WINDOW) continue;
01261 
01262     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01263     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
01264     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01265     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
01266   }
01267 
01268   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
01269    * of (+5, +5)
01270    */
01271   int left = 0, top = 24;
01272 
01273 restart:
01274   FOR_ALL_WINDOWS_FROM_BACK(w) {
01275     if (w->left == left && w->top == top) {
01276       left += 5;
01277       top += 5;
01278       goto restart;
01279     }
01280   }
01281 
01282   pt.x = left;
01283   pt.y = top;
01284   return pt;
01285 }
01286 
01293 Point GetToolbarAlignedWindowPosition(int window_width)
01294 {
01295   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01296   assert(w != NULL);
01297   Point pt = { _current_text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
01298   return pt;
01299 }
01300 
01318 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01319 {
01320   Point pt;
01321   const Window *w;
01322 
01323   int16 default_width  = max(desc->default_width,  sm_width);
01324   int16 default_height = max(desc->default_height, sm_height);
01325 
01326   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
01327       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
01328       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
01329 
01330     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
01331     if (pt.x > _screen.width + 10 - default_width) {
01332       pt.x = (_screen.width + 10 - default_width) - 20;
01333     }
01334     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
01335     return pt;
01336   }
01337 
01338   switch (desc->default_pos) {
01339     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
01340       return GetToolbarAlignedWindowPosition(default_width);
01341 
01342     case WDP_AUTO: // Find a good automatic position for the window
01343       return GetAutoPlacePosition(default_width, default_height);
01344 
01345     case WDP_CENTER: // Centre the window horizontally
01346       pt.x = (_screen.width - default_width) / 2;
01347       pt.y = (_screen.height - default_height) / 2;
01348       break;
01349 
01350     case WDP_MANUAL:
01351       pt.x = 0;
01352       pt.y = 0;
01353       break;
01354 
01355     default:
01356       NOT_REACHED();
01357   }
01358 
01359   return pt;
01360 }
01361 
01362 /* virtual */ Point Window::OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01363 {
01364   return LocalGetWindowPlacement(desc, sm_width, sm_height, window_number);
01365 }
01366 
01375 void Window::CreateNestedTree(const WindowDesc *desc, bool fill_nested)
01376 {
01377   int biggest_index = -1;
01378   this->nested_root = MakeWindowNWidgetTree(desc->nwid_parts, desc->nwid_length, &biggest_index, &this->shade_select);
01379   this->nested_array_size = (uint)(biggest_index + 1);
01380 
01381   if (fill_nested) {
01382     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01383     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
01384   }
01385 }
01386 
01392 void Window::FinishInitNested(const WindowDesc *desc, WindowNumber window_number)
01393 {
01394   this->InitializeData(desc, window_number);
01395   Point pt = this->OnInitialPosition(desc, this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
01396   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
01397   this->FindWindowPlacementAndResize(desc->default_width, desc->default_height);
01398 }
01399 
01405 void Window::InitNested(const WindowDesc *desc, WindowNumber window_number)
01406 {
01407   this->CreateNestedTree(desc, false);
01408   this->FinishInitNested(desc, window_number);
01409 }
01410 
01412 Window::Window() : scrolling_scrollbar(-1)
01413 {
01414 }
01415 
01423 Window *FindWindowFromPt(int x, int y)
01424 {
01425   Window *w;
01426   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01427     if (MayBeShown(w) && IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
01428       return w;
01429     }
01430   }
01431 
01432   return NULL;
01433 }
01434 
01438 void InitWindowSystem()
01439 {
01440   IConsoleClose();
01441 
01442   _z_back_window = NULL;
01443   _z_front_window = NULL;
01444   _focused_window = NULL;
01445   _mouseover_last_w = NULL;
01446   _last_scroll_window = NULL;
01447   _scrolling_viewport = false;
01448   _mouse_hovering = false;
01449 
01450   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
01451   NWidgetScrollbar::InvalidateDimensionCache();
01452 
01453   ShowFirstError();
01454 }
01455 
01459 void UnInitWindowSystem()
01460 {
01461   UnshowCriticalError();
01462 
01463   Window *w;
01464   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
01465 
01466   for (w = _z_front_window; w != NULL; /* nothing */) {
01467     Window *to_del = w;
01468     w = w->z_back;
01469     free(to_del);
01470   }
01471 
01472   _z_front_window = NULL;
01473   _z_back_window = NULL;
01474 }
01475 
01479 void ResetWindowSystem()
01480 {
01481   UnInitWindowSystem();
01482   InitWindowSystem();
01483   _thd.Reset();
01484 }
01485 
01486 static void DecreaseWindowCounters()
01487 {
01488   Window *w;
01489   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01490     if (_scroller_click_timeout == 0) {
01491       /* Unclick scrollbar buttons if they are pressed. */
01492       for (uint i = 0; i < w->nested_array_size; i++) {
01493         NWidgetBase *nwid = w->nested_array[i];
01494         if (nwid != NULL && (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR)) {
01495           NWidgetScrollbar *sb = static_cast<NWidgetScrollbar*>(nwid);
01496           if (sb->disp_flags & (ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN)) {
01497             sb->disp_flags &= ~(ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN);
01498             w->scrolling_scrollbar = -1;
01499             sb->SetDirty(w);
01500           }
01501         }
01502       }
01503     }
01504     w->OnMouseLoop();
01505   }
01506 
01507   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01508     if ((w->flags4 & WF_TIMEOUT_MASK) && !(--w->flags4 & WF_TIMEOUT_MASK)) {
01509       w->OnTimeout();
01510       if (w->desc_flags & WDF_UNCLICK_BUTTONS) w->RaiseButtons(true);
01511     }
01512   }
01513 }
01514 
01515 static void HandlePlacePresize()
01516 {
01517   if (_special_mouse_mode != WSM_PRESIZE) return;
01518 
01519   Window *w = _thd.GetCallbackWnd();
01520   if (w == NULL) return;
01521 
01522   Point pt = GetTileBelowCursor();
01523   if (pt.x == -1) {
01524     _thd.selend.x = -1;
01525     return;
01526   }
01527 
01528   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
01529 }
01530 
01535 static EventState HandleMouseDragDrop()
01536 {
01537   if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
01538 
01539   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED; // Dragging, but the mouse did not move.
01540 
01541   Window *w = _thd.GetCallbackWnd();
01542   if (w != NULL) {
01543     /* Send an event in client coordinates. */
01544     Point pt;
01545     pt.x = _cursor.pos.x - w->left;
01546     pt.y = _cursor.pos.y - w->top;
01547     if (_left_button_down) {
01548       w->OnMouseDrag(pt, GetWidgetFromPos(w, pt.x, pt.y));
01549     } else {
01550       w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
01551     }
01552   }
01553 
01554   if (!_left_button_down) ResetObjectToPlace(); // Button released, finished dragging.
01555   return ES_HANDLED;
01556 }
01557 
01559 static void HandleMouseOver()
01560 {
01561   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01562 
01563   /* We changed window, put a MOUSEOVER event to the last window */
01564   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
01565     /* Reset mouse-over coordinates of previous window */
01566     Point pt = { -1, -1 };
01567     _mouseover_last_w->OnMouseOver(pt, 0);
01568   }
01569 
01570   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
01571   _mouseover_last_w = w;
01572 
01573   if (w != NULL) {
01574     /* send an event in client coordinates. */
01575     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
01576     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
01577     if (widget != NULL) w->OnMouseOver(pt, widget->index);
01578   }
01579 }
01580 
01582 static const int MIN_VISIBLE_TITLE_BAR = 13;
01583 
01585 enum PreventHideDirection {
01586   PHD_UP,   
01587   PHD_DOWN, 
01588 };
01589 
01600 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
01601 {
01602   if (v == NULL) return;
01603 
01604   int v_bottom = v->top + v->height;
01605   int v_right = v->left + v->width;
01606   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.
01607 
01608   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
01609   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
01610 
01611   /* Vertically, the rectangle is hidden behind v. */
01612   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
01613     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
01614     return;
01615   }
01616   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
01617     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
01618     return;
01619   }
01620 
01621   /* Horizontally also hidden, force movement to a safe area. */
01622   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
01623     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
01624   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
01625     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
01626   } else {
01627     *ny = safe_y;
01628   }
01629 }
01630 
01638 static void EnsureVisibleCaption(Window *w, int nx, int ny)
01639 {
01640   /* Search for the title bar rectangle. */
01641   Rect caption_rect;
01642   const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
01643   if (caption != NULL) {
01644     caption_rect.left   = caption->pos_x;
01645     caption_rect.right  = caption->pos_x + caption->current_x;
01646     caption_rect.top    = caption->pos_y;
01647     caption_rect.bottom = caption->pos_y + caption->current_y;
01648 
01649     /* Make sure the window doesn't leave the screen */
01650     nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
01651     ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
01652 
01653     /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
01654     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
01655     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
01656   }
01657 
01658   if (w->viewport != NULL) {
01659     w->viewport->left += nx - w->left;
01660     w->viewport->top  += ny - w->top;
01661   }
01662 
01663   w->left = nx;
01664   w->top  = ny;
01665 }
01666 
01676 void ResizeWindow(Window *w, int delta_x, int delta_y)
01677 {
01678   if (delta_x != 0 || delta_y != 0) {
01679     /* Determine the new right/bottom position. If that is outside of the bounds of
01680      * the resolution clamp it in such a manner that it stays within the bounts. */
01681     int new_right  = w->left + w->width  + delta_x;
01682     int new_bottom = w->top  + w->height + delta_y;
01683     if (new_right  >= (int)_cur_resolution.width)  delta_x -= Ceil(new_right  - _cur_resolution.width,  max(1U, w->nested_root->resize_x));
01684     if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, max(1U, w->nested_root->resize_y));
01685 
01686     w->SetDirty();
01687 
01688     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);
01689     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);
01690     assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
01691     assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
01692 
01693     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);
01694     w->width  = w->nested_root->current_x;
01695     w->height = w->nested_root->current_y;
01696   }
01697 
01698   EnsureVisibleCaption(w, w->left, w->top);
01699 
01700   /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */
01701   w->OnResize();
01702   w->SetDirty();
01703 }
01704 
01710 int GetMainViewTop()
01711 {
01712   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01713   return (w == NULL) ? 0 : w->top + w->height;
01714 }
01715 
01721 int GetMainViewBottom()
01722 {
01723   Window *w = FindWindowById(WC_STATUS_BAR, 0);
01724   return (w == NULL) ? _screen.height : w->top;
01725 }
01726 
01727 static bool _dragging_window; 
01728 
01733 static EventState HandleWindowDragging()
01734 {
01735   /* Get out immediately if no window is being dragged at all. */
01736   if (!_dragging_window) return ES_NOT_HANDLED;
01737 
01738   /* If button still down, but cursor hasn't moved, there is nothing to do. */
01739   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
01740 
01741   /* Otherwise find the window... */
01742   Window *w;
01743   FOR_ALL_WINDOWS_FROM_BACK(w) {
01744     if (w->flags4 & WF_DRAGGING) {
01745       /* Stop the dragging if the left mouse button was released */
01746       if (!_left_button_down) {
01747         w->flags4 &= ~WF_DRAGGING;
01748         break;
01749       }
01750 
01751       w->SetDirty();
01752 
01753       int x = _cursor.pos.x + _drag_delta.x;
01754       int y = _cursor.pos.y + _drag_delta.y;
01755       int nx = x;
01756       int ny = y;
01757 
01758       if (_settings_client.gui.window_snap_radius != 0) {
01759         const Window *v;
01760 
01761         int hsnap = _settings_client.gui.window_snap_radius;
01762         int vsnap = _settings_client.gui.window_snap_radius;
01763         int delta;
01764 
01765         FOR_ALL_WINDOWS_FROM_BACK(v) {
01766           if (v == w) continue; // Don't snap at yourself
01767 
01768           if (y + w->height > v->top && y < v->top + v->height) {
01769             /* Your left border <-> other right border */
01770             delta = abs(v->left + v->width - x);
01771             if (delta <= hsnap) {
01772               nx = v->left + v->width;
01773               hsnap = delta;
01774             }
01775 
01776             /* Your right border <-> other left border */
01777             delta = abs(v->left - x - w->width);
01778             if (delta <= hsnap) {
01779               nx = v->left - w->width;
01780               hsnap = delta;
01781             }
01782           }
01783 
01784           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
01785             /* Your left border <-> other left border */
01786             delta = abs(v->left - x);
01787             if (delta <= hsnap) {
01788               nx = v->left;
01789               hsnap = delta;
01790             }
01791 
01792             /* Your right border <-> other right border */
01793             delta = abs(v->left + v->width - x - w->width);
01794             if (delta <= hsnap) {
01795               nx = v->left + v->width - w->width;
01796               hsnap = delta;
01797             }
01798           }
01799 
01800           if (x + w->width > v->left && x < v->left + v->width) {
01801             /* Your top border <-> other bottom border */
01802             delta = abs(v->top + v->height - y);
01803             if (delta <= vsnap) {
01804               ny = v->top + v->height;
01805               vsnap = delta;
01806             }
01807 
01808             /* Your bottom border <-> other top border */
01809             delta = abs(v->top - y - w->height);
01810             if (delta <= vsnap) {
01811               ny = v->top - w->height;
01812               vsnap = delta;
01813             }
01814           }
01815 
01816           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
01817             /* Your top border <-> other top border */
01818             delta = abs(v->top - y);
01819             if (delta <= vsnap) {
01820               ny = v->top;
01821               vsnap = delta;
01822             }
01823 
01824             /* Your bottom border <-> other bottom border */
01825             delta = abs(v->top + v->height - y - w->height);
01826             if (delta <= vsnap) {
01827               ny = v->top + v->height - w->height;
01828               vsnap = delta;
01829             }
01830           }
01831         }
01832       }
01833 
01834       EnsureVisibleCaption(w, nx, ny);
01835 
01836       w->SetDirty();
01837       return ES_HANDLED;
01838     } else if (w->flags4 & WF_SIZING) {
01839       /* Stop the sizing if the left mouse button was released */
01840       if (!_left_button_down) {
01841         w->flags4 &= ~WF_SIZING;
01842         w->SetDirty();
01843         break;
01844       }
01845 
01846       /* Compute difference in pixels between cursor position and reference point in the window.
01847        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
01848        */
01849       int x, y = _cursor.pos.y - _drag_delta.y;
01850       if (w->flags4 & WF_SIZING_LEFT) {
01851         x = _drag_delta.x - _cursor.pos.x;
01852       } else {
01853         x = _cursor.pos.x - _drag_delta.x;
01854       }
01855 
01856       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
01857       if (w->resize.step_width  == 0) x = 0;
01858       if (w->resize.step_height == 0) y = 0;
01859 
01860       /* Check the resize button won't go past the bottom of the screen */
01861       if (w->top + w->height + y > _screen.height) {
01862         y = _screen.height - w->height - w->top;
01863       }
01864 
01865       /* X and Y has to go by step.. calculate it.
01866        * The cast to int is necessary else x/y are implicitly casted to
01867        * unsigned int, which won't work. */
01868       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
01869       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
01870 
01871       /* Check that we don't go below the minimum set size */
01872       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
01873         x = w->nested_root->smallest_x - w->width;
01874       }
01875       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
01876         y = w->nested_root->smallest_y - w->height;
01877       }
01878 
01879       /* Window already on size */
01880       if (x == 0 && y == 0) return ES_HANDLED;
01881 
01882       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
01883       _drag_delta.y += y;
01884       if ((w->flags4 & WF_SIZING_LEFT) && x != 0) {
01885         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
01886         w->SetDirty();
01887         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
01888         /* ResizeWindow() below ensures marking new position as dirty. */
01889       } else {
01890         _drag_delta.x += x;
01891       }
01892 
01893       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
01894       ResizeWindow(w, x, y);
01895       return ES_HANDLED;
01896     }
01897   }
01898 
01899   _dragging_window = false;
01900   return ES_HANDLED;
01901 }
01902 
01907 static void StartWindowDrag(Window *w)
01908 {
01909   w->flags4 |= WF_DRAGGING;
01910   w->flags4 &= ~WF_CENTERED;
01911   _dragging_window = true;
01912 
01913   _drag_delta.x = w->left - _cursor.pos.x;
01914   _drag_delta.y = w->top  - _cursor.pos.y;
01915 
01916   BringWindowToFront(w);
01917   DeleteWindowById(WC_DROPDOWN_MENU, 0);
01918 }
01919 
01925 static void StartWindowSizing(Window *w, bool to_left)
01926 {
01927   w->flags4 |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
01928   w->flags4 &= ~WF_CENTERED;
01929   _dragging_window = true;
01930 
01931   _drag_delta.x = _cursor.pos.x;
01932   _drag_delta.y = _cursor.pos.y;
01933 
01934   BringWindowToFront(w);
01935   DeleteWindowById(WC_DROPDOWN_MENU, 0);
01936 }
01937 
01942 static EventState HandleScrollbarScrolling()
01943 {
01944   Window *w;
01945   FOR_ALL_WINDOWS_FROM_BACK(w) {
01946     if (w->scrolling_scrollbar >= 0) {
01947       /* Abort if no button is clicked any more. */
01948       if (!_left_button_down) {
01949         w->scrolling_scrollbar = -1;
01950         w->SetDirty();
01951         return ES_HANDLED;
01952       }
01953 
01954       int i;
01955       NWidgetScrollbar *sb = w->GetWidget<NWidgetScrollbar>(w->scrolling_scrollbar);
01956       bool rtl = false;
01957 
01958       if (sb->type == NWID_HSCROLLBAR) {
01959         i = _cursor.pos.x - _cursorpos_drag_start.x;
01960         rtl = _current_text_dir == TD_RTL;
01961       } else {
01962         i = _cursor.pos.y - _cursorpos_drag_start.y;
01963       }
01964 
01965       if (sb->disp_flags & ND_SCROLLBAR_BTN) {
01966         if (_scroller_click_timeout == 1) {
01967           _scroller_click_timeout = 3;
01968           sb->UpdatePosition(rtl == HasBit(sb->disp_flags, NDB_SCROLLBAR_UP) ? 1 : -1);
01969           w->SetDirty();
01970         }
01971         return ES_HANDLED;
01972       }
01973 
01974       /* Find the item we want to move to and make sure it's inside bounds. */
01975       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
01976       if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
01977       if (pos != sb->GetPosition()) {
01978         sb->SetPosition(pos);
01979         w->SetDirty();
01980       }
01981       return ES_HANDLED;
01982     }
01983   }
01984 
01985   return ES_NOT_HANDLED;
01986 }
01987 
01992 static EventState HandleViewportScroll()
01993 {
01994   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
01995 
01996   if (!_scrolling_viewport) return ES_NOT_HANDLED;
01997 
01998   /* When we don't have a last scroll window we are starting to scroll.
01999    * When the last scroll window and this are not the same we went
02000    * outside of the window and should not left-mouse scroll anymore. */
02001   if (_last_scroll_window == NULL) _last_scroll_window = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
02002 
02003   if (_last_scroll_window == NULL || !(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down))) {
02004     _cursor.fix_at = false;
02005     _scrolling_viewport = false;
02006     _last_scroll_window = NULL;
02007     return ES_NOT_HANDLED;
02008   }
02009 
02010   if (_last_scroll_window == FindWindowById(WC_MAIN_WINDOW, 0) && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) {
02011     /* If the main window is following a vehicle, then first let go of it! */
02012     const Vehicle *veh = Vehicle::Get(_last_scroll_window->viewport->follow_vehicle);
02013     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
02014     return ES_NOT_HANDLED;
02015   }
02016 
02017   Point delta;
02018   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
02019     delta.x = -_cursor.delta.x;
02020     delta.y = -_cursor.delta.y;
02021   } else {
02022     delta.x = _cursor.delta.x;
02023     delta.y = _cursor.delta.y;
02024   }
02025 
02026   if (scrollwheel_scrolling) {
02027     /* We are using scrollwheels for scrolling */
02028     delta.x = _cursor.h_wheel;
02029     delta.y = _cursor.v_wheel;
02030     _cursor.v_wheel = 0;
02031     _cursor.h_wheel = 0;
02032   }
02033 
02034   /* Create a scroll-event and send it to the window */
02035   if (delta.x != 0 || delta.y != 0) _last_scroll_window->OnScroll(delta);
02036 
02037   _cursor.delta.x = 0;
02038   _cursor.delta.y = 0;
02039   return ES_HANDLED;
02040 }
02041 
02052 static bool MaybeBringWindowToFront(Window *w)
02053 {
02054   bool bring_to_front = false;
02055 
02056   if (w->window_class == WC_MAIN_WINDOW ||
02057       IsVitalWindow(w) ||
02058       w->window_class == WC_TOOLTIPS ||
02059       w->window_class == WC_DROPDOWN_MENU) {
02060     return true;
02061   }
02062 
02063   /* Use unshaded window size rather than current size for shaded windows. */
02064   int w_width  = w->width;
02065   int w_height = w->height;
02066   if (w->IsShaded()) {
02067     w_width  = w->unshaded_size.width;
02068     w_height = w->unshaded_size.height;
02069   }
02070 
02071   Window *u;
02072   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
02073     /* A modal child will prevent the activation of the parent window */
02074     if (u->parent == w && (u->desc_flags & WDF_MODAL)) {
02075       u->flags4 |= WF_WHITE_BORDER_MASK;
02076       u->SetDirty();
02077       return false;
02078     }
02079 
02080     if (u->window_class == WC_MAIN_WINDOW ||
02081         IsVitalWindow(u) ||
02082         u->window_class == WC_TOOLTIPS ||
02083         u->window_class == WC_DROPDOWN_MENU) {
02084       continue;
02085     }
02086 
02087     /* Window sizes don't interfere, leave z-order alone */
02088     if (w->left + w_width <= u->left ||
02089         u->left + u->width <= w->left ||
02090         w->top  + w_height <= u->top ||
02091         u->top + u->height <= w->top) {
02092       continue;
02093     }
02094 
02095     bring_to_front = true;
02096   }
02097 
02098   if (bring_to_front) BringWindowToFront(w);
02099   return true;
02100 }
02101 
02106 void HandleKeypress(uint32 raw_key)
02107 {
02108   /* World generation is multithreaded and messes with companies.
02109    * But there is no company related window open anyway, so _current_company is not used. */
02110   assert(HasModalProgress() || IsLocalCompany());
02111 
02112   /* Setup event */
02113   uint16 key     = GB(raw_key,  0, 16);
02114   uint16 keycode = GB(raw_key, 16, 16);
02115 
02116   /*
02117    * The Unicode standard defines an area called the private use area. Code points in this
02118    * area are reserved for private use and thus not portable between systems. For instance,
02119    * Apple defines code points for the arrow keys in this area, but these are only printable
02120    * on a system running OS X. We don't want these keys to show up in text fields and such,
02121    * and thus we have to clear the unicode character when we encounter such a key.
02122    */
02123   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
02124 
02125   /*
02126    * If both key and keycode is zero, we don't bother to process the event.
02127    */
02128   if (key == 0 && keycode == 0) return;
02129 
02130   /* Check if the focused window has a focused editbox */
02131   if (EditBoxInGlobalFocus()) {
02132     /* All input will in this case go to the focused window */
02133     if (_focused_window->OnKeyPress(key, keycode) == ES_HANDLED) return;
02134   }
02135 
02136   /* Call the event, start with the uppermost window, but ignore the toolbar. */
02137   Window *w;
02138   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02139     if (w->window_class == WC_MAIN_TOOLBAR) continue;
02140     if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02141   }
02142 
02143   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
02144   /* When there is no toolbar w is null, check for that */
02145   if (w != NULL && w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02146 
02147   HandleGlobalHotkeys(key, keycode);
02148 }
02149 
02153 void HandleCtrlChanged()
02154 {
02155   /* Call the event, start with the uppermost window. */
02156   Window *w;
02157   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02158     if (w->OnCTRLStateChange() == ES_HANDLED) return;
02159   }
02160 }
02161 
02168 static int _input_events_this_tick = 0;
02169 
02174 static void HandleAutoscroll()
02175 {
02176   if (_settings_client.gui.autoscroll && _game_mode != GM_MENU && !HasModalProgress()) {
02177     int x = _cursor.pos.x;
02178     int y = _cursor.pos.y;
02179     Window *w = FindWindowFromPt(x, y);
02180     if (w == NULL || w->flags4 & WF_DISABLE_VP_SCROLL) return;
02181     ViewPort *vp = IsPtInWindowViewport(w, x, y);
02182     if (vp != NULL) {
02183       x -= vp->left;
02184       y -= vp->top;
02185 
02186       /* here allows scrolling in both x and y axis */
02187 #define scrollspeed 3
02188       if (x - 15 < 0) {
02189         w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
02190       } else if (15 - (vp->width - x) > 0) {
02191         w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
02192       }
02193       if (y - 15 < 0) {
02194         w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
02195       } else if (15 - (vp->height - y) > 0) {
02196         w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
02197       }
02198 #undef scrollspeed
02199     }
02200   }
02201 }
02202 
02203 enum MouseClick {
02204   MC_NONE = 0,
02205   MC_LEFT,
02206   MC_RIGHT,
02207   MC_DOUBLE_LEFT,
02208   MC_HOVER,
02209 
02210   MAX_OFFSET_DOUBLE_CLICK = 5,     
02211   TIME_BETWEEN_DOUBLE_CLICK = 500, 
02212   MAX_OFFSET_HOVER = 5,            
02213 };
02214 extern EventState VpHandlePlaceSizingDrag();
02215 
02216 static void ScrollMainViewport(int x, int y)
02217 {
02218   if (_game_mode != GM_MENU) {
02219     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
02220     assert(w);
02221 
02222     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
02223     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
02224   }
02225 }
02226 
02236 static const int8 scrollamt[16][2] = {
02237   { 0,  0}, 
02238   {-2,  0}, 
02239   { 0, -2}, 
02240   {-2, -1}, 
02241   { 2,  0}, 
02242   { 0,  0}, 
02243   { 2, -1}, 
02244   { 0, -2}, 
02245   { 0,  2}, 
02246   {-2,  1}, 
02247   { 0,  0}, 
02248   {-2,  0}, 
02249   { 2,  1}, 
02250   { 0,  2}, 
02251   { 2,  0}, 
02252   { 0,  0}, 
02253 };
02254 
02255 static void HandleKeyScrolling()
02256 {
02257   /*
02258    * Check that any of the dirkeys is pressed and that the focused window
02259    * dont has an edit-box as focused widget.
02260    */
02261   if (_dirkeys && !EditBoxInGlobalFocus()) {
02262     int factor = _shift_pressed ? 50 : 10;
02263     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
02264   }
02265 }
02266 
02267 static void MouseLoop(MouseClick click, int mousewheel)
02268 {
02269   /* World generation is multithreaded and messes with companies.
02270    * But there is no company related window open anyway, so _current_company is not used. */
02271   assert(HasModalProgress() || IsLocalCompany());
02272 
02273   HandlePlacePresize();
02274   UpdateTileSelection();
02275 
02276   if (VpHandlePlaceSizingDrag()  == ES_HANDLED) return;
02277   if (HandleMouseDragDrop()      == ES_HANDLED) return;
02278   if (HandleWindowDragging()     == ES_HANDLED) return;
02279   if (HandleScrollbarScrolling() == ES_HANDLED) return;
02280   if (HandleViewportScroll()     == ES_HANDLED) return;
02281 
02282   HandleMouseOver();
02283 
02284   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02285   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
02286 
02287   int x = _cursor.pos.x;
02288   int y = _cursor.pos.y;
02289   Window *w = FindWindowFromPt(x, y);
02290   if (w == NULL) return;
02291 
02292   if (click != MC_HOVER && !MaybeBringWindowToFront(w)) return;
02293   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02294 
02295   /* Don't allow any action in a viewport if either in menu or when having a modal progress window */
02296   if (vp != NULL && (_game_mode == GM_MENU || HasModalProgress())) return;
02297 
02298   if (mousewheel != 0) {
02299     /* Send mousewheel event to window */
02300     w->OnMouseWheel(mousewheel);
02301 
02302     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
02303     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
02304   }
02305 
02306   if (vp != NULL) {
02307     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
02308     switch (click) {
02309       case MC_DOUBLE_LEFT:
02310       case MC_LEFT:
02311         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
02312         if (!HandleViewportClicked(vp, x, y) &&
02313             !(w->flags4 & WF_DISABLE_VP_SCROLL) &&
02314             _settings_client.gui.left_mouse_btn_scrolling) {
02315           _scrolling_viewport = true;
02316           _cursor.fix_at = false;
02317         }
02318         break;
02319 
02320       case MC_RIGHT:
02321         if (!(w->flags4 & WF_DISABLE_VP_SCROLL)) {
02322           _scrolling_viewport = true;
02323           _cursor.fix_at = true;
02324 
02325           /* clear 2D scrolling caches before we start a 2D scroll */
02326           _cursor.h_wheel = 0;
02327           _cursor.v_wheel = 0;
02328         }
02329         break;
02330 
02331       default:
02332         break;
02333     }
02334   } else {
02335     switch (click) {
02336       case MC_LEFT:
02337       case MC_DOUBLE_LEFT:
02338         DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
02339         break;
02340 
02341       default:
02342         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
02343         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
02344          * Simulate a right button click so we can get started. */
02345         /* FALL THROUGH */
02346 
02347       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
02348 
02349       case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
02350     }
02351   }
02352 }
02353 
02357 void HandleMouseEvents()
02358 {
02359   /* World generation is multithreaded and messes with companies.
02360    * But there is no company related window open anyway, so _current_company is not used. */
02361   assert(HasModalProgress() || IsLocalCompany());
02362 
02363   static int double_click_time = 0;
02364   static Point double_click_pos = {0, 0};
02365 
02366   /* Mouse event? */
02367   MouseClick click = MC_NONE;
02368   if (_left_button_down && !_left_button_clicked) {
02369     click = MC_LEFT;
02370     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
02371         double_click_pos.x != 0 && abs(_cursor.pos.x - double_click_pos.x) < MAX_OFFSET_DOUBLE_CLICK  &&
02372         double_click_pos.y != 0 && abs(_cursor.pos.y - double_click_pos.y) < MAX_OFFSET_DOUBLE_CLICK) {
02373       click = MC_DOUBLE_LEFT;
02374     }
02375     double_click_time = _realtime_tick;
02376     double_click_pos = _cursor.pos;
02377     _left_button_clicked = true;
02378     _input_events_this_tick++;
02379   } else if (_right_button_clicked) {
02380     _right_button_clicked = false;
02381     click = MC_RIGHT;
02382     _input_events_this_tick++;
02383   }
02384 
02385   int mousewheel = 0;
02386   if (_cursor.wheel) {
02387     mousewheel = _cursor.wheel;
02388     _cursor.wheel = 0;
02389     _input_events_this_tick++;
02390   }
02391 
02392   static uint32 hover_time = 0;
02393   static Point hover_pos = {0, 0};
02394 
02395   if (_settings_client.gui.hover_delay > 0) {
02396     if (!_cursor.in_window || click != MC_NONE || mousewheel != 0 || _left_button_down || _right_button_down ||
02397         hover_pos.x == 0 || abs(_cursor.pos.x - hover_pos.x) >= MAX_OFFSET_HOVER  ||
02398         hover_pos.y == 0 || abs(_cursor.pos.y - hover_pos.y) >= MAX_OFFSET_HOVER) {
02399       hover_pos = _cursor.pos;
02400       hover_time = _realtime_tick;
02401       _mouse_hovering = false;
02402     } else {
02403       if (hover_time != 0 && _realtime_tick > hover_time + _settings_client.gui.hover_delay * 1000) {
02404         click = MC_HOVER;
02405         _input_events_this_tick++;
02406         _mouse_hovering = true;
02407       }
02408     }
02409   }
02410 
02411   /* Handle sprite picker before any GUI interaction */
02412   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && _newgrf_debug_sprite_picker.click_time != _realtime_tick) {
02413     /* Next realtime tick? Then redraw has finished */
02414     _newgrf_debug_sprite_picker.mode = SPM_NONE;
02415     InvalidateWindowData(WC_SPRITE_ALIGNER, 0, 1);
02416   }
02417 
02418   if (click == MC_LEFT && _newgrf_debug_sprite_picker.mode == SPM_WAIT_CLICK) {
02419     /* Mark whole screen dirty, and wait for the next realtime tick, when drawing is finished. */
02420     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
02421     _newgrf_debug_sprite_picker.clicked_pixel = blitter->MoveTo(_screen.dst_ptr, _cursor.pos.x, _cursor.pos.y);
02422     _newgrf_debug_sprite_picker.click_time = _realtime_tick;
02423     _newgrf_debug_sprite_picker.sprites.Clear();
02424     _newgrf_debug_sprite_picker.mode = SPM_REDRAW;
02425     MarkWholeScreenDirty();
02426   } else {
02427     MouseLoop(click, mousewheel);
02428   }
02429 
02430   /* We have moved the mouse the required distance,
02431    * no need to move it at any later time. */
02432   _cursor.delta.x = 0;
02433   _cursor.delta.y = 0;
02434 }
02435 
02439 static void CheckSoftLimit()
02440 {
02441   if (_settings_client.gui.window_soft_limit == 0) return;
02442 
02443   for (;;) {
02444     uint deletable_count = 0;
02445     Window *w, *last_deletable = NULL;
02446     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02447       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags4 & WF_STICKY)) continue;
02448 
02449       last_deletable = w;
02450       deletable_count++;
02451     }
02452 
02453     /* We've ot reached the soft limit yet */
02454     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
02455 
02456     assert(last_deletable != NULL);
02457     delete last_deletable;
02458   }
02459 }
02460 
02464 void InputLoop()
02465 {
02466   /* World generation is multithreaded and messes with companies.
02467    * But there is no company related window open anyway, so _current_company is not used. */
02468   assert(HasModalProgress() || IsLocalCompany());
02469 
02470   CheckSoftLimit();
02471   HandleKeyScrolling();
02472 
02473   /* Do the actual free of the deleted windows. */
02474   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
02475     Window *w = v;
02476     v = v->z_back;
02477 
02478     if (w->window_class != WC_INVALID) continue;
02479 
02480     RemoveWindowFromZOrdering(w);
02481     free(w);
02482   }
02483 
02484   if (_scroller_click_timeout != 0) _scroller_click_timeout--;
02485   DecreaseWindowCounters();
02486 
02487   if (_input_events_this_tick != 0) {
02488     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
02489     _input_events_this_tick = 0;
02490     /* there were some inputs this tick, don't scroll ??? */
02491     return;
02492   }
02493 
02494   /* HandleMouseEvents was already called for this tick */
02495   HandleMouseEvents();
02496   HandleAutoscroll();
02497 }
02498 
02502 void UpdateWindows()
02503 {
02504   Window *w;
02505 
02506   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02507     w->ProcessScheduledInvalidations();
02508   }
02509 
02510   static int we4_timer = 0;
02511   int t = we4_timer + 1;
02512 
02513   if (t >= 100) {
02514     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02515       w->OnHundredthTick();
02516     }
02517     t = 0;
02518   }
02519   we4_timer = t;
02520 
02521   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02522     if (w->flags4 & WF_WHITE_BORDER_MASK) {
02523       w->flags4 -= WF_WHITE_BORDER_ONE;
02524 
02525       if (!(w->flags4 & WF_WHITE_BORDER_MASK)) w->SetDirty();
02526     }
02527   }
02528 
02529   DrawDirtyBlocks();
02530 
02531   FOR_ALL_WINDOWS_FROM_BACK(w) {
02532     /* Update viewport only if window is not shaded. */
02533     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
02534   }
02535   NetworkDrawChatMessage();
02536   /* Redraw mouse cursor in case it was hidden */
02537   DrawMouseCursor();
02538 }
02539 
02545 void SetWindowDirty(WindowClass cls, WindowNumber number)
02546 {
02547   const Window *w;
02548   FOR_ALL_WINDOWS_FROM_BACK(w) {
02549     if (w->window_class == cls && w->window_number == number) w->SetDirty();
02550   }
02551 }
02552 
02559 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
02560 {
02561   const Window *w;
02562   FOR_ALL_WINDOWS_FROM_BACK(w) {
02563     if (w->window_class == cls && w->window_number == number) {
02564       w->SetWidgetDirty(widget_index);
02565     }
02566   }
02567 }
02568 
02573 void SetWindowClassesDirty(WindowClass cls)
02574 {
02575   Window *w;
02576   FOR_ALL_WINDOWS_FROM_BACK(w) {
02577     if (w->window_class == cls) w->SetDirty();
02578   }
02579 }
02580 
02607 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
02608 {
02609   Window *w;
02610   FOR_ALL_WINDOWS_FROM_BACK(w) {
02611     if (w->window_class == cls && w->window_number == number) {
02612       w->InvalidateData(data, gui_scope);
02613     }
02614   }
02615 }
02616 
02625 void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
02626 {
02627   Window *w;
02628 
02629   FOR_ALL_WINDOWS_FROM_BACK(w) {
02630     if (w->window_class == cls) {
02631       w->InvalidateData(data, gui_scope);
02632     }
02633   }
02634 }
02635 
02639 void CallWindowTickEvent()
02640 {
02641   Window *w;
02642   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02643     w->OnTick();
02644   }
02645 }
02646 
02653 void DeleteNonVitalWindows()
02654 {
02655   Window *w;
02656 
02657 restart_search:
02658   /* When we find the window to delete, we need to restart the search
02659    * as deleting this window could cascade in deleting (many) others
02660    * anywhere in the z-array */
02661   FOR_ALL_WINDOWS_FROM_BACK(w) {
02662     if (w->window_class != WC_MAIN_WINDOW &&
02663         w->window_class != WC_SELECT_GAME &&
02664         w->window_class != WC_MAIN_TOOLBAR &&
02665         w->window_class != WC_STATUS_BAR &&
02666         w->window_class != WC_TOOLTIPS &&
02667         (w->flags4 & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
02668 
02669       delete w;
02670       goto restart_search;
02671     }
02672   }
02673 }
02674 
02682 void DeleteAllNonVitalWindows()
02683 {
02684   Window *w;
02685 
02686   /* Delete every window except for stickied ones, then sticky ones as well */
02687   DeleteNonVitalWindows();
02688 
02689 restart_search:
02690   /* When we find the window to delete, we need to restart the search
02691    * as deleting this window could cascade in deleting (many) others
02692    * anywhere in the z-array */
02693   FOR_ALL_WINDOWS_FROM_BACK(w) {
02694     if (w->flags4 & WF_STICKY) {
02695       delete w;
02696       goto restart_search;
02697     }
02698   }
02699 }
02700 
02705 void DeleteConstructionWindows()
02706 {
02707   Window *w;
02708 
02709 restart_search:
02710   /* When we find the window to delete, we need to restart the search
02711    * as deleting this window could cascade in deleting (many) others
02712    * anywhere in the z-array */
02713   FOR_ALL_WINDOWS_FROM_BACK(w) {
02714     if (w->desc_flags & WDF_CONSTRUCTION) {
02715       delete w;
02716       goto restart_search;
02717     }
02718   }
02719 
02720   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
02721 }
02722 
02724 void HideVitalWindows()
02725 {
02726   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
02727   DeleteWindowById(WC_STATUS_BAR, 0);
02728 }
02729 
02731 void ReInitAllWindows()
02732 {
02733   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
02734   NWidgetScrollbar::InvalidateDimensionCache();
02735 
02736   Window *w;
02737   FOR_ALL_WINDOWS_FROM_BACK(w) {
02738     w->ReInit();
02739   }
02740 #ifdef ENABLE_NETWORK
02741   void NetworkReInitChatBoxSize();
02742   NetworkReInitChatBoxSize();
02743 #endif
02744 
02745   /* Make sure essential parts of all windows are visible */
02746   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
02747   MarkWholeScreenDirty();
02748 }
02749 
02757 static int PositionWindow(Window *w, WindowClass clss, int setting)
02758 {
02759   if (w == NULL || w->window_class != clss) {
02760     w = FindWindowById(clss, 0);
02761   }
02762   if (w == NULL) return 0;
02763 
02764   int old_left = w->left;
02765   switch (setting) {
02766     case 1:  w->left = (_screen.width - w->width) / 2; break;
02767     case 2:  w->left = _screen.width - w->width; break;
02768     default: w->left = 0; break;
02769   }
02770   if (w->viewport != NULL) w->viewport->left += w->left - old_left;
02771   SetDirtyBlocks(0, w->top, _screen.width, w->top + w->height); // invalidate the whole row
02772   return w->left;
02773 }
02774 
02780 int PositionMainToolbar(Window *w)
02781 {
02782   DEBUG(misc, 5, "Repositioning Main Toolbar...");
02783   return PositionWindow(w, WC_MAIN_TOOLBAR, _settings_client.gui.toolbar_pos);
02784 }
02785 
02791 int PositionStatusbar(Window *w)
02792 {
02793   DEBUG(misc, 5, "Repositioning statusbar...");
02794   return PositionWindow(w, WC_STATUS_BAR, _settings_client.gui.statusbar_pos);
02795 }
02796 
02802 int PositionNewsMessage(Window *w)
02803 {
02804   DEBUG(misc, 5, "Repositioning news message...");
02805   return PositionWindow(w, WC_NEWS_WINDOW, _settings_client.gui.statusbar_pos);
02806 }
02807 
02813 int PositionNetworkChatWindow(Window *w)
02814 {
02815   DEBUG(misc, 5, "Repositioning network chat window...");
02816   return PositionWindow(w, WC_SEND_NETWORK_MSG, _settings_client.gui.statusbar_pos);
02817 }
02818 
02819 
02825 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
02826 {
02827   Window *w;
02828   FOR_ALL_WINDOWS_FROM_BACK(w) {
02829     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
02830       w->viewport->follow_vehicle = to_index;
02831       w->SetDirty();
02832     }
02833   }
02834 }
02835 
02836 
02842 void RelocateAllWindows(int neww, int newh)
02843 {
02844   Window *w;
02845 
02846   FOR_ALL_WINDOWS_FROM_BACK(w) {
02847     int left, top;
02848 
02849     if (w->window_class == WC_MAIN_WINDOW) {
02850       ViewPort *vp = w->viewport;
02851       vp->width = w->width = neww;
02852       vp->height = w->height = newh;
02853       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
02854       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
02855       continue; // don't modify top,left
02856     }
02857 
02858     /* XXX - this probably needs something more sane. For example specifying
02859      * in a 'backup'-desc that the window should always be centered. */
02860     switch (w->window_class) {
02861       case WC_BOOTSTRAP:
02862         ResizeWindow(w, neww, newh);
02863         continue;
02864 
02865       case WC_MAIN_TOOLBAR:
02866         ResizeWindow(w, min(neww, *_preferred_toolbar_size) - w->width, 0);
02867 
02868         top = w->top;
02869         left = PositionMainToolbar(w); // changes toolbar orientation
02870         break;
02871 
02872       case WC_NEWS_WINDOW:
02873         top = newh - w->height;
02874         left = PositionNewsMessage(w);
02875         break;
02876 
02877       case WC_STATUS_BAR:
02878         ResizeWindow(w, min(neww, *_preferred_statusbar_size) - w->width, 0);
02879 
02880         top = newh - w->height;
02881         left = PositionStatusbar(w);
02882         break;
02883 
02884       case WC_SEND_NETWORK_MSG:
02885         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0);
02886         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
02887         left = PositionNetworkChatWindow(w);
02888         break;
02889 
02890       case WC_CONSOLE:
02891         IConsoleResize(w);
02892         continue;
02893 
02894       default: {
02895         if (w->flags4 & WF_CENTERED) {
02896           top = (newh - w->height) >> 1;
02897           left = (neww - w->width) >> 1;
02898           break;
02899         }
02900 
02901         left = w->left;
02902         if (left + (w->width >> 1) >= neww) left = neww - w->width;
02903         if (left < 0) left = 0;
02904 
02905         top = w->top;
02906         if (top + (w->height >> 1) >= newh) top = newh - w->height;
02907         break;
02908       }
02909     }
02910 
02911     EnsureVisibleCaption(w, left, top);
02912   }
02913 }
02914 
02920 PickerWindowBase::~PickerWindowBase()
02921 {
02922   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
02923   ResetObjectToPlace();
02924 }