window.cpp

Go to the documentation of this file.
00001 /* $Id$ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include <stdarg.h>
00014 #include "company_func.h"
00015 #include "gfx_func.h"
00016 #include "console_func.h"
00017 #include "console_gui.h"
00018 #include "viewport_func.h"
00019 #include "progress.h"
00020 #include "blitter/factory.hpp"
00021 #include "zoom_func.h"
00022 #include "vehicle_base.h"
00023 #include "window_func.h"
00024 #include "tilehighlight_func.h"
00025 #include "network/network.h"
00026 #include "querystring_gui.h"
00027 #include "widgets/dropdown_func.h"
00028 #include "strings_func.h"
00029 #include "settings_type.h"
00030 #include "newgrf_debug.h"
00031 #include "hotkeys.h"
00032 #include "toolbar_gui.h"
00033 #include "statusbar_gui.h"
00034 #include "error.h"
00035 #include "game/game.hpp"
00036 
00038 enum ViewportAutoscrolling {
00039   VA_DISABLED,                  
00040   VA_MAIN_VIEWPORT_FULLSCREEN,  
00041   VA_MAIN_VIEWPORT,             
00042   VA_EVERY_VIEWPORT,            
00043 };
00044 
00045 static Point _drag_delta; 
00046 static Window *_mouseover_last_w = NULL; 
00047 static Window *_last_scroll_window = NULL; 
00048 
00050 Window *_z_front_window = NULL;
00052 Window *_z_back_window  = NULL;
00053 
00055 bool _window_highlight_colour = false;
00056 
00057 /*
00058  * Window that currently has focus. - The main purpose is to generate
00059  * #FocusLost events, not to give next window in z-order focus when a
00060  * window is closed.
00061  */
00062 Window *_focused_window;
00063 
00064 Point _cursorpos_drag_start;
00065 
00066 int _scrollbar_start_pos;
00067 int _scrollbar_size;
00068 byte _scroller_click_timeout = 0;
00069 
00070 bool _scrolling_viewport;  
00071 bool _mouse_hovering;      
00072 
00073 SpecialMouseMode _special_mouse_mode; 
00074 
00076 WindowDesc::WindowDesc(WindowPosition def_pos, int16 def_width, int16 def_height,
00077       WindowClass window_class, WindowClass parent_class, uint32 flags,
00078       const NWidgetPart *nwid_parts, int16 nwid_length) :
00079   default_pos(def_pos),
00080   default_width(def_width),
00081   default_height(def_height),
00082   cls(window_class),
00083   parent_cls(parent_class),
00084   flags(flags),
00085   nwid_parts(nwid_parts),
00086   nwid_length(nwid_length)
00087 {
00088 }
00089 
00090 WindowDesc::~WindowDesc()
00091 {
00092 }
00093 
00103 int Window::GetRowFromWidget(int clickpos, int widget, int padding, int line_height) const
00104 {
00105   const NWidgetBase *wid = this->GetWidget<NWidgetBase>(widget);
00106   if (line_height < 0) line_height = wid->resize_y;
00107   if (clickpos < (int)wid->pos_y + padding) return INT_MAX;
00108   return (clickpos - (int)wid->pos_y - padding) / line_height;
00109 }
00110 
00114 void Window::DisableAllWidgetHighlight()
00115 {
00116   for (uint i = 0; i < this->nested_array_size; i++) {
00117     NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
00118     if (nwid == NULL) continue;
00119 
00120     if (nwid->IsHighlighted()) {
00121       nwid->SetHighlighted(TC_INVALID);
00122       this->SetWidgetDirty(i);
00123     }
00124   }
00125 
00126   CLRBITS(this->flags, WF_HIGHLIGHTED);
00127 }
00128 
00134 void Window::SetWidgetHighlight(byte widget_index, TextColour highlighted_colour)
00135 {
00136   assert(widget_index < this->nested_array_size);
00137 
00138   NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
00139   if (nwid == NULL) return;
00140 
00141   nwid->SetHighlighted(highlighted_colour);
00142   this->SetWidgetDirty(widget_index);
00143 
00144   if (highlighted_colour != TC_INVALID) {
00145     /* If we set a highlight, the window has a highlight */
00146     this->flags |= WF_HIGHLIGHTED;
00147   } else {
00148     /* If we disable a highlight, check all widgets if anyone still has a highlight */
00149     bool valid = false;
00150     for (uint i = 0; i < this->nested_array_size; i++) {
00151       NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
00152       if (nwid == NULL) continue;
00153       if (!nwid->IsHighlighted()) continue;
00154 
00155       valid = true;
00156     }
00157     /* If nobody has a highlight, disable the flag on the window */
00158     if (!valid) CLRBITS(this->flags, WF_HIGHLIGHTED);
00159   }
00160 }
00161 
00167 bool Window::IsWidgetHighlighted(byte widget_index) const
00168 {
00169   assert(widget_index < this->nested_array_size);
00170 
00171   const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
00172   if (nwid == NULL) return false;
00173 
00174   return nwid->IsHighlighted();
00175 }
00176 
00184 void Window::OnDropdownClose(Point pt, int widget, int index, bool instant_close)
00185 {
00186   if (widget < 0) return;
00187 
00188   if (instant_close) {
00189     /* Send event for selected option if we're still
00190      * on the parent button of the dropdown (behaviour of the dropdowns in the main toolbar). */
00191     if (GetWidgetFromPos(this, pt.x, pt.y) == widget) {
00192       this->OnDropdownSelect(widget, index);
00193     }
00194   }
00195 
00196   /* Raise the dropdown button */
00197   if (this->nested_array != NULL) {
00198     NWidgetCore *nwi2 = this->GetWidget<NWidgetCore>(widget);
00199     if ((nwi2->type & WWT_MASK) == NWID_BUTTON_DROPDOWN) {
00200       nwi2->disp_flags &= ~ND_DROPDOWN_ACTIVE;
00201     } else {
00202       this->RaiseWidget(widget);
00203     }
00204   } else {
00205     this->RaiseWidget(widget);
00206   }
00207   this->SetWidgetDirty(widget);
00208 }
00209 
00215 const Scrollbar *Window::GetScrollbar(uint widnum) const
00216 {
00217   return this->GetWidget<NWidgetScrollbar>(widnum);
00218 }
00219 
00225 Scrollbar *Window::GetScrollbar(uint widnum)
00226 {
00227   return this->GetWidget<NWidgetScrollbar>(widnum);
00228 }
00229 
00235 const QueryString *Window::GetQueryString(uint widnum) const
00236 {
00237   const SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
00238   return query != this->querystrings.End() ? query->second : NULL;
00239 }
00240 
00246 QueryString *Window::GetQueryString(uint widnum)
00247 {
00248   SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
00249   return query != this->querystrings.End() ? query->second : NULL;
00250 }
00251 
00252 
00257 void SetFocusedWindow(Window *w)
00258 {
00259   if (_focused_window == w) return;
00260 
00261   /* Invalidate focused widget */
00262   if (_focused_window != NULL) {
00263     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
00264   }
00265 
00266   /* Remember which window was previously focused */
00267   Window *old_focused = _focused_window;
00268   _focused_window = w;
00269 
00270   /* So we can inform it that it lost focus */
00271   if (old_focused != NULL) old_focused->OnFocusLost();
00272   if (_focused_window != NULL) _focused_window->OnFocus();
00273 }
00274 
00280 static bool EditBoxInGlobalFocus()
00281 {
00282   if (_focused_window == NULL) return false;
00283 
00284   /* The console does not have an edit box so a special case is needed. */
00285   if (_focused_window->window_class == WC_CONSOLE) return true;
00286 
00287   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
00288 }
00289 
00293 void Window::UnfocusFocusedWidget()
00294 {
00295   if (this->nested_focus != NULL) {
00296     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00297     this->nested_focus->SetDirty(this);
00298     this->nested_focus = NULL;
00299   }
00300 }
00301 
00307 bool Window::SetFocusedWidget(byte widget_index)
00308 {
00309   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
00310   if (widget_index >= this->nested_array_size) return false;
00311 
00312   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
00313   if (this->nested_focus != NULL) {
00314     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
00315 
00316     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00317     this->nested_focus->SetDirty(this);
00318   }
00319   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
00320   return true;
00321 }
00322 
00330 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
00331 {
00332   va_list wdg_list;
00333 
00334   va_start(wdg_list, widgets);
00335 
00336   while (widgets != WIDGET_LIST_END) {
00337     SetWidgetDisabledState(widgets, disab_stat);
00338     widgets = va_arg(wdg_list, int);
00339   }
00340 
00341   va_end(wdg_list);
00342 }
00343 
00349 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
00350 {
00351   va_list wdg_list;
00352 
00353   va_start(wdg_list, widgets);
00354 
00355   while (widgets != WIDGET_LIST_END) {
00356     SetWidgetLoweredState(widgets, lowered_stat);
00357     widgets = va_arg(wdg_list, int);
00358   }
00359 
00360   va_end(wdg_list);
00361 }
00362 
00367 void Window::RaiseButtons(bool autoraise)
00368 {
00369   for (uint i = 0; i < this->nested_array_size; i++) {
00370     if (this->nested_array[i] == NULL) continue;
00371     WidgetType type = this->nested_array[i]->type;
00372     if (((type & ~WWB_PUSHBUTTON) < WWT_LAST || type == NWID_PUSHBUTTON_DROPDOWN) &&
00373         (!autoraise || (type & WWB_PUSHBUTTON) || type == WWT_EDITBOX) && this->IsWidgetLowered(i)) {
00374       this->RaiseWidget(i);
00375       this->SetWidgetDirty(i);
00376     }
00377   }
00378 }
00379 
00384 void Window::SetWidgetDirty(byte widget_index) const
00385 {
00386   /* Sometimes this function is called before the window is even fully initialized */
00387   if (this->nested_array == NULL) return;
00388 
00389   this->nested_array[widget_index]->SetDirty(this);
00390 }
00391 
00397 void Window::HandleButtonClick(byte widget)
00398 {
00399   this->LowerWidget(widget);
00400   this->SetTimeout();
00401   this->SetWidgetDirty(widget);
00402 }
00403 
00404 static void StartWindowDrag(Window *w);
00405 static void StartWindowSizing(Window *w, bool to_left);
00406 
00414 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
00415 {
00416   NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
00417   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
00418 
00419   bool focused_widget_changed = false;
00420   /* If clicked on a window that previously did dot have focus */
00421   if (_focused_window != w &&                 // We already have focus, right?
00422       (w->desc_flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
00423       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
00424     focused_widget_changed = true;
00425     if (_focused_window != NULL) {
00426       _focused_window->OnFocusLost();
00427 
00428       /* The window that lost focus may have had opened a OSK, window so close it, unless the user has clicked on the OSK window. */
00429       if (w->window_class != WC_OSK) DeleteWindowById(WC_OSK, 0);
00430     }
00431     SetFocusedWindow(w);
00432     w->OnFocus();
00433   }
00434 
00435   if (nw == NULL) return; // exit if clicked outside of widgets
00436 
00437   /* don't allow any interaction if the button has been disabled */
00438   if (nw->IsDisabled()) return;
00439 
00440   int widget_index = nw->index; 
00441 
00442   /* Clicked on a widget that is not disabled.
00443    * So unless the clicked widget is the caption bar, change focus to this widget */
00444   if (widget_type != WWT_CAPTION) {
00445     /* Close the OSK window if a edit box loses focus */
00446     if (w->nested_focus != NULL &&  w->nested_focus->type == WWT_EDITBOX && w->nested_focus != nw && w->window_class != WC_OSK) {
00447       DeleteWindowById(WC_OSK, 0);
00448     }
00449 
00450     /* focused_widget_changed is 'now' only true if the window this widget
00451      * is in gained focus. In that case it must remain true, also if the
00452      * local widget focus did not change. As such it's the logical-or of
00453      * both changed states.
00454      *
00455      * If this is not preserved, then the OSK window would be opened when
00456      * a user has the edit box focused and then click on another window and
00457      * then back again on the edit box (to type some text).
00458      */
00459     focused_widget_changed |= w->SetFocusedWidget(widget_index);
00460   }
00461 
00462   /* Close any child drop down menus. If the button pressed was the drop down
00463    * list's own button, then we should not process the click any further. */
00464   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
00465 
00466   if ((widget_type & ~WWB_PUSHBUTTON) < WWT_LAST && (widget_type & WWB_PUSHBUTTON)) w->HandleButtonClick(widget_index);
00467 
00468   Point pt = { x, y };
00469 
00470   switch (widget_type) {
00471     case NWID_VSCROLLBAR:
00472     case NWID_HSCROLLBAR:
00473       ScrollbarClickHandler(w, nw, x, y);
00474       break;
00475 
00476     case WWT_EDITBOX: {
00477       QueryString *query = w->GetQueryString(widget_index);
00478       if (query != NULL) query->ClickEditBox(w, pt, widget_index, click_count, focused_widget_changed);
00479       break;
00480     }
00481 
00482     case WWT_CLOSEBOX: // 'X'
00483       delete w;
00484       return;
00485 
00486     case WWT_CAPTION: // 'Title bar'
00487       StartWindowDrag(w);
00488       return;
00489 
00490     case WWT_RESIZEBOX:
00491       /* When the resize widget is on the left size of the window
00492        * we assume that that button is used to resize to the left. */
00493       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
00494       nw->SetDirty(w);
00495       return;
00496 
00497     case WWT_DEBUGBOX:
00498       w->ShowNewGRFInspectWindow();
00499       break;
00500 
00501     case WWT_SHADEBOX:
00502       nw->SetDirty(w);
00503       w->SetShaded(!w->IsShaded());
00504       return;
00505 
00506     case WWT_STICKYBOX:
00507       w->flags ^= WF_STICKY;
00508       nw->SetDirty(w);
00509       return;
00510 
00511     default:
00512       break;
00513   }
00514 
00515   /* Widget has no index, so the window is not interested in it. */
00516   if (widget_index < 0) return;
00517 
00518   /* Check if the widget is highlighted; if so, disable highlight and dispatch an event to the GameScript */
00519   if (w->IsWidgetHighlighted(widget_index)) {
00520     w->SetWidgetHighlight(widget_index, TC_INVALID);
00521     Game::NewEvent(new ScriptEventWindowWidgetClick((ScriptWindow::WindowClass)w->window_class, w->window_number, widget_index));
00522   }
00523 
00524   w->OnClick(pt, widget_index, click_count);
00525 }
00526 
00533 static void DispatchRightClickEvent(Window *w, int x, int y)
00534 {
00535   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00536   if (wid == NULL) return;
00537 
00538   /* No widget to handle, or the window is not interested in it. */
00539   if (wid->index >= 0) {
00540     Point pt = { x, y };
00541     if (w->OnRightClick(pt, wid->index)) return;
00542   }
00543 
00544   if (_settings_client.gui.hover_delay == 0 && wid->tool_tip != 0) GuiShowTooltips(w, wid->tool_tip, 0, NULL, TCC_RIGHT_CLICK);
00545 }
00546 
00553 static void DispatchHoverEvent(Window *w, int x, int y)
00554 {
00555   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00556 
00557   /* No widget to handle */
00558   if (wid == NULL) return;
00559 
00560   /* Show the tooltip if there is any */
00561   if (wid->tool_tip != 0) {
00562     GuiShowTooltips(w, wid->tool_tip);
00563     return;
00564   }
00565 
00566   /* Widget has no index, so the window is not interested in it. */
00567   if (wid->index < 0) return;
00568 
00569   Point pt = { x, y };
00570   w->OnHover(pt, wid->index);
00571 }
00572 
00580 static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel)
00581 {
00582   if (nwid == NULL) return;
00583 
00584   /* Using wheel on caption/shade-box shades or unshades the window. */
00585   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
00586     w->SetShaded(wheel < 0);
00587     return;
00588   }
00589 
00590   /* Wheeling a vertical scrollbar. */
00591   if (nwid->type == NWID_VSCROLLBAR) {
00592     NWidgetScrollbar *sb = static_cast<NWidgetScrollbar *>(nwid);
00593     if (sb->GetCount() > sb->GetCapacity()) {
00594       sb->UpdatePosition(wheel);
00595       w->SetDirty();
00596     }
00597     return;
00598   }
00599 
00600   /* Scroll the widget attached to the scrollbar. */
00601   Scrollbar *sb = (nwid->scrollbar_index >= 0 ? w->GetScrollbar(nwid->scrollbar_index) : NULL);
00602   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
00603     sb->UpdatePosition(wheel);
00604     w->SetDirty();
00605   }
00606 }
00607 
00613 static bool MayBeShown(const Window *w)
00614 {
00615   /* If we're not modal, everything is okay. */
00616   if (!HasModalProgress()) return true;
00617 
00618   switch (w->window_class) {
00619     case WC_MAIN_WINDOW:    
00620     case WC_MODAL_PROGRESS: 
00621     case WC_CONFIRM_POPUP_QUERY: 
00622       return true;
00623 
00624     default:
00625       return false;
00626   }
00627 }
00628 
00641 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
00642 {
00643   const Window *v;
00644   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
00645     if (MayBeShown(v) &&
00646         right > v->left &&
00647         bottom > v->top &&
00648         left < v->left + v->width &&
00649         top < v->top + v->height) {
00650       /* v and rectangle intersect with each other */
00651       int x;
00652 
00653       if (left < (x = v->left)) {
00654         DrawOverlappedWindow(w, left, top, x, bottom);
00655         DrawOverlappedWindow(w, x, top, right, bottom);
00656         return;
00657       }
00658 
00659       if (right > (x = v->left + v->width)) {
00660         DrawOverlappedWindow(w, left, top, x, bottom);
00661         DrawOverlappedWindow(w, x, top, right, bottom);
00662         return;
00663       }
00664 
00665       if (top < (x = v->top)) {
00666         DrawOverlappedWindow(w, left, top, right, x);
00667         DrawOverlappedWindow(w, left, x, right, bottom);
00668         return;
00669       }
00670 
00671       if (bottom > (x = v->top + v->height)) {
00672         DrawOverlappedWindow(w, left, top, right, x);
00673         DrawOverlappedWindow(w, left, x, right, bottom);
00674         return;
00675       }
00676 
00677       return;
00678     }
00679   }
00680 
00681   /* Setup blitter, and dispatch a repaint event to window *wz */
00682   DrawPixelInfo *dp = _cur_dpi;
00683   dp->width = right - left;
00684   dp->height = bottom - top;
00685   dp->left = left - w->left;
00686   dp->top = top - w->top;
00687   dp->pitch = _screen.pitch;
00688   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
00689   dp->zoom = ZOOM_LVL_NORMAL;
00690   w->OnPaint();
00691 }
00692 
00701 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
00702 {
00703   Window *w;
00704   DrawPixelInfo bk;
00705   _cur_dpi = &bk;
00706 
00707   FOR_ALL_WINDOWS_FROM_BACK(w) {
00708     if (MayBeShown(w) &&
00709         right > w->left &&
00710         bottom > w->top &&
00711         left < w->left + w->width &&
00712         top < w->top + w->height) {
00713       /* Window w intersects with the rectangle => needs repaint */
00714       DrawOverlappedWindow(w, left, top, right, bottom);
00715     }
00716   }
00717 }
00718 
00723 void Window::SetDirty() const
00724 {
00725   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
00726 }
00727 
00734 void Window::ReInit(int rx, int ry)
00735 {
00736   this->SetDirty(); // Mark whole current window as dirty.
00737 
00738   /* Save current size. */
00739   int window_width  = this->width;
00740   int window_height = this->height;
00741 
00742   this->OnInit();
00743   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
00744   this->nested_root->SetupSmallestSize(this, false);
00745   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
00746   this->width  = this->nested_root->smallest_x;
00747   this->height = this->nested_root->smallest_y;
00748   this->resize.step_width  = this->nested_root->resize_x;
00749   this->resize.step_height = this->nested_root->resize_y;
00750 
00751   /* Resize as close to the original size + requested resize as possible. */
00752   window_width  = max(window_width  + rx, this->width);
00753   window_height = max(window_height + ry, this->height);
00754   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
00755   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
00756   /* dx and dy has to go by step.. calculate it.
00757    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
00758   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
00759   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
00760 
00761   ResizeWindow(this, dx, dy);
00762   /* ResizeWindow() does this->SetDirty() already, no need to do it again here. */
00763 }
00764 
00770 void Window::SetShaded(bool make_shaded)
00771 {
00772   if (this->shade_select == NULL) return;
00773 
00774   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
00775   if (this->shade_select->shown_plane != desired) {
00776     if (make_shaded) {
00777       this->unshaded_size.width  = this->width;
00778       this->unshaded_size.height = this->height;
00779       this->shade_select->SetDisplayedPlane(desired);
00780       this->ReInit(0, -this->height);
00781     } else {
00782       this->shade_select->SetDisplayedPlane(desired);
00783       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
00784       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
00785       this->ReInit(dx, dy);
00786     }
00787   }
00788 }
00789 
00796 static Window *FindChildWindow(const Window *w, WindowClass wc)
00797 {
00798   Window *v;
00799   FOR_ALL_WINDOWS_FROM_BACK(v) {
00800     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
00801   }
00802 
00803   return NULL;
00804 }
00805 
00810 void Window::DeleteChildWindows(WindowClass wc) const
00811 {
00812   Window *child = FindChildWindow(this, wc);
00813   while (child != NULL) {
00814     delete child;
00815     child = FindChildWindow(this, wc);
00816   }
00817 }
00818 
00822 Window::~Window()
00823 {
00824   if (_thd.window_class == this->window_class &&
00825       _thd.window_number == this->window_number) {
00826     ResetObjectToPlace();
00827   }
00828 
00829   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
00830   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
00831 
00832   /* We can't scroll the window when it's closed. */
00833   if (_last_scroll_window == this) _last_scroll_window = NULL;
00834 
00835   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
00836   if (_focused_window == this) _focused_window = NULL;
00837 
00838   this->DeleteChildWindows();
00839 
00840   if (this->viewport != NULL) DeleteWindowViewport(this);
00841 
00842   this->SetDirty();
00843 
00844   free(this->nested_array); // Contents is released through deletion of #nested_root.
00845   delete this->nested_root;
00846 
00847   this->window_class = WC_INVALID;
00848 }
00849 
00856 Window *FindWindowById(WindowClass cls, WindowNumber number)
00857 {
00858   Window *w;
00859   FOR_ALL_WINDOWS_FROM_BACK(w) {
00860     if (w->window_class == cls && w->window_number == number) return w;
00861   }
00862 
00863   return NULL;
00864 }
00865 
00872 Window *FindWindowByClass(WindowClass cls)
00873 {
00874   Window *w;
00875   FOR_ALL_WINDOWS_FROM_BACK(w) {
00876     if (w->window_class == cls) return w;
00877   }
00878 
00879   return NULL;
00880 }
00881 
00888 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
00889 {
00890   Window *w = FindWindowById(cls, number);
00891   if (force || w == NULL ||
00892       (w->flags & WF_STICKY) == 0) {
00893     delete w;
00894   }
00895 }
00896 
00901 void DeleteWindowByClass(WindowClass cls)
00902 {
00903   Window *w;
00904 
00905 restart_search:
00906   /* When we find the window to delete, we need to restart the search
00907    * as deleting this window could cascade in deleting (many) others
00908    * anywhere in the z-array */
00909   FOR_ALL_WINDOWS_FROM_BACK(w) {
00910     if (w->window_class == cls) {
00911       delete w;
00912       goto restart_search;
00913     }
00914   }
00915 }
00916 
00923 void DeleteCompanyWindows(CompanyID id)
00924 {
00925   Window *w;
00926 
00927 restart_search:
00928   /* When we find the window to delete, we need to restart the search
00929    * as deleting this window could cascade in deleting (many) others
00930    * anywhere in the z-array */
00931   FOR_ALL_WINDOWS_FROM_BACK(w) {
00932     if (w->owner == id) {
00933       delete w;
00934       goto restart_search;
00935     }
00936   }
00937 
00938   /* Also delete the company specific windows that don't have a company-colour. */
00939   DeleteWindowById(WC_BUY_COMPANY, id);
00940 }
00941 
00949 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
00950 {
00951   Window *w;
00952   FOR_ALL_WINDOWS_FROM_BACK(w) {
00953     if (w->owner != old_owner) continue;
00954 
00955     switch (w->window_class) {
00956       case WC_COMPANY_COLOUR:
00957       case WC_FINANCES:
00958       case WC_STATION_LIST:
00959       case WC_TRAINS_LIST:
00960       case WC_ROADVEH_LIST:
00961       case WC_SHIPS_LIST:
00962       case WC_AIRCRAFT_LIST:
00963       case WC_BUY_COMPANY:
00964       case WC_COMPANY:
00965       case WC_COMPANY_INFRASTRUCTURE:
00966         continue;
00967 
00968       default:
00969         w->owner = new_owner;
00970         break;
00971     }
00972   }
00973 }
00974 
00975 static void BringWindowToFront(Window *w);
00976 
00984 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
00985 {
00986   Window *w = FindWindowById(cls, number);
00987 
00988   if (w != NULL) {
00989     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
00990 
00991     w->SetWhiteBorder();
00992     BringWindowToFront(w);
00993     w->SetDirty();
00994   }
00995 
00996   return w;
00997 }
00998 
00999 static inline bool IsVitalWindow(const Window *w)
01000 {
01001   switch (w->window_class) {
01002     case WC_MAIN_TOOLBAR:
01003     case WC_STATUS_BAR:
01004     case WC_NEWS_WINDOW:
01005     case WC_SEND_NETWORK_MSG:
01006       return true;
01007 
01008     default:
01009       return false;
01010   }
01011 }
01012 
01021 static uint GetWindowZPriority(const Window *w)
01022 {
01023   assert(w->window_class != WC_INVALID);
01024 
01025   uint z_priority = 0;
01026 
01027   switch (w->window_class) {
01028     case WC_ENDSCREEN:
01029       ++z_priority;
01030 
01031     case WC_HIGHSCORE:
01032       ++z_priority;
01033 
01034     case WC_TOOLTIPS:
01035       ++z_priority;
01036 
01037     case WC_DROPDOWN_MENU:
01038       ++z_priority;
01039 
01040     case WC_MAIN_TOOLBAR:
01041     case WC_STATUS_BAR:
01042       ++z_priority;
01043 
01044     case WC_OSK:
01045       ++z_priority;
01046 
01047     case WC_QUERY_STRING:
01048     case WC_SEND_NETWORK_MSG:
01049       ++z_priority;
01050 
01051     case WC_ERRMSG:
01052     case WC_CONFIRM_POPUP_QUERY:
01053     case WC_MODAL_PROGRESS:
01054     case WC_NETWORK_STATUS_WINDOW:
01055       ++z_priority;
01056 
01057     case WC_GENERATE_LANDSCAPE:
01058     case WC_SAVELOAD:
01059     case WC_GAME_OPTIONS:
01060     case WC_CUSTOM_CURRENCY:
01061     case WC_NETWORK_WINDOW:
01062     case WC_GRF_PARAMETERS:
01063     case WC_AI_LIST:
01064     case WC_AI_SETTINGS:
01065     case WC_TEXTFILE:
01066       ++z_priority;
01067 
01068     case WC_CONSOLE:
01069       ++z_priority;
01070 
01071     case WC_NEWS_WINDOW:
01072       ++z_priority;
01073 
01074     default:
01075       ++z_priority;
01076 
01077     case WC_MAIN_WINDOW:
01078       return z_priority;
01079   }
01080 }
01081 
01086 static void AddWindowToZOrdering(Window *w)
01087 {
01088   assert(w->z_front == NULL && w->z_back == NULL);
01089 
01090   if (_z_front_window == NULL) {
01091     /* It's the only window. */
01092     _z_front_window = _z_back_window = w;
01093     w->z_front = w->z_back = NULL;
01094   } else {
01095     /* Search down the z-ordering for its location. */
01096     Window *v = _z_front_window;
01097     uint last_z_priority = UINT_MAX;
01098     while (v != NULL && (v->window_class == WC_INVALID || GetWindowZPriority(v) > GetWindowZPriority(w))) {
01099       if (v->window_class != WC_INVALID) {
01100         /* Sanity check z-ordering, while we're at it. */
01101         assert(last_z_priority >= GetWindowZPriority(v));
01102         last_z_priority = GetWindowZPriority(v);
01103       }
01104 
01105       v = v->z_back;
01106     }
01107 
01108     if (v == NULL) {
01109       /* It's the new back window. */
01110       w->z_front = _z_back_window;
01111       w->z_back = NULL;
01112       _z_back_window->z_back = w;
01113       _z_back_window = w;
01114     } else if (v == _z_front_window) {
01115       /* It's the new front window. */
01116       w->z_front = NULL;
01117       w->z_back = _z_front_window;
01118       _z_front_window->z_front = w;
01119       _z_front_window = w;
01120     } else {
01121       /* It's somewhere else in the z-ordering. */
01122       w->z_front = v->z_front;
01123       w->z_back = v;
01124       v->z_front->z_back = w;
01125       v->z_front = w;
01126     }
01127   }
01128 }
01129 
01130 
01135 static void RemoveWindowFromZOrdering(Window *w)
01136 {
01137   if (w->z_front == NULL) {
01138     assert(_z_front_window == w);
01139     _z_front_window = w->z_back;
01140   } else {
01141     w->z_front->z_back = w->z_back;
01142   }
01143 
01144   if (w->z_back == NULL) {
01145     assert(_z_back_window == w);
01146     _z_back_window = w->z_front;
01147   } else {
01148     w->z_back->z_front = w->z_front;
01149   }
01150 
01151   w->z_front = w->z_back = NULL;
01152 }
01153 
01159 static void BringWindowToFront(Window *w)
01160 {
01161   RemoveWindowFromZOrdering(w);
01162   AddWindowToZOrdering(w);
01163 
01164   w->SetDirty();
01165 }
01166 
01175 void Window::InitializeData(const WindowDesc *desc, WindowNumber window_number)
01176 {
01177   /* Set up window properties; some of them are needed to set up smallest size below */
01178   this->window_class = desc->cls;
01179   this->SetWhiteBorder();
01180   if (desc->default_pos == WDP_CENTER) this->flags |= WF_CENTERED;
01181   this->owner = INVALID_OWNER;
01182   this->nested_focus = NULL;
01183   this->window_number = window_number;
01184   this->desc_flags = desc->flags;
01185 
01186   this->OnInit();
01187   /* Initialize nested widget tree. */
01188   if (this->nested_array == NULL) {
01189     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01190     this->nested_root->SetupSmallestSize(this, true);
01191   } else {
01192     this->nested_root->SetupSmallestSize(this, false);
01193   }
01194   /* Initialize to smallest size. */
01195   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
01196 
01197   /* Further set up window properties,
01198    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
01199   this->resize.step_width  = this->nested_root->resize_x;
01200   this->resize.step_height = this->nested_root->resize_y;
01201 
01202   /* Give focus to the opened window unless it is the OSK window or a text box
01203    * of focused window has focus (so we don't interrupt typing). But if the new
01204    * window has a text box, then take focus anyway. */
01205   if (this->window_class != WC_OSK && (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL)) SetFocusedWindow(this);
01206 
01207   /* Insert the window into the correct location in the z-ordering. */
01208   AddWindowToZOrdering(this);
01209 }
01210 
01218 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
01219 {
01220   this->left = x;
01221   this->top = y;
01222   this->width = sm_width;
01223   this->height = sm_height;
01224 }
01225 
01236 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
01237 {
01238   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
01239   def_height = max(def_height, this->height);
01240   /* Try to make windows smaller when our window is too small.
01241    * w->(width|height) is normally the same as min_(width|height),
01242    * but this way the GUIs can be made a little more dynamic;
01243    * one can use the same spec for multiple windows and those
01244    * can then determine the real minimum size of the window. */
01245   if (this->width != def_width || this->height != def_height) {
01246     /* Think about the overlapping toolbars when determining the minimum window size */
01247     int free_height = _screen.height;
01248     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
01249     if (wt != NULL) free_height -= wt->height;
01250     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01251     if (wt != NULL) free_height -= wt->height;
01252 
01253     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
01254     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
01255 
01256     /* X and Y has to go by step.. calculate it.
01257      * The cast to int is necessary else x/y are implicitly casted to
01258      * unsigned int, which won't work. */
01259     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
01260     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
01261 
01262     ResizeWindow(this, enlarge_x, enlarge_y);
01263     /* ResizeWindow() calls this->OnResize(). */
01264   } else {
01265     /* Always call OnResize; that way the scrollbars and matrices get initialized. */
01266     this->OnResize();
01267   }
01268 
01269   int nx = this->left;
01270   int ny = this->top;
01271 
01272   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
01273 
01274   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01275   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
01276   nx = max(nx, 0);
01277 
01278   if (this->viewport != NULL) {
01279     this->viewport->left += nx - this->left;
01280     this->viewport->top  += ny - this->top;
01281   }
01282   this->left = nx;
01283   this->top = ny;
01284 
01285   this->SetDirty();
01286 }
01287 
01299 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
01300 {
01301   int right  = width + left;
01302   int bottom = height + top;
01303 
01304   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01305   if (left < 0 || (main_toolbar != NULL && top < main_toolbar->height) || right > _screen.width || bottom > _screen.height) return false;
01306 
01307   /* Make sure it is not obscured by any window. */
01308   const Window *w;
01309   FOR_ALL_WINDOWS_FROM_BACK(w) {
01310     if (w->window_class == WC_MAIN_WINDOW) continue;
01311 
01312     if (right > w->left &&
01313         w->left + w->width > left &&
01314         bottom > w->top &&
01315         w->top + w->height > top) {
01316       return false;
01317     }
01318   }
01319 
01320   pos.x = left;
01321   pos.y = top;
01322   return true;
01323 }
01324 
01336 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
01337 {
01338   /* Left part of the rectangle may be at most 1/4 off-screen,
01339    * right part of the rectangle may be at most 1/2 off-screen
01340    */
01341   if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
01342   /* Bottom part of the rectangle may be at most 1/4 off-screen */
01343   if (top < 22 || top > _screen.height - (height >> 2)) return false;
01344 
01345   /* Make sure it is not obscured by any window. */
01346   const Window *w;
01347   FOR_ALL_WINDOWS_FROM_BACK(w) {
01348     if (w->window_class == WC_MAIN_WINDOW) continue;
01349 
01350     if (left + width > w->left &&
01351         w->left + w->width > left &&
01352         top + height > w->top &&
01353         w->top + w->height > top) {
01354       return false;
01355     }
01356   }
01357 
01358   pos.x = left;
01359   pos.y = top;
01360   return true;
01361 }
01362 
01369 static Point GetAutoPlacePosition(int width, int height)
01370 {
01371   Point pt;
01372 
01373   /* First attempt, try top-left of the screen */
01374   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01375   if (IsGoodAutoPlace1(0, main_toolbar != NULL ? main_toolbar->height + 2 : 2, width, height, pt)) return pt;
01376 
01377   /* Second attempt, try around all existing windows with a distance of 2 pixels.
01378    * The new window must be entirely on-screen, and not overlap with an existing window.
01379    * Eight starting points are tried, two at each corner.
01380    */
01381   const Window *w;
01382   FOR_ALL_WINDOWS_FROM_BACK(w) {
01383     if (w->window_class == WC_MAIN_WINDOW) continue;
01384 
01385     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01386     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
01387     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01388     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
01389     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
01390     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
01391     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
01392     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
01393   }
01394 
01395   /* Third attempt, try around all existing windows with a distance of 2 pixels.
01396    * The new window may be partly off-screen, and must not overlap with an existing window.
01397    * Only four starting points are tried.
01398    */
01399   FOR_ALL_WINDOWS_FROM_BACK(w) {
01400     if (w->window_class == WC_MAIN_WINDOW) continue;
01401 
01402     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01403     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
01404     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01405     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
01406   }
01407 
01408   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
01409    * of (+5, +5)
01410    */
01411   int left = 0, top = 24;
01412 
01413 restart:
01414   FOR_ALL_WINDOWS_FROM_BACK(w) {
01415     if (w->left == left && w->top == top) {
01416       left += 5;
01417       top += 5;
01418       goto restart;
01419     }
01420   }
01421 
01422   pt.x = left;
01423   pt.y = top;
01424   return pt;
01425 }
01426 
01433 Point GetToolbarAlignedWindowPosition(int window_width)
01434 {
01435   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01436   assert(w != NULL);
01437   Point pt = { _current_text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
01438   return pt;
01439 }
01440 
01458 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01459 {
01460   Point pt;
01461   const Window *w;
01462 
01463   int16 default_width  = max(desc->default_width,  sm_width);
01464   int16 default_height = max(desc->default_height, sm_height);
01465 
01466   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
01467       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
01468       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
01469 
01470     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
01471     if (pt.x > _screen.width + 10 - default_width) {
01472       pt.x = (_screen.width + 10 - default_width) - 20;
01473     }
01474     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
01475     return pt;
01476   }
01477 
01478   switch (desc->default_pos) {
01479     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
01480       return GetToolbarAlignedWindowPosition(default_width);
01481 
01482     case WDP_AUTO: // Find a good automatic position for the window
01483       return GetAutoPlacePosition(default_width, default_height);
01484 
01485     case WDP_CENTER: // Centre the window horizontally
01486       pt.x = (_screen.width - default_width) / 2;
01487       pt.y = (_screen.height - default_height) / 2;
01488       break;
01489 
01490     case WDP_MANUAL:
01491       pt.x = 0;
01492       pt.y = 0;
01493       break;
01494 
01495     default:
01496       NOT_REACHED();
01497   }
01498 
01499   return pt;
01500 }
01501 
01502 /* virtual */ Point Window::OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01503 {
01504   return LocalGetWindowPlacement(desc, sm_width, sm_height, window_number);
01505 }
01506 
01515 void Window::CreateNestedTree(const WindowDesc *desc, bool fill_nested)
01516 {
01517   int biggest_index = -1;
01518   this->nested_root = MakeWindowNWidgetTree(desc->nwid_parts, desc->nwid_length, &biggest_index, &this->shade_select);
01519   this->nested_array_size = (uint)(biggest_index + 1);
01520 
01521   if (fill_nested) {
01522     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01523     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
01524   }
01525 }
01526 
01532 void Window::FinishInitNested(const WindowDesc *desc, WindowNumber window_number)
01533 {
01534   this->InitializeData(desc, window_number);
01535   Point pt = this->OnInitialPosition(desc, this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
01536   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
01537   this->FindWindowPlacementAndResize(desc->default_width, desc->default_height);
01538 }
01539 
01545 void Window::InitNested(const WindowDesc *desc, WindowNumber window_number)
01546 {
01547   this->CreateNestedTree(desc, false);
01548   this->FinishInitNested(desc, window_number);
01549 }
01550 
01552 Window::Window() : scrolling_scrollbar(-1)
01553 {
01554 }
01555 
01563 Window *FindWindowFromPt(int x, int y)
01564 {
01565   Window *w;
01566   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01567     if (MayBeShown(w) && IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
01568       return w;
01569     }
01570   }
01571 
01572   return NULL;
01573 }
01574 
01578 void InitWindowSystem()
01579 {
01580   IConsoleClose();
01581 
01582   _z_back_window = NULL;
01583   _z_front_window = NULL;
01584   _focused_window = NULL;
01585   _mouseover_last_w = NULL;
01586   _last_scroll_window = NULL;
01587   _scrolling_viewport = false;
01588   _mouse_hovering = false;
01589 
01590   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
01591   NWidgetScrollbar::InvalidateDimensionCache();
01592 
01593   ShowFirstError();
01594 }
01595 
01599 void UnInitWindowSystem()
01600 {
01601   UnshowCriticalError();
01602 
01603   Window *w;
01604   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
01605 
01606   for (w = _z_front_window; w != NULL; /* nothing */) {
01607     Window *to_del = w;
01608     w = w->z_back;
01609     free(to_del);
01610   }
01611 
01612   _z_front_window = NULL;
01613   _z_back_window = NULL;
01614 }
01615 
01619 void ResetWindowSystem()
01620 {
01621   UnInitWindowSystem();
01622   InitWindowSystem();
01623   _thd.Reset();
01624 }
01625 
01626 static void DecreaseWindowCounters()
01627 {
01628   Window *w;
01629   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01630     if (_scroller_click_timeout == 0) {
01631       /* Unclick scrollbar buttons if they are pressed. */
01632       for (uint i = 0; i < w->nested_array_size; i++) {
01633         NWidgetBase *nwid = w->nested_array[i];
01634         if (nwid != NULL && (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR)) {
01635           NWidgetScrollbar *sb = static_cast<NWidgetScrollbar*>(nwid);
01636           if (sb->disp_flags & (ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN)) {
01637             sb->disp_flags &= ~(ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN);
01638             w->scrolling_scrollbar = -1;
01639             sb->SetDirty(w);
01640           }
01641         }
01642       }
01643     }
01644 
01645     /* Handle editboxes */
01646     for (SmallMap<int, QueryString*>::Pair *it = w->querystrings.Begin(); it != w->querystrings.End(); ++it) {
01647       it->second->HandleEditBox(w, it->first);
01648     }
01649 
01650     w->OnMouseLoop();
01651   }
01652 
01653   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01654     if ((w->flags & WF_TIMEOUT) && --w->timeout_timer == 0) {
01655       CLRBITS(w->flags, WF_TIMEOUT);
01656 
01657       w->OnTimeout();
01658       w->RaiseButtons(true);
01659     }
01660   }
01661 }
01662 
01663 static void HandlePlacePresize()
01664 {
01665   if (_special_mouse_mode != WSM_PRESIZE) return;
01666 
01667   Window *w = _thd.GetCallbackWnd();
01668   if (w == NULL) return;
01669 
01670   Point pt = GetTileBelowCursor();
01671   if (pt.x == -1) {
01672     _thd.selend.x = -1;
01673     return;
01674   }
01675 
01676   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
01677 }
01678 
01683 static EventState HandleMouseDragDrop()
01684 {
01685   if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
01686 
01687   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED; // Dragging, but the mouse did not move.
01688 
01689   Window *w = _thd.GetCallbackWnd();
01690   if (w != NULL) {
01691     /* Send an event in client coordinates. */
01692     Point pt;
01693     pt.x = _cursor.pos.x - w->left;
01694     pt.y = _cursor.pos.y - w->top;
01695     if (_left_button_down) {
01696       w->OnMouseDrag(pt, GetWidgetFromPos(w, pt.x, pt.y));
01697     } else {
01698       w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
01699     }
01700   }
01701 
01702   if (!_left_button_down) ResetObjectToPlace(); // Button released, finished dragging.
01703   return ES_HANDLED;
01704 }
01705 
01707 static void HandleMouseOver()
01708 {
01709   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01710 
01711   /* We changed window, put a MOUSEOVER event to the last window */
01712   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
01713     /* Reset mouse-over coordinates of previous window */
01714     Point pt = { -1, -1 };
01715     _mouseover_last_w->OnMouseOver(pt, 0);
01716   }
01717 
01718   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
01719   _mouseover_last_w = w;
01720 
01721   if (w != NULL) {
01722     /* send an event in client coordinates. */
01723     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
01724     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
01725     if (widget != NULL) w->OnMouseOver(pt, widget->index);
01726   }
01727 }
01728 
01730 static const int MIN_VISIBLE_TITLE_BAR = 13;
01731 
01733 enum PreventHideDirection {
01734   PHD_UP,   
01735   PHD_DOWN, 
01736 };
01737 
01748 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
01749 {
01750   if (v == NULL) return;
01751 
01752   int v_bottom = v->top + v->height;
01753   int v_right = v->left + v->width;
01754   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.
01755 
01756   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
01757   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
01758 
01759   /* Vertically, the rectangle is hidden behind v. */
01760   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
01761     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
01762     return;
01763   }
01764   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
01765     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
01766     return;
01767   }
01768 
01769   /* Horizontally also hidden, force movement to a safe area. */
01770   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
01771     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
01772   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
01773     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
01774   } else {
01775     *ny = safe_y;
01776   }
01777 }
01778 
01786 static void EnsureVisibleCaption(Window *w, int nx, int ny)
01787 {
01788   /* Search for the title bar rectangle. */
01789   Rect caption_rect;
01790   const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
01791   if (caption != NULL) {
01792     caption_rect.left   = caption->pos_x;
01793     caption_rect.right  = caption->pos_x + caption->current_x;
01794     caption_rect.top    = caption->pos_y;
01795     caption_rect.bottom = caption->pos_y + caption->current_y;
01796 
01797     /* Make sure the window doesn't leave the screen */
01798     nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
01799     ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
01800 
01801     /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
01802     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
01803     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
01804   }
01805 
01806   if (w->viewport != NULL) {
01807     w->viewport->left += nx - w->left;
01808     w->viewport->top  += ny - w->top;
01809   }
01810 
01811   w->left = nx;
01812   w->top  = ny;
01813 }
01814 
01825 void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen)
01826 {
01827   if (delta_x != 0 || delta_y != 0) {
01828     if (clamp_to_screen) {
01829       /* Determine the new right/bottom position. If that is outside of the bounds of
01830        * the resolution clamp it in such a manner that it stays within the bounds. */
01831       int new_right  = w->left + w->width  + delta_x;
01832       int new_bottom = w->top  + w->height + delta_y;
01833       if (new_right  >= (int)_cur_resolution.width)  delta_x -= Ceil(new_right  - _cur_resolution.width,  max(1U, w->nested_root->resize_x));
01834       if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, max(1U, w->nested_root->resize_y));
01835     }
01836 
01837     w->SetDirty();
01838 
01839     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);
01840     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);
01841     assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
01842     assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
01843 
01844     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);
01845     w->width  = w->nested_root->current_x;
01846     w->height = w->nested_root->current_y;
01847   }
01848 
01849   EnsureVisibleCaption(w, w->left, w->top);
01850 
01851   /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */
01852   w->OnResize();
01853   w->SetDirty();
01854 }
01855 
01861 int GetMainViewTop()
01862 {
01863   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01864   return (w == NULL) ? 0 : w->top + w->height;
01865 }
01866 
01872 int GetMainViewBottom()
01873 {
01874   Window *w = FindWindowById(WC_STATUS_BAR, 0);
01875   return (w == NULL) ? _screen.height : w->top;
01876 }
01877 
01878 static bool _dragging_window; 
01879 
01884 static EventState HandleWindowDragging()
01885 {
01886   /* Get out immediately if no window is being dragged at all. */
01887   if (!_dragging_window) return ES_NOT_HANDLED;
01888 
01889   /* If button still down, but cursor hasn't moved, there is nothing to do. */
01890   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
01891 
01892   /* Otherwise find the window... */
01893   Window *w;
01894   FOR_ALL_WINDOWS_FROM_BACK(w) {
01895     if (w->flags & WF_DRAGGING) {
01896       /* Stop the dragging if the left mouse button was released */
01897       if (!_left_button_down) {
01898         w->flags &= ~WF_DRAGGING;
01899         break;
01900       }
01901 
01902       w->SetDirty();
01903 
01904       int x = _cursor.pos.x + _drag_delta.x;
01905       int y = _cursor.pos.y + _drag_delta.y;
01906       int nx = x;
01907       int ny = y;
01908 
01909       if (_settings_client.gui.window_snap_radius != 0) {
01910         const Window *v;
01911 
01912         int hsnap = _settings_client.gui.window_snap_radius;
01913         int vsnap = _settings_client.gui.window_snap_radius;
01914         int delta;
01915 
01916         FOR_ALL_WINDOWS_FROM_BACK(v) {
01917           if (v == w) continue; // Don't snap at yourself
01918 
01919           if (y + w->height > v->top && y < v->top + v->height) {
01920             /* Your left border <-> other right border */
01921             delta = abs(v->left + v->width - x);
01922             if (delta <= hsnap) {
01923               nx = v->left + v->width;
01924               hsnap = delta;
01925             }
01926 
01927             /* Your right border <-> other left border */
01928             delta = abs(v->left - x - w->width);
01929             if (delta <= hsnap) {
01930               nx = v->left - w->width;
01931               hsnap = delta;
01932             }
01933           }
01934 
01935           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
01936             /* Your left border <-> other left border */
01937             delta = abs(v->left - x);
01938             if (delta <= hsnap) {
01939               nx = v->left;
01940               hsnap = delta;
01941             }
01942 
01943             /* Your right border <-> other right border */
01944             delta = abs(v->left + v->width - x - w->width);
01945             if (delta <= hsnap) {
01946               nx = v->left + v->width - w->width;
01947               hsnap = delta;
01948             }
01949           }
01950 
01951           if (x + w->width > v->left && x < v->left + v->width) {
01952             /* Your top border <-> other bottom border */
01953             delta = abs(v->top + v->height - y);
01954             if (delta <= vsnap) {
01955               ny = v->top + v->height;
01956               vsnap = delta;
01957             }
01958 
01959             /* Your bottom border <-> other top border */
01960             delta = abs(v->top - y - w->height);
01961             if (delta <= vsnap) {
01962               ny = v->top - w->height;
01963               vsnap = delta;
01964             }
01965           }
01966 
01967           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
01968             /* Your top border <-> other top border */
01969             delta = abs(v->top - y);
01970             if (delta <= vsnap) {
01971               ny = v->top;
01972               vsnap = delta;
01973             }
01974 
01975             /* Your bottom border <-> other bottom border */
01976             delta = abs(v->top + v->height - y - w->height);
01977             if (delta <= vsnap) {
01978               ny = v->top + v->height - w->height;
01979               vsnap = delta;
01980             }
01981           }
01982         }
01983       }
01984 
01985       EnsureVisibleCaption(w, nx, ny);
01986 
01987       w->SetDirty();
01988       return ES_HANDLED;
01989     } else if (w->flags & WF_SIZING) {
01990       /* Stop the sizing if the left mouse button was released */
01991       if (!_left_button_down) {
01992         w->flags &= ~WF_SIZING;
01993         w->SetDirty();
01994         break;
01995       }
01996 
01997       /* Compute difference in pixels between cursor position and reference point in the window.
01998        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
01999        */
02000       int x, y = _cursor.pos.y - _drag_delta.y;
02001       if (w->flags & WF_SIZING_LEFT) {
02002         x = _drag_delta.x - _cursor.pos.x;
02003       } else {
02004         x = _cursor.pos.x - _drag_delta.x;
02005       }
02006 
02007       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
02008       if (w->resize.step_width  == 0) x = 0;
02009       if (w->resize.step_height == 0) y = 0;
02010 
02011       /* Check the resize button won't go past the bottom of the screen */
02012       if (w->top + w->height + y > _screen.height) {
02013         y = _screen.height - w->height - w->top;
02014       }
02015 
02016       /* X and Y has to go by step.. calculate it.
02017        * The cast to int is necessary else x/y are implicitly casted to
02018        * unsigned int, which won't work. */
02019       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
02020       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
02021 
02022       /* Check that we don't go below the minimum set size */
02023       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
02024         x = w->nested_root->smallest_x - w->width;
02025       }
02026       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
02027         y = w->nested_root->smallest_y - w->height;
02028       }
02029 
02030       /* Window already on size */
02031       if (x == 0 && y == 0) return ES_HANDLED;
02032 
02033       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
02034       _drag_delta.y += y;
02035       if ((w->flags & WF_SIZING_LEFT) && x != 0) {
02036         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
02037         w->SetDirty();
02038         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
02039         /* ResizeWindow() below ensures marking new position as dirty. */
02040       } else {
02041         _drag_delta.x += x;
02042       }
02043 
02044       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
02045       ResizeWindow(w, x, y);
02046       return ES_HANDLED;
02047     }
02048   }
02049 
02050   _dragging_window = false;
02051   return ES_HANDLED;
02052 }
02053 
02058 static void StartWindowDrag(Window *w)
02059 {
02060   w->flags |= WF_DRAGGING;
02061   w->flags &= ~WF_CENTERED;
02062   _dragging_window = true;
02063 
02064   _drag_delta.x = w->left - _cursor.pos.x;
02065   _drag_delta.y = w->top  - _cursor.pos.y;
02066 
02067   BringWindowToFront(w);
02068   DeleteWindowById(WC_DROPDOWN_MENU, 0);
02069 }
02070 
02076 static void StartWindowSizing(Window *w, bool to_left)
02077 {
02078   w->flags |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
02079   w->flags &= ~WF_CENTERED;
02080   _dragging_window = true;
02081 
02082   _drag_delta.x = _cursor.pos.x;
02083   _drag_delta.y = _cursor.pos.y;
02084 
02085   BringWindowToFront(w);
02086   DeleteWindowById(WC_DROPDOWN_MENU, 0);
02087 }
02088 
02093 static EventState HandleScrollbarScrolling()
02094 {
02095   Window *w;
02096   FOR_ALL_WINDOWS_FROM_BACK(w) {
02097     if (w->scrolling_scrollbar >= 0) {
02098       /* Abort if no button is clicked any more. */
02099       if (!_left_button_down) {
02100         w->scrolling_scrollbar = -1;
02101         w->SetDirty();
02102         return ES_HANDLED;
02103       }
02104 
02105       int i;
02106       NWidgetScrollbar *sb = w->GetWidget<NWidgetScrollbar>(w->scrolling_scrollbar);
02107       bool rtl = false;
02108 
02109       if (sb->type == NWID_HSCROLLBAR) {
02110         i = _cursor.pos.x - _cursorpos_drag_start.x;
02111         rtl = _current_text_dir == TD_RTL;
02112       } else {
02113         i = _cursor.pos.y - _cursorpos_drag_start.y;
02114       }
02115 
02116       if (sb->disp_flags & ND_SCROLLBAR_BTN) {
02117         if (_scroller_click_timeout == 1) {
02118           _scroller_click_timeout = 3;
02119           sb->UpdatePosition(rtl == HasBit(sb->disp_flags, NDB_SCROLLBAR_UP) ? 1 : -1);
02120           w->SetDirty();
02121         }
02122         return ES_HANDLED;
02123       }
02124 
02125       /* Find the item we want to move to and make sure it's inside bounds. */
02126       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
02127       if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
02128       if (pos != sb->GetPosition()) {
02129         sb->SetPosition(pos);
02130         w->SetDirty();
02131       }
02132       return ES_HANDLED;
02133     }
02134   }
02135 
02136   return ES_NOT_HANDLED;
02137 }
02138 
02143 static EventState HandleViewportScroll()
02144 {
02145   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02146 
02147   if (!_scrolling_viewport) return ES_NOT_HANDLED;
02148 
02149   /* When we don't have a last scroll window we are starting to scroll.
02150    * When the last scroll window and this are not the same we went
02151    * outside of the window and should not left-mouse scroll anymore. */
02152   if (_last_scroll_window == NULL) _last_scroll_window = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
02153 
02154   if (_last_scroll_window == NULL || !(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down))) {
02155     _cursor.fix_at = false;
02156     _scrolling_viewport = false;
02157     _last_scroll_window = NULL;
02158     return ES_NOT_HANDLED;
02159   }
02160 
02161   if (_last_scroll_window == FindWindowById(WC_MAIN_WINDOW, 0) && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) {
02162     /* If the main window is following a vehicle, then first let go of it! */
02163     const Vehicle *veh = Vehicle::Get(_last_scroll_window->viewport->follow_vehicle);
02164     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
02165     return ES_NOT_HANDLED;
02166   }
02167 
02168   Point delta;
02169   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
02170     delta.x = -_cursor.delta.x;
02171     delta.y = -_cursor.delta.y;
02172   } else {
02173     delta.x = _cursor.delta.x;
02174     delta.y = _cursor.delta.y;
02175   }
02176 
02177   if (scrollwheel_scrolling) {
02178     /* We are using scrollwheels for scrolling */
02179     delta.x = _cursor.h_wheel;
02180     delta.y = _cursor.v_wheel;
02181     _cursor.v_wheel = 0;
02182     _cursor.h_wheel = 0;
02183   }
02184 
02185   /* Create a scroll-event and send it to the window */
02186   if (delta.x != 0 || delta.y != 0) _last_scroll_window->OnScroll(delta);
02187 
02188   _cursor.delta.x = 0;
02189   _cursor.delta.y = 0;
02190   return ES_HANDLED;
02191 }
02192 
02203 static bool MaybeBringWindowToFront(Window *w)
02204 {
02205   bool bring_to_front = false;
02206 
02207   if (w->window_class == WC_MAIN_WINDOW ||
02208       IsVitalWindow(w) ||
02209       w->window_class == WC_TOOLTIPS ||
02210       w->window_class == WC_DROPDOWN_MENU) {
02211     return true;
02212   }
02213 
02214   /* Use unshaded window size rather than current size for shaded windows. */
02215   int w_width  = w->width;
02216   int w_height = w->height;
02217   if (w->IsShaded()) {
02218     w_width  = w->unshaded_size.width;
02219     w_height = w->unshaded_size.height;
02220   }
02221 
02222   Window *u;
02223   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
02224     /* A modal child will prevent the activation of the parent window */
02225     if (u->parent == w && (u->desc_flags & WDF_MODAL)) {
02226       u->SetWhiteBorder();
02227       u->SetDirty();
02228       return false;
02229     }
02230 
02231     if (u->window_class == WC_MAIN_WINDOW ||
02232         IsVitalWindow(u) ||
02233         u->window_class == WC_TOOLTIPS ||
02234         u->window_class == WC_DROPDOWN_MENU) {
02235       continue;
02236     }
02237 
02238     /* Window sizes don't interfere, leave z-order alone */
02239     if (w->left + w_width <= u->left ||
02240         u->left + u->width <= w->left ||
02241         w->top  + w_height <= u->top ||
02242         u->top + u->height <= w->top) {
02243       continue;
02244     }
02245 
02246     bring_to_front = true;
02247   }
02248 
02249   if (bring_to_front) BringWindowToFront(w);
02250   return true;
02251 }
02252 
02261 EventState Window::HandleEditBoxKey(int wid, uint16 key, uint16 keycode)
02262 {
02263   EventState state = ES_NOT_HANDLED;
02264 
02265   QueryString *query = this->GetQueryString(wid);
02266   if (query == NULL) return state;
02267 
02268   int action = QueryString::ACTION_NOTHING;
02269 
02270   switch (query->HandleEditBoxKey(this, wid, key, keycode, state)) {
02271     case HEBR_EDITING:
02272       this->OnEditboxChanged(wid);
02273       break;
02274 
02275     case HEBR_CONFIRM:
02276       if (query->ok_button >= 0) {
02277         this->OnClick(Point(), query->ok_button, 1);
02278       } else {
02279         action = query->ok_button;
02280       }
02281       break;
02282 
02283     case HEBR_CANCEL:
02284       if (query->cancel_button >= 0) {
02285         this->OnClick(Point(), query->cancel_button, 1);
02286       } else {
02287         action = query->cancel_button;
02288       }
02289       break;
02290 
02291     default: break;
02292   }
02293 
02294   switch (action) {
02295     case QueryString::ACTION_DESELECT:
02296       this->UnfocusFocusedWidget();
02297       break;
02298 
02299     case QueryString::ACTION_CLEAR:
02300       query->text.DeleteAll();
02301       this->SetWidgetDirty(wid);
02302       this->OnEditboxChanged(wid);
02303       break;
02304 
02305     default:
02306       break;
02307   }
02308 
02309   return state;
02310 }
02311 
02316 void HandleKeypress(uint32 raw_key)
02317 {
02318   /* World generation is multithreaded and messes with companies.
02319    * But there is no company related window open anyway, so _current_company is not used. */
02320   assert(HasModalProgress() || IsLocalCompany());
02321 
02322   /* Setup event */
02323   uint16 key     = GB(raw_key,  0, 16);
02324   uint16 keycode = GB(raw_key, 16, 16);
02325 
02326   /*
02327    * The Unicode standard defines an area called the private use area. Code points in this
02328    * area are reserved for private use and thus not portable between systems. For instance,
02329    * Apple defines code points for the arrow keys in this area, but these are only printable
02330    * on a system running OS X. We don't want these keys to show up in text fields and such,
02331    * and thus we have to clear the unicode character when we encounter such a key.
02332    */
02333   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
02334 
02335   /*
02336    * If both key and keycode is zero, we don't bother to process the event.
02337    */
02338   if (key == 0 && keycode == 0) return;
02339 
02340   /* Check if the focused window has a focused editbox */
02341   if (EditBoxInGlobalFocus()) {
02342     /* All input will in this case go to the focused editbox */
02343     if (_focused_window->window_class == WC_CONSOLE) {
02344       if (_focused_window->OnKeyPress(key, keycode) == ES_HANDLED) return;
02345     } else {
02346       if (_focused_window->HandleEditBoxKey(_focused_window->nested_focus->index, key, keycode) == ES_HANDLED) return;
02347     }
02348   }
02349 
02350   /* Call the event, start with the uppermost window, but ignore the toolbar. */
02351   Window *w;
02352   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02353     if (w->window_class == WC_MAIN_TOOLBAR) continue;
02354     if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02355   }
02356 
02357   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
02358   /* When there is no toolbar w is null, check for that */
02359   if (w != NULL && w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02360 
02361   HandleGlobalHotkeys(key, keycode);
02362 }
02363 
02367 void HandleCtrlChanged()
02368 {
02369   /* Call the event, start with the uppermost window. */
02370   Window *w;
02371   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02372     if (w->OnCTRLStateChange() == ES_HANDLED) return;
02373   }
02374 }
02375 
02382 static int _input_events_this_tick = 0;
02383 
02388 static void HandleAutoscroll()
02389 {
02390   if (_game_mode == GM_MENU || HasModalProgress()) return;
02391   if (_settings_client.gui.auto_scrolling == VA_DISABLED) return;
02392   if (_settings_client.gui.auto_scrolling == VA_MAIN_VIEWPORT_FULLSCREEN && !_fullscreen) return;
02393 
02394   int x = _cursor.pos.x;
02395   int y = _cursor.pos.y;
02396   Window *w = FindWindowFromPt(x, y);
02397   if (w == NULL || w->flags & WF_DISABLE_VP_SCROLL) return;
02398   if (_settings_client.gui.auto_scrolling != VA_EVERY_VIEWPORT && w->window_class != WC_MAIN_WINDOW) return;
02399 
02400   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02401   if (vp == NULL) return;
02402 
02403   x -= vp->left;
02404   y -= vp->top;
02405 
02406   /* here allows scrolling in both x and y axis */
02407 #define scrollspeed 3
02408   if (x - 15 < 0) {
02409     w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
02410   } else if (15 - (vp->width - x) > 0) {
02411     w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
02412   }
02413   if (y - 15 < 0) {
02414     w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
02415   } else if (15 - (vp->height - y) > 0) {
02416     w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
02417   }
02418 #undef scrollspeed
02419 }
02420 
02421 enum MouseClick {
02422   MC_NONE = 0,
02423   MC_LEFT,
02424   MC_RIGHT,
02425   MC_DOUBLE_LEFT,
02426   MC_HOVER,
02427 
02428   MAX_OFFSET_DOUBLE_CLICK = 5,     
02429   TIME_BETWEEN_DOUBLE_CLICK = 500, 
02430   MAX_OFFSET_HOVER = 5,            
02431 };
02432 extern EventState VpHandlePlaceSizingDrag();
02433 
02434 static void ScrollMainViewport(int x, int y)
02435 {
02436   if (_game_mode != GM_MENU) {
02437     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
02438     assert(w);
02439 
02440     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
02441     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
02442   }
02443 }
02444 
02454 static const int8 scrollamt[16][2] = {
02455   { 0,  0}, 
02456   {-2,  0}, 
02457   { 0, -2}, 
02458   {-2, -1}, 
02459   { 2,  0}, 
02460   { 0,  0}, 
02461   { 2, -1}, 
02462   { 0, -2}, 
02463   { 0,  2}, 
02464   {-2,  1}, 
02465   { 0,  0}, 
02466   {-2,  0}, 
02467   { 2,  1}, 
02468   { 0,  2}, 
02469   { 2,  0}, 
02470   { 0,  0}, 
02471 };
02472 
02473 static void HandleKeyScrolling()
02474 {
02475   /*
02476    * Check that any of the dirkeys is pressed and that the focused window
02477    * dont has an edit-box as focused widget.
02478    */
02479   if (_dirkeys && !EditBoxInGlobalFocus()) {
02480     int factor = _shift_pressed ? 50 : 10;
02481     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
02482   }
02483 }
02484 
02485 static void MouseLoop(MouseClick click, int mousewheel)
02486 {
02487   /* World generation is multithreaded and messes with companies.
02488    * But there is no company related window open anyway, so _current_company is not used. */
02489   assert(HasModalProgress() || IsLocalCompany());
02490 
02491   HandlePlacePresize();
02492   UpdateTileSelection();
02493 
02494   if (VpHandlePlaceSizingDrag()  == ES_HANDLED) return;
02495   if (HandleMouseDragDrop()      == ES_HANDLED) return;
02496   if (HandleWindowDragging()     == ES_HANDLED) return;
02497   if (HandleScrollbarScrolling() == ES_HANDLED) return;
02498   if (HandleViewportScroll()     == ES_HANDLED) return;
02499 
02500   HandleMouseOver();
02501 
02502   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02503   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
02504 
02505   int x = _cursor.pos.x;
02506   int y = _cursor.pos.y;
02507   Window *w = FindWindowFromPt(x, y);
02508   if (w == NULL) return;
02509 
02510   if (click != MC_HOVER && !MaybeBringWindowToFront(w)) return;
02511   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02512 
02513   /* Don't allow any action in a viewport if either in menu or when having a modal progress window */
02514   if (vp != NULL && (_game_mode == GM_MENU || HasModalProgress())) return;
02515 
02516   if (mousewheel != 0) {
02517     /* Send mousewheel event to window */
02518     w->OnMouseWheel(mousewheel);
02519 
02520     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
02521     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
02522   }
02523 
02524   if (vp != NULL) {
02525     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
02526     switch (click) {
02527       case MC_DOUBLE_LEFT:
02528       case MC_LEFT:
02529         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
02530         if (!HandleViewportClicked(vp, x, y) &&
02531             !(w->flags & WF_DISABLE_VP_SCROLL) &&
02532             _settings_client.gui.left_mouse_btn_scrolling) {
02533           _scrolling_viewport = true;
02534           _cursor.fix_at = false;
02535         }
02536         break;
02537 
02538       case MC_RIGHT:
02539         if (!(w->flags & WF_DISABLE_VP_SCROLL)) {
02540           _scrolling_viewport = true;
02541           _cursor.fix_at = true;
02542 
02543           /* clear 2D scrolling caches before we start a 2D scroll */
02544           _cursor.h_wheel = 0;
02545           _cursor.v_wheel = 0;
02546         }
02547         break;
02548 
02549       default:
02550         break;
02551     }
02552   } else {
02553     switch (click) {
02554       case MC_LEFT:
02555       case MC_DOUBLE_LEFT:
02556         DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
02557         break;
02558 
02559       default:
02560         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
02561         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
02562          * Simulate a right button click so we can get started. */
02563         /* FALL THROUGH */
02564 
02565       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
02566 
02567       case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
02568     }
02569   }
02570 }
02571 
02575 void HandleMouseEvents()
02576 {
02577   /* World generation is multithreaded and messes with companies.
02578    * But there is no company related window open anyway, so _current_company is not used. */
02579   assert(HasModalProgress() || IsLocalCompany());
02580 
02581   static int double_click_time = 0;
02582   static Point double_click_pos = {0, 0};
02583 
02584   /* Mouse event? */
02585   MouseClick click = MC_NONE;
02586   if (_left_button_down && !_left_button_clicked) {
02587     click = MC_LEFT;
02588     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
02589         double_click_pos.x != 0 && abs(_cursor.pos.x - double_click_pos.x) < MAX_OFFSET_DOUBLE_CLICK  &&
02590         double_click_pos.y != 0 && abs(_cursor.pos.y - double_click_pos.y) < MAX_OFFSET_DOUBLE_CLICK) {
02591       click = MC_DOUBLE_LEFT;
02592     }
02593     double_click_time = _realtime_tick;
02594     double_click_pos = _cursor.pos;
02595     _left_button_clicked = true;
02596     _input_events_this_tick++;
02597   } else if (_right_button_clicked) {
02598     _right_button_clicked = false;
02599     click = MC_RIGHT;
02600     _input_events_this_tick++;
02601   }
02602 
02603   int mousewheel = 0;
02604   if (_cursor.wheel) {
02605     mousewheel = _cursor.wheel;
02606     _cursor.wheel = 0;
02607     _input_events_this_tick++;
02608   }
02609 
02610   static uint32 hover_time = 0;
02611   static Point hover_pos = {0, 0};
02612 
02613   if (_settings_client.gui.hover_delay > 0) {
02614     if (!_cursor.in_window || click != MC_NONE || mousewheel != 0 || _left_button_down || _right_button_down ||
02615         hover_pos.x == 0 || abs(_cursor.pos.x - hover_pos.x) >= MAX_OFFSET_HOVER  ||
02616         hover_pos.y == 0 || abs(_cursor.pos.y - hover_pos.y) >= MAX_OFFSET_HOVER) {
02617       hover_pos = _cursor.pos;
02618       hover_time = _realtime_tick;
02619       _mouse_hovering = false;
02620     } else {
02621       if (hover_time != 0 && _realtime_tick > hover_time + _settings_client.gui.hover_delay * 1000) {
02622         click = MC_HOVER;
02623         _input_events_this_tick++;
02624         _mouse_hovering = true;
02625       }
02626     }
02627   }
02628 
02629   /* Handle sprite picker before any GUI interaction */
02630   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && _newgrf_debug_sprite_picker.click_time != _realtime_tick) {
02631     /* Next realtime tick? Then redraw has finished */
02632     _newgrf_debug_sprite_picker.mode = SPM_NONE;
02633     InvalidateWindowData(WC_SPRITE_ALIGNER, 0, 1);
02634   }
02635 
02636   if (click == MC_LEFT && _newgrf_debug_sprite_picker.mode == SPM_WAIT_CLICK) {
02637     /* Mark whole screen dirty, and wait for the next realtime tick, when drawing is finished. */
02638     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
02639     _newgrf_debug_sprite_picker.clicked_pixel = blitter->MoveTo(_screen.dst_ptr, _cursor.pos.x, _cursor.pos.y);
02640     _newgrf_debug_sprite_picker.click_time = _realtime_tick;
02641     _newgrf_debug_sprite_picker.sprites.Clear();
02642     _newgrf_debug_sprite_picker.mode = SPM_REDRAW;
02643     MarkWholeScreenDirty();
02644   } else {
02645     MouseLoop(click, mousewheel);
02646   }
02647 
02648   /* We have moved the mouse the required distance,
02649    * no need to move it at any later time. */
02650   _cursor.delta.x = 0;
02651   _cursor.delta.y = 0;
02652 }
02653 
02657 static void CheckSoftLimit()
02658 {
02659   if (_settings_client.gui.window_soft_limit == 0) return;
02660 
02661   for (;;) {
02662     uint deletable_count = 0;
02663     Window *w, *last_deletable = NULL;
02664     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02665       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags & WF_STICKY)) continue;
02666 
02667       last_deletable = w;
02668       deletable_count++;
02669     }
02670 
02671     /* We've not reached the soft limit yet. */
02672     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
02673 
02674     assert(last_deletable != NULL);
02675     delete last_deletable;
02676   }
02677 }
02678 
02682 void InputLoop()
02683 {
02684   /* World generation is multithreaded and messes with companies.
02685    * But there is no company related window open anyway, so _current_company is not used. */
02686   assert(HasModalProgress() || IsLocalCompany());
02687 
02688   CheckSoftLimit();
02689   HandleKeyScrolling();
02690 
02691   /* Do the actual free of the deleted windows. */
02692   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
02693     Window *w = v;
02694     v = v->z_back;
02695 
02696     if (w->window_class != WC_INVALID) continue;
02697 
02698     RemoveWindowFromZOrdering(w);
02699     free(w);
02700   }
02701 
02702   if (_scroller_click_timeout != 0) _scroller_click_timeout--;
02703   DecreaseWindowCounters();
02704 
02705   if (_input_events_this_tick != 0) {
02706     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
02707     _input_events_this_tick = 0;
02708     /* there were some inputs this tick, don't scroll ??? */
02709     return;
02710   }
02711 
02712   /* HandleMouseEvents was already called for this tick */
02713   HandleMouseEvents();
02714   HandleAutoscroll();
02715 }
02716 
02720 void UpdateWindows()
02721 {
02722   Window *w;
02723 
02724   static int highlight_timer = 1;
02725   if (--highlight_timer == 0) {
02726     highlight_timer = 15;
02727     _window_highlight_colour = !_window_highlight_colour;
02728   }
02729 
02730   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02731     w->ProcessScheduledInvalidations();
02732     w->ProcessHighlightedInvalidations();
02733   }
02734 
02735   static int we4_timer = 0;
02736   int t = we4_timer + 1;
02737 
02738   if (t >= 100) {
02739     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02740       w->OnHundredthTick();
02741     }
02742     t = 0;
02743   }
02744   we4_timer = t;
02745 
02746   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02747     if ((w->flags & WF_WHITE_BORDER) && --w->white_border_timer == 0) {
02748       CLRBITS(w->flags, WF_WHITE_BORDER);
02749       w->SetDirty();
02750     }
02751   }
02752 
02753   DrawDirtyBlocks();
02754 
02755   FOR_ALL_WINDOWS_FROM_BACK(w) {
02756     /* Update viewport only if window is not shaded. */
02757     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
02758   }
02759   NetworkDrawChatMessage();
02760   /* Redraw mouse cursor in case it was hidden */
02761   DrawMouseCursor();
02762 }
02763 
02769 void SetWindowDirty(WindowClass cls, WindowNumber number)
02770 {
02771   const Window *w;
02772   FOR_ALL_WINDOWS_FROM_BACK(w) {
02773     if (w->window_class == cls && w->window_number == number) w->SetDirty();
02774   }
02775 }
02776 
02783 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
02784 {
02785   const Window *w;
02786   FOR_ALL_WINDOWS_FROM_BACK(w) {
02787     if (w->window_class == cls && w->window_number == number) {
02788       w->SetWidgetDirty(widget_index);
02789     }
02790   }
02791 }
02792 
02797 void SetWindowClassesDirty(WindowClass cls)
02798 {
02799   Window *w;
02800   FOR_ALL_WINDOWS_FROM_BACK(w) {
02801     if (w->window_class == cls) w->SetDirty();
02802   }
02803 }
02804 
02810 void Window::InvalidateData(int data, bool gui_scope)
02811 {
02812   this->SetDirty();
02813   if (!gui_scope) {
02814     /* Schedule GUI-scope invalidation for next redraw. */
02815     *this->scheduled_invalidation_data.Append() = data;
02816   }
02817   this->OnInvalidateData(data, gui_scope);
02818 }
02819 
02823 void Window::ProcessScheduledInvalidations()
02824 {
02825   for (int *data = this->scheduled_invalidation_data.Begin(); this->window_class != WC_INVALID && data != this->scheduled_invalidation_data.End(); data++) {
02826     this->OnInvalidateData(*data, true);
02827   }
02828   this->scheduled_invalidation_data.Clear();
02829 }
02830 
02834 void Window::ProcessHighlightedInvalidations()
02835 {
02836   if ((this->flags & WF_HIGHLIGHTED) == 0) return;
02837 
02838   for (uint i = 0; i < this->nested_array_size; i++) {
02839     if (this->IsWidgetHighlighted(i)) this->SetWidgetDirty(i);
02840   }
02841 }
02842 
02869 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
02870 {
02871   Window *w;
02872   FOR_ALL_WINDOWS_FROM_BACK(w) {
02873     if (w->window_class == cls && w->window_number == number) {
02874       w->InvalidateData(data, gui_scope);
02875     }
02876   }
02877 }
02878 
02887 void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
02888 {
02889   Window *w;
02890 
02891   FOR_ALL_WINDOWS_FROM_BACK(w) {
02892     if (w->window_class == cls) {
02893       w->InvalidateData(data, gui_scope);
02894     }
02895   }
02896 }
02897 
02901 void CallWindowTickEvent()
02902 {
02903   Window *w;
02904   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02905     w->OnTick();
02906   }
02907 }
02908 
02915 void DeleteNonVitalWindows()
02916 {
02917   Window *w;
02918 
02919 restart_search:
02920   /* When we find the window to delete, we need to restart the search
02921    * as deleting this window could cascade in deleting (many) others
02922    * anywhere in the z-array */
02923   FOR_ALL_WINDOWS_FROM_BACK(w) {
02924     if (w->window_class != WC_MAIN_WINDOW &&
02925         w->window_class != WC_SELECT_GAME &&
02926         w->window_class != WC_MAIN_TOOLBAR &&
02927         w->window_class != WC_STATUS_BAR &&
02928         w->window_class != WC_TOOLTIPS &&
02929         (w->flags & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
02930 
02931       delete w;
02932       goto restart_search;
02933     }
02934   }
02935 }
02936 
02944 void DeleteAllNonVitalWindows()
02945 {
02946   Window *w;
02947 
02948   /* Delete every window except for stickied ones, then sticky ones as well */
02949   DeleteNonVitalWindows();
02950 
02951 restart_search:
02952   /* When we find the window to delete, we need to restart the search
02953    * as deleting this window could cascade in deleting (many) others
02954    * anywhere in the z-array */
02955   FOR_ALL_WINDOWS_FROM_BACK(w) {
02956     if (w->flags & WF_STICKY) {
02957       delete w;
02958       goto restart_search;
02959     }
02960   }
02961 }
02962 
02967 void DeleteConstructionWindows()
02968 {
02969   Window *w;
02970 
02971 restart_search:
02972   /* When we find the window to delete, we need to restart the search
02973    * as deleting this window could cascade in deleting (many) others
02974    * anywhere in the z-array */
02975   FOR_ALL_WINDOWS_FROM_BACK(w) {
02976     if (w->desc_flags & WDF_CONSTRUCTION) {
02977       delete w;
02978       goto restart_search;
02979     }
02980   }
02981 
02982   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
02983 }
02984 
02986 void HideVitalWindows()
02987 {
02988   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
02989   DeleteWindowById(WC_STATUS_BAR, 0);
02990 }
02991 
02993 void ReInitAllWindows()
02994 {
02995   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
02996   NWidgetScrollbar::InvalidateDimensionCache();
02997 
02998   Window *w;
02999   FOR_ALL_WINDOWS_FROM_BACK(w) {
03000     w->ReInit();
03001   }
03002 #ifdef ENABLE_NETWORK
03003   void NetworkReInitChatBoxSize();
03004   NetworkReInitChatBoxSize();
03005 #endif
03006 
03007   /* Make sure essential parts of all windows are visible */
03008   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
03009   MarkWholeScreenDirty();
03010 }
03011 
03019 static int PositionWindow(Window *w, WindowClass clss, int setting)
03020 {
03021   if (w == NULL || w->window_class != clss) {
03022     w = FindWindowById(clss, 0);
03023   }
03024   if (w == NULL) return 0;
03025 
03026   int old_left = w->left;
03027   switch (setting) {
03028     case 1:  w->left = (_screen.width - w->width) / 2; break;
03029     case 2:  w->left = _screen.width - w->width; break;
03030     default: w->left = 0; break;
03031   }
03032   if (w->viewport != NULL) w->viewport->left += w->left - old_left;
03033   SetDirtyBlocks(0, w->top, _screen.width, w->top + w->height); // invalidate the whole row
03034   return w->left;
03035 }
03036 
03042 int PositionMainToolbar(Window *w)
03043 {
03044   DEBUG(misc, 5, "Repositioning Main Toolbar...");
03045   return PositionWindow(w, WC_MAIN_TOOLBAR, _settings_client.gui.toolbar_pos);
03046 }
03047 
03053 int PositionStatusbar(Window *w)
03054 {
03055   DEBUG(misc, 5, "Repositioning statusbar...");
03056   return PositionWindow(w, WC_STATUS_BAR, _settings_client.gui.statusbar_pos);
03057 }
03058 
03064 int PositionNewsMessage(Window *w)
03065 {
03066   DEBUG(misc, 5, "Repositioning news message...");
03067   return PositionWindow(w, WC_NEWS_WINDOW, _settings_client.gui.statusbar_pos);
03068 }
03069 
03075 int PositionNetworkChatWindow(Window *w)
03076 {
03077   DEBUG(misc, 5, "Repositioning network chat window...");
03078   return PositionWindow(w, WC_SEND_NETWORK_MSG, _settings_client.gui.statusbar_pos);
03079 }
03080 
03081 
03087 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
03088 {
03089   Window *w;
03090   FOR_ALL_WINDOWS_FROM_BACK(w) {
03091     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
03092       w->viewport->follow_vehicle = to_index;
03093       w->SetDirty();
03094     }
03095   }
03096 }
03097 
03098 
03104 void RelocateAllWindows(int neww, int newh)
03105 {
03106   Window *w;
03107 
03108   FOR_ALL_WINDOWS_FROM_BACK(w) {
03109     int left, top;
03110 
03111     if (w->window_class == WC_MAIN_WINDOW) {
03112       ViewPort *vp = w->viewport;
03113       vp->width = w->width = neww;
03114       vp->height = w->height = newh;
03115       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
03116       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
03117       continue; // don't modify top,left
03118     }
03119 
03120     /* XXX - this probably needs something more sane. For example specifying
03121      * in a 'backup'-desc that the window should always be centered. */
03122     switch (w->window_class) {
03123       case WC_BOOTSTRAP:
03124         ResizeWindow(w, neww, newh);
03125         continue;
03126 
03127       case WC_MAIN_TOOLBAR:
03128         ResizeWindow(w, min(neww, *_preferred_toolbar_size) - w->width, 0, false);
03129 
03130         top = w->top;
03131         left = PositionMainToolbar(w); // changes toolbar orientation
03132         break;
03133 
03134       case WC_NEWS_WINDOW:
03135         top = newh - w->height;
03136         left = PositionNewsMessage(w);
03137         break;
03138 
03139       case WC_STATUS_BAR:
03140         ResizeWindow(w, min(neww, *_preferred_statusbar_size) - w->width, 0, false);
03141 
03142         top = newh - w->height;
03143         left = PositionStatusbar(w);
03144         break;
03145 
03146       case WC_SEND_NETWORK_MSG:
03147         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0, false);
03148         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
03149         left = PositionNetworkChatWindow(w);
03150         break;
03151 
03152       case WC_CONSOLE:
03153         IConsoleResize(w);
03154         continue;
03155 
03156       default: {
03157         if (w->flags & WF_CENTERED) {
03158           top = (newh - w->height) >> 1;
03159           left = (neww - w->width) >> 1;
03160           break;
03161         }
03162 
03163         left = w->left;
03164         if (left + (w->width >> 1) >= neww) left = neww - w->width;
03165         if (left < 0) left = 0;
03166 
03167         top = w->top;
03168         if (top + (w->height >> 1) >= newh) top = newh - w->height;
03169         break;
03170       }
03171     }
03172 
03173     EnsureVisibleCaption(w, left, top);
03174   }
03175 }
03176 
03182 PickerWindowBase::~PickerWindowBase()
03183 {
03184   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
03185   ResetObjectToPlace();
03186 }