window.cpp

Go to the documentation of this file.
00001 /* $Id: window.cpp 21667 2010-12-30 15:32:31Z alberth $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include <stdarg.h>
00014 #include "openttd.h"
00015 #include "company_func.h"
00016 #include "gfx_func.h"
00017 #include "console_func.h"
00018 #include "console_gui.h"
00019 #include "viewport_func.h"
00020 #include "genworld.h"
00021 #include "blitter/factory.hpp"
00022 #include "zoom_func.h"
00023 #include "map_func.h"
00024 #include "vehicle_base.h"
00025 #include "cheat_type.h"
00026 #include "window_func.h"
00027 #include "tilehighlight_func.h"
00028 #include "network/network.h"
00029 #include "querystring_gui.h"
00030 #include "widgets/dropdown_func.h"
00031 #include "strings_func.h"
00032 #include "settings_type.h"
00033 #include "newgrf_debug.h"
00034 #include "hotkeys.h"
00035 #include "toolbar_gui.h"
00036 #include "statusbar_gui.h"
00037 
00038 #include "table/sprites.h"
00039 
00040 static Point _drag_delta; 
00041 static Window *_mouseover_last_w = NULL; 
00042 
00044 Window *_z_front_window = NULL;
00046 Window *_z_back_window  = NULL;
00047 
00048 /*
00049  * Window that currently has focus. - The main purpose is to generate
00050  * #FocusLost events, not to give next window in z-order focus when a
00051  * window is closed.
00052  */
00053 Window *_focused_window;
00054 
00055 Point _cursorpos_drag_start;
00056 
00057 int _scrollbar_start_pos;
00058 int _scrollbar_size;
00059 byte _scroller_click_timeout = 0;
00060 
00061 bool _scrolling_viewport;  
00062 bool _mouse_hovering;      
00063 
00064 SpecialMouseMode _special_mouse_mode; 
00065 
00067 WindowDesc::WindowDesc(WindowPosition def_pos, int16 def_width, int16 def_height,
00068       WindowClass window_class, WindowClass parent_class, uint32 flags,
00069       const NWidgetPart *nwid_parts, int16 nwid_length) :
00070   default_pos(def_pos),
00071   default_width(def_width),
00072   default_height(def_height),
00073   cls(window_class),
00074   parent_cls(parent_class),
00075   flags(flags),
00076   nwid_parts(nwid_parts),
00077   nwid_length(nwid_length)
00078 {
00079 }
00080 
00081 WindowDesc::~WindowDesc()
00082 {
00083 }
00084 
00094 int Window::GetRowFromWidget(int clickpos, int widget, int padding, int line_height) const
00095 {
00096   const NWidgetBase *wid = this->GetWidget<NWidgetBase>(widget);
00097   if (line_height < 0) line_height = wid->resize_y;
00098   if (clickpos < (int)wid->pos_y + padding) return INT_MAX;
00099   return (clickpos - (int)wid->pos_y - padding) / line_height;
00100 }
00101 
00107 const Scrollbar *Window::GetScrollbar(uint widnum) const
00108 {
00109   return this->GetWidget<NWidgetScrollbar>(widnum);
00110 }
00111 
00117 Scrollbar *Window::GetScrollbar(uint widnum)
00118 {
00119   return this->GetWidget<NWidgetScrollbar>(widnum);
00120 }
00121 
00122 
00127 void SetFocusedWindow(Window *w)
00128 {
00129   if (_focused_window == w) return;
00130 
00131   /* Invalidate focused widget */
00132   if (_focused_window != NULL) {
00133     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
00134   }
00135 
00136   /* Remember which window was previously focused */
00137   Window *old_focused = _focused_window;
00138   _focused_window = w;
00139 
00140   /* So we can inform it that it lost focus */
00141   if (old_focused != NULL) old_focused->OnFocusLost();
00142   if (_focused_window != NULL) _focused_window->OnFocus();
00143 }
00144 
00150 static bool EditBoxInGlobalFocus()
00151 {
00152   if (_focused_window == NULL) return false;
00153 
00154   /* The console does not have an edit box so a special case is needed. */
00155   if (_focused_window->window_class == WC_CONSOLE) return true;
00156 
00157   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
00158 }
00159 
00163 void Window::UnfocusFocusedWidget()
00164 {
00165   if (this->nested_focus != NULL) {
00166     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00167     this->nested_focus->SetDirty(this);
00168     this->nested_focus = NULL;
00169   }
00170 }
00171 
00177 bool Window::SetFocusedWidget(byte widget_index)
00178 {
00179   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
00180   if (widget_index >= this->nested_array_size) return false;
00181 
00182   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
00183   if (this->nested_focus != NULL) {
00184     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
00185 
00186     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00187     this->nested_focus->SetDirty(this);
00188   }
00189   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
00190   return true;
00191 }
00192 
00200 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
00201 {
00202   va_list wdg_list;
00203 
00204   va_start(wdg_list, widgets);
00205 
00206   while (widgets != WIDGET_LIST_END) {
00207     SetWidgetDisabledState(widgets, disab_stat);
00208     widgets = va_arg(wdg_list, int);
00209   }
00210 
00211   va_end(wdg_list);
00212 }
00213 
00219 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
00220 {
00221   va_list wdg_list;
00222 
00223   va_start(wdg_list, widgets);
00224 
00225   while (widgets != WIDGET_LIST_END) {
00226     SetWidgetLoweredState(widgets, lowered_stat);
00227     widgets = va_arg(wdg_list, int);
00228   }
00229 
00230   va_end(wdg_list);
00231 }
00232 
00237 void Window::RaiseButtons(bool autoraise)
00238 {
00239   for (uint i = 0; i < this->nested_array_size; i++) {
00240     if (this->nested_array[i] != NULL && (this->nested_array[i]->type & ~WWB_PUSHBUTTON) < WWT_LAST &&
00241         (!autoraise || (this->nested_array[i]->type & WWB_PUSHBUTTON)) && this->IsWidgetLowered(i)) {
00242       this->RaiseWidget(i);
00243       this->SetWidgetDirty(i);
00244     }
00245   }
00246 }
00247 
00252 void Window::SetWidgetDirty(byte widget_index) const
00253 {
00254   /* Sometimes this function is called before the window is even fully initialized */
00255   if (this->nested_array == NULL) return;
00256 
00257   this->nested_array[widget_index]->SetDirty(this);
00258 }
00259 
00265 void Window::HandleButtonClick(byte widget)
00266 {
00267   this->LowerWidget(widget);
00268   this->flags4 |= WF_TIMEOUT_BEGIN;
00269   this->SetWidgetDirty(widget);
00270 }
00271 
00272 static void StartWindowDrag(Window *w);
00273 static void StartWindowSizing(Window *w, bool to_left);
00274 
00282 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
00283 {
00284   NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
00285   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
00286 
00287   bool focused_widget_changed = false;
00288   /* If clicked on a window that previously did dot have focus */
00289   if (_focused_window != w &&                 // We already have focus, right?
00290       (w->desc_flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
00291       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
00292     focused_widget_changed = true;
00293     if (_focused_window != NULL) {
00294       _focused_window->OnFocusLost();
00295 
00296       /* The window that lost focus may have had opened a OSK, window so close it, unless the user has clicked on the OSK window. */
00297       if (w->window_class != WC_OSK) DeleteWindowById(WC_OSK, 0);
00298     }
00299     SetFocusedWindow(w);
00300     w->OnFocus();
00301   }
00302 
00303   if (nw == NULL) return; // exit if clicked outside of widgets
00304 
00305   /* don't allow any interaction if the button has been disabled */
00306   if (nw->IsDisabled()) return;
00307 
00308   int widget_index = nw->index; 
00309 
00310   /* Clicked on a widget that is not disabled.
00311    * So unless the clicked widget is the caption bar, change focus to this widget */
00312   if (widget_type != WWT_CAPTION) {
00313     /* Close the OSK window if a edit box loses focus */
00314     if (w->nested_focus != NULL &&  w->nested_focus->type == WWT_EDITBOX && w->nested_focus != nw && w->window_class != WC_OSK) {
00315       DeleteWindowById(WC_OSK, 0);
00316     }
00317 
00318     /* focused_widget_changed is 'now' only true if the window this widget
00319      * is in gained focus. In that case it must remain true, also if the
00320      * local widget focus did not change. As such it's the logical-or of
00321      * both changed states.
00322      *
00323      * If this is not preserved, then the OSK window would be opened when
00324      * a user has the edit box focused and then click on another window and
00325      * then back again on the edit box (to type some text).
00326      */
00327     focused_widget_changed |= w->SetFocusedWidget(widget_index);
00328   }
00329 
00330   /* Close any child drop down menus. If the button pressed was the drop down
00331    * list's own button, then we should not process the click any further. */
00332   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
00333 
00334   if ((widget_type & ~WWB_PUSHBUTTON) < WWT_LAST && (widget_type & WWB_PUSHBUTTON)) w->HandleButtonClick(widget_index);
00335 
00336   switch (widget_type) {
00337     case NWID_VSCROLLBAR:
00338     case NWID_HSCROLLBAR:
00339       ScrollbarClickHandler(w, nw, x, y);
00340       break;
00341 
00342     case WWT_EDITBOX:
00343       if (!focused_widget_changed) { // Only open the OSK window if clicking on an already focused edit box
00344         /* Open the OSK window if clicked on an edit box */
00345         QueryStringBaseWindow *qs = dynamic_cast<QueryStringBaseWindow *>(w);
00346         if (qs != NULL) {
00347           qs->OnOpenOSKWindow(widget_index);
00348         }
00349       }
00350       break;
00351 
00352     case WWT_CLOSEBOX: // 'X'
00353       delete w;
00354       return;
00355 
00356     case WWT_CAPTION: // 'Title bar'
00357       StartWindowDrag(w);
00358       return;
00359 
00360     case WWT_RESIZEBOX:
00361       /* When the resize widget is on the left size of the window
00362        * we assume that that button is used to resize to the left. */
00363       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
00364       nw->SetDirty(w);
00365       return;
00366 
00367     case WWT_DEBUGBOX:
00368       w->ShowNewGRFInspectWindow();
00369       break;
00370 
00371     case WWT_SHADEBOX:
00372       nw->SetDirty(w);
00373       w->SetShaded(!w->IsShaded());
00374       return;
00375 
00376     case WWT_STICKYBOX:
00377       w->flags4 ^= WF_STICKY;
00378       nw->SetDirty(w);
00379       return;
00380 
00381     default:
00382       break;
00383   }
00384 
00385   /* Widget has no index, so the window is not interested in it. */
00386   if (widget_index < 0) return;
00387 
00388   Point pt = { x, y };
00389   w->OnClick(pt, widget_index, click_count);
00390 }
00391 
00398 static void DispatchRightClickEvent(Window *w, int x, int y)
00399 {
00400   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00401   if (wid == NULL) return;
00402 
00403   /* No widget to handle, or the window is not interested in it. */
00404   if (wid->index >= 0) {
00405     Point pt = { x, y };
00406     if (w->OnRightClick(pt, wid->index)) return;
00407   }
00408 
00409   if (_settings_client.gui.hover_delay == 0 && wid->tool_tip != 0) GuiShowTooltips(w, wid->tool_tip, 0, NULL, TCC_RIGHT_CLICK);
00410 }
00411 
00418 static void DispatchHoverEvent(Window *w, int x, int y)
00419 {
00420   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00421 
00422   /* No widget to handle */
00423   if (wid == NULL) return;
00424 
00425   /* Show the tooltip if there is any */
00426   if (wid->tool_tip != 0) {
00427     GuiShowTooltips(w, wid->tool_tip);
00428     return;
00429   }
00430 
00431   /* Widget has no index, so the window is not interested in it. */
00432   if (wid->index < 0) return;
00433 
00434   Point pt = { x, y };
00435   w->OnHover(pt, wid->index);
00436 }
00437 
00445 static void DispatchMouseWheelEvent(Window *w, const NWidgetCore *nwid, int wheel)
00446 {
00447   if (nwid == NULL) return;
00448 
00449   /* Using wheel on caption/shade-box shades or unshades the window. */
00450   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
00451     w->SetShaded(wheel < 0);
00452     return;
00453   }
00454 
00455   /* Scroll the widget attached to the scrollbar. */
00456   Scrollbar *sb = (nwid->scrollbar_index >= 0 ? w->GetScrollbar(nwid->scrollbar_index) : NULL);
00457   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
00458     sb->UpdatePosition(wheel);
00459     w->SetDirty();
00460   }
00461 }
00462 
00475 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
00476 {
00477   const Window *v;
00478   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
00479     if (right > v->left &&
00480         bottom > v->top &&
00481         left < v->left + v->width &&
00482         top < v->top + v->height) {
00483       /* v and rectangle intersect with eeach other */
00484       int x;
00485 
00486       if (left < (x = v->left)) {
00487         DrawOverlappedWindow(w, left, top, x, bottom);
00488         DrawOverlappedWindow(w, x, top, right, bottom);
00489         return;
00490       }
00491 
00492       if (right > (x = v->left + v->width)) {
00493         DrawOverlappedWindow(w, left, top, x, bottom);
00494         DrawOverlappedWindow(w, x, top, right, bottom);
00495         return;
00496       }
00497 
00498       if (top < (x = v->top)) {
00499         DrawOverlappedWindow(w, left, top, right, x);
00500         DrawOverlappedWindow(w, left, x, right, bottom);
00501         return;
00502       }
00503 
00504       if (bottom > (x = v->top + v->height)) {
00505         DrawOverlappedWindow(w, left, top, right, x);
00506         DrawOverlappedWindow(w, left, x, right, bottom);
00507         return;
00508       }
00509 
00510       return;
00511     }
00512   }
00513 
00514   /* Setup blitter, and dispatch a repaint event to window *wz */
00515   DrawPixelInfo *dp = _cur_dpi;
00516   dp->width = right - left;
00517   dp->height = bottom - top;
00518   dp->left = left - w->left;
00519   dp->top = top - w->top;
00520   dp->pitch = _screen.pitch;
00521   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
00522   dp->zoom = ZOOM_LVL_NORMAL;
00523   w->OnPaint();
00524 }
00525 
00534 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
00535 {
00536   Window *w;
00537   DrawPixelInfo bk;
00538   _cur_dpi = &bk;
00539 
00540   FOR_ALL_WINDOWS_FROM_BACK(w) {
00541     if (right > w->left &&
00542         bottom > w->top &&
00543         left < w->left + w->width &&
00544         top < w->top + w->height) {
00545       /* Window w intersects with the rectangle => needs repaint */
00546       DrawOverlappedWindow(w, left, top, right, bottom);
00547     }
00548   }
00549 }
00550 
00555 void Window::SetDirty() const
00556 {
00557   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
00558 }
00559 
00566 void Window::ReInit(int rx, int ry)
00567 {
00568   this->SetDirty(); // Mark whole current window as dirty.
00569 
00570   /* Save current size. */
00571   int window_width  = this->width;
00572   int window_height = this->height;
00573 
00574   this->OnInit();
00575   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
00576   this->nested_root->SetupSmallestSize(this, false);
00577   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
00578   this->width  = this->nested_root->smallest_x;
00579   this->height = this->nested_root->smallest_y;
00580   this->resize.step_width  = this->nested_root->resize_x;
00581   this->resize.step_height = this->nested_root->resize_y;
00582 
00583   /* Resize as close to the original size + requested resize as possible. */
00584   window_width  = max(window_width  + rx, this->width);
00585   window_height = max(window_height + ry, this->height);
00586   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
00587   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
00588   /* dx and dy has to go by step.. calculate it.
00589    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
00590   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
00591   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
00592 
00593   ResizeWindow(this, dx, dy);
00594   /* ResizeWindow() does this->SetDirty() already, no need to do it again here. */
00595 }
00596 
00602 void Window::SetShaded(bool make_shaded)
00603 {
00604   if (this->shade_select == NULL) return;
00605 
00606   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
00607   if (this->shade_select->shown_plane != desired) {
00608     if (make_shaded) {
00609       this->unshaded_size.width  = this->width;
00610       this->unshaded_size.height = this->height;
00611       this->shade_select->SetDisplayedPlane(desired);
00612       this->ReInit(0, -this->height);
00613     } else {
00614       this->shade_select->SetDisplayedPlane(desired);
00615       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
00616       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
00617       this->ReInit(dx, dy);
00618     }
00619   }
00620 }
00621 
00628 static Window *FindChildWindow(const Window *w, WindowClass wc)
00629 {
00630   Window *v;
00631   FOR_ALL_WINDOWS_FROM_BACK(v) {
00632     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
00633   }
00634 
00635   return NULL;
00636 }
00637 
00642 void Window::DeleteChildWindows(WindowClass wc) const
00643 {
00644   Window *child = FindChildWindow(this, wc);
00645   while (child != NULL) {
00646     delete child;
00647     child = FindChildWindow(this, wc);
00648   }
00649 }
00650 
00654 Window::~Window()
00655 {
00656   if (_thd.window_class == this->window_class &&
00657       _thd.window_number == this->window_number) {
00658     ResetObjectToPlace();
00659   }
00660 
00661   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
00662   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
00663 
00664   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
00665   if (_focused_window == this) _focused_window = NULL;
00666 
00667   this->DeleteChildWindows();
00668 
00669   if (this->viewport != NULL) DeleteWindowViewport(this);
00670 
00671   this->SetDirty();
00672 
00673   free(this->nested_array); // Contents is released through deletion of #nested_root.
00674   delete this->nested_root;
00675 
00676   this->window_class = WC_INVALID;
00677 }
00678 
00685 Window *FindWindowById(WindowClass cls, WindowNumber number)
00686 {
00687   Window *w;
00688   FOR_ALL_WINDOWS_FROM_BACK(w) {
00689     if (w->window_class == cls && w->window_number == number) return w;
00690   }
00691 
00692   return NULL;
00693 }
00694 
00701 Window *FindWindowByClass(WindowClass cls)
00702 {
00703   Window *w;
00704   FOR_ALL_WINDOWS_FROM_BACK(w) {
00705     if (w->window_class == cls) return w;
00706   }
00707 
00708   return NULL;
00709 }
00710 
00717 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
00718 {
00719   Window *w = FindWindowById(cls, number);
00720   if (force || w == NULL ||
00721       (w->flags4 & WF_STICKY) == 0) {
00722     delete w;
00723   }
00724 }
00725 
00730 void DeleteWindowByClass(WindowClass cls)
00731 {
00732   Window *w;
00733 
00734 restart_search:
00735   /* When we find the window to delete, we need to restart the search
00736    * as deleting this window could cascade in deleting (many) others
00737    * anywhere in the z-array */
00738   FOR_ALL_WINDOWS_FROM_BACK(w) {
00739     if (w->window_class == cls) {
00740       delete w;
00741       goto restart_search;
00742     }
00743   }
00744 }
00745 
00752 void DeleteCompanyWindows(CompanyID id)
00753 {
00754   Window *w;
00755 
00756 restart_search:
00757   /* When we find the window to delete, we need to restart the search
00758    * as deleting this window could cascade in deleting (many) others
00759    * anywhere in the z-array */
00760   FOR_ALL_WINDOWS_FROM_BACK(w) {
00761     if (w->owner == id) {
00762       delete w;
00763       goto restart_search;
00764     }
00765   }
00766 
00767   /* Also delete the company specific windows that don't have a company-colour. */
00768   DeleteWindowById(WC_BUY_COMPANY, id);
00769 }
00770 
00778 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
00779 {
00780   Window *w;
00781   FOR_ALL_WINDOWS_FROM_BACK(w) {
00782     if (w->owner != old_owner) continue;
00783 
00784     switch (w->window_class) {
00785       case WC_COMPANY_COLOUR:
00786       case WC_FINANCES:
00787       case WC_STATION_LIST:
00788       case WC_TRAINS_LIST:
00789       case WC_ROADVEH_LIST:
00790       case WC_SHIPS_LIST:
00791       case WC_AIRCRAFT_LIST:
00792       case WC_BUY_COMPANY:
00793       case WC_COMPANY:
00794         continue;
00795 
00796       default:
00797         w->owner = new_owner;
00798         break;
00799     }
00800   }
00801 }
00802 
00803 static void BringWindowToFront(Window *w);
00804 
00812 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
00813 {
00814   Window *w = FindWindowById(cls, number);
00815 
00816   if (w != NULL) {
00817     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
00818 
00819     w->flags4 |= WF_WHITE_BORDER_MASK;
00820     BringWindowToFront(w);
00821     w->SetDirty();
00822   }
00823 
00824   return w;
00825 }
00826 
00827 static inline bool IsVitalWindow(const Window *w)
00828 {
00829   switch (w->window_class) {
00830     case WC_MAIN_TOOLBAR:
00831     case WC_STATUS_BAR:
00832     case WC_NEWS_WINDOW:
00833     case WC_SEND_NETWORK_MSG:
00834       return true;
00835 
00836     default:
00837       return false;
00838   }
00839 }
00840 
00850 static void BringWindowToFront(Window *w)
00851 {
00852   Window *v = _z_front_window;
00853 
00854   /* Bring the window just below the vital windows */
00855   for (; v != NULL && v != w && IsVitalWindow(v); v = v->z_back) { }
00856 
00857   if (v == NULL || w == v) return; // window is already in the right position
00858 
00859   /* w cannot be at the top already! */
00860   assert(w != _z_front_window);
00861 
00862   if (w->z_back == NULL) {
00863     _z_back_window = w->z_front;
00864   } else {
00865     w->z_back->z_front = w->z_front;
00866   }
00867   w->z_front->z_back = w->z_back;
00868 
00869   w->z_front = v->z_front;
00870   w->z_back = v;
00871 
00872   if (v->z_front == NULL) {
00873     _z_front_window = w;
00874   } else {
00875     v->z_front->z_back = w;
00876   }
00877   v->z_front = w;
00878 
00879   w->SetDirty();
00880 }
00881 
00890 void Window::InitializeData(const WindowDesc *desc, WindowNumber window_number)
00891 {
00892   /* Set up window properties; some of them are needed to set up smallest size below */
00893   this->window_class = desc->cls;
00894   this->flags4 |= WF_WHITE_BORDER_MASK; // just opened windows have a white border
00895   if (desc->default_pos == WDP_CENTER) this->flags4 |= WF_CENTERED;
00896   this->owner = INVALID_OWNER;
00897   this->nested_focus = NULL;
00898   this->window_number = window_number;
00899   this->desc_flags = desc->flags;
00900 
00901   this->OnInit();
00902   /* Initialize nested widget tree. */
00903   if (this->nested_array == NULL) {
00904     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
00905     this->nested_root->SetupSmallestSize(this, true);
00906   } else {
00907     this->nested_root->SetupSmallestSize(this, false);
00908   }
00909   /* Initialize to smallest size. */
00910   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
00911 
00912   /* Further set up window properties,
00913    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
00914   this->resize.step_width  = this->nested_root->resize_x;
00915   this->resize.step_height = this->nested_root->resize_y;
00916 
00917   /* Give focus to the opened window unless it is the OSK window or a text box
00918    * of focused window has focus (so we don't interrupt typing). But if the new
00919    * window has a text box, then take focus anyway. */
00920   if (this->window_class != WC_OSK && (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL)) SetFocusedWindow(this);
00921 
00922   /* Hacky way of specifying always-on-top windows. These windows are
00923    * always above other windows because they are moved below them.
00924    * status-bar is above news-window because it has been created earlier.
00925    * Also, as the chat-window is excluded from this, it will always be
00926    * the last window, thus always on top.
00927    * XXX - Yes, ugly, probably needs something like w->always_on_top flag
00928    * to implement correctly, but even then you need some kind of distinction
00929    * between on-top of chat/news and status windows, because these conflict */
00930   Window *w = _z_front_window;
00931   if (w != NULL && this->window_class != WC_SEND_NETWORK_MSG && this->window_class != WC_HIGHSCORE && this->window_class != WC_ENDSCREEN) {
00932     if (FindWindowById(WC_MAIN_TOOLBAR, 0)     != NULL) w = w->z_back;
00933     if (FindWindowById(WC_STATUS_BAR, 0)       != NULL) w = w->z_back;
00934     if (FindWindowById(WC_NEWS_WINDOW, 0)      != NULL) w = w->z_back;
00935     if (FindWindowByClass(WC_SEND_NETWORK_MSG) != NULL) w = w->z_back;
00936 
00937     if (w == NULL) {
00938       _z_back_window->z_front = this;
00939       this->z_back = _z_back_window;
00940       _z_back_window = this;
00941     } else {
00942       if (w->z_front == NULL) {
00943         _z_front_window = this;
00944       } else {
00945         this->z_front = w->z_front;
00946         w->z_front->z_back = this;
00947       }
00948 
00949       this->z_back = w;
00950       w->z_front = this;
00951     }
00952   } else {
00953     this->z_back = _z_front_window;
00954     if (_z_front_window != NULL) {
00955       _z_front_window->z_front = this;
00956     } else {
00957       _z_back_window = this;
00958     }
00959     _z_front_window = this;
00960   }
00961 }
00962 
00970 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
00971 {
00972   this->left = x;
00973   this->top = y;
00974   this->width = sm_width;
00975   this->height = sm_height;
00976 }
00977 
00988 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
00989 {
00990   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
00991   def_height = max(def_height, this->height);
00992   /* Try to make windows smaller when our window is too small.
00993    * w->(width|height) is normally the same as min_(width|height),
00994    * but this way the GUIs can be made a little more dynamic;
00995    * one can use the same spec for multiple windows and those
00996    * can then determine the real minimum size of the window. */
00997   if (this->width != def_width || this->height != def_height) {
00998     /* Think about the overlapping toolbars when determining the minimum window size */
00999     int free_height = _screen.height;
01000     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
01001     if (wt != NULL) free_height -= wt->height;
01002     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01003     if (wt != NULL) free_height -= wt->height;
01004 
01005     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
01006     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
01007 
01008     /* X and Y has to go by step.. calculate it.
01009      * The cast to int is necessary else x/y are implicitly casted to
01010      * unsigned int, which won't work. */
01011     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
01012     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
01013 
01014     ResizeWindow(this, enlarge_x, enlarge_y);
01015     /* ResizeWindow() calls this->OnResize(). */
01016   } else {
01017     /* Always call OnResize; that way the scrollbars and matrices get initialized. */
01018     this->OnResize();
01019   }
01020 
01021   int nx = this->left;
01022   int ny = this->top;
01023 
01024   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
01025 
01026   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01027   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
01028   nx = max(nx, 0);
01029 
01030   if (this->viewport != NULL) {
01031     this->viewport->left += nx - this->left;
01032     this->viewport->top  += ny - this->top;
01033   }
01034   this->left = nx;
01035   this->top = ny;
01036 
01037   this->SetDirty();
01038 }
01039 
01051 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
01052 {
01053   int right  = width + left;
01054   int bottom = height + top;
01055 
01056   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01057   if (left < 0 || (main_toolbar != NULL && top < main_toolbar->height) || right > _screen.width || bottom > _screen.height) return false;
01058 
01059   /* Make sure it is not obscured by any window. */
01060   const Window *w;
01061   FOR_ALL_WINDOWS_FROM_BACK(w) {
01062     if (w->window_class == WC_MAIN_WINDOW) continue;
01063 
01064     if (right > w->left &&
01065         w->left + w->width > left &&
01066         bottom > w->top &&
01067         w->top + w->height > top) {
01068       return false;
01069     }
01070   }
01071 
01072   pos.x = left;
01073   pos.y = top;
01074   return true;
01075 }
01076 
01088 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
01089 {
01090   /* Left part of the rectangle may be at most 1/4 off-screen,
01091    * right part of the rectangle may be at most 1/2 off-screen
01092    */
01093   if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
01094   /* Bottom part of the rectangle may be at most 1/4 off-screen */
01095   if (top < 22 || top > _screen.height - (height >> 2)) return false;
01096 
01097   /* Make sure it is not obscured by any window. */
01098   const Window *w;
01099   FOR_ALL_WINDOWS_FROM_BACK(w) {
01100     if (w->window_class == WC_MAIN_WINDOW) continue;
01101 
01102     if (left + width > w->left &&
01103         w->left + w->width > left &&
01104         top + height > w->top &&
01105         w->top + w->height > top) {
01106       return false;
01107     }
01108   }
01109 
01110   pos.x = left;
01111   pos.y = top;
01112   return true;
01113 }
01114 
01121 static Point GetAutoPlacePosition(int width, int height)
01122 {
01123   Point pt;
01124 
01125   /* First attempt, try top-left of the screen */
01126   const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01127   if (IsGoodAutoPlace1(0, main_toolbar != NULL ? main_toolbar->height + 2 : 2, width, height, pt)) return pt;
01128 
01129   /* Second attempt, try around all existing windows with a distance of 2 pixels.
01130    * The new window must be entirely on-screen, and not overlap with an existing window.
01131    * Eight starting points are tried, two at each corner.
01132    */
01133   const Window *w;
01134   FOR_ALL_WINDOWS_FROM_BACK(w) {
01135     if (w->window_class == WC_MAIN_WINDOW) continue;
01136 
01137     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01138     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
01139     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01140     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
01141     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
01142     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
01143     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
01144     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
01145   }
01146 
01147   /* Third attempt, try around all existing windows with a distance of 2 pixels.
01148    * The new window may be partly off-screen, and must not overlap with an existing window.
01149    * Only four starting points are tried.
01150    */
01151   FOR_ALL_WINDOWS_FROM_BACK(w) {
01152     if (w->window_class == WC_MAIN_WINDOW) continue;
01153 
01154     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01155     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
01156     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01157     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
01158   }
01159 
01160   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
01161    * of (+5, +5)
01162    */
01163   int left = 0, top = 24;
01164 
01165 restart:
01166   FOR_ALL_WINDOWS_FROM_BACK(w) {
01167     if (w->left == left && w->top == top) {
01168       left += 5;
01169       top += 5;
01170       goto restart;
01171     }
01172   }
01173 
01174   pt.x = left;
01175   pt.y = top;
01176   return pt;
01177 }
01178 
01185 Point GetToolbarAlignedWindowPosition(int window_width)
01186 {
01187   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01188   assert(w != NULL);
01189   Point pt = { _current_text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
01190   return pt;
01191 }
01192 
01210 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01211 {
01212   Point pt;
01213   const Window *w;
01214 
01215   int16 default_width  = max(desc->default_width,  sm_width);
01216   int16 default_height = max(desc->default_height, sm_height);
01217 
01218   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
01219       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
01220       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
01221 
01222     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
01223     if (pt.x > _screen.width + 10 - default_width) {
01224       pt.x = (_screen.width + 10 - default_width) - 20;
01225     }
01226     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
01227     return pt;
01228   }
01229 
01230   switch (desc->default_pos) {
01231     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
01232       return GetToolbarAlignedWindowPosition(default_width);
01233 
01234     case WDP_AUTO: // Find a good automatic position for the window
01235       return GetAutoPlacePosition(default_width, default_height);
01236 
01237     case WDP_CENTER: // Centre the window horizontally
01238       pt.x = (_screen.width - default_width) / 2;
01239       pt.y = (_screen.height - default_height) / 2;
01240       break;
01241 
01242     case WDP_MANUAL:
01243       pt.x = 0;
01244       pt.y = 0;
01245       break;
01246 
01247     default:
01248       NOT_REACHED();
01249   }
01250 
01251   return pt;
01252 }
01253 
01254 /* virtual */ Point Window::OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01255 {
01256   return LocalGetWindowPlacement(desc, sm_width, sm_height, window_number);
01257 }
01258 
01267 void Window::CreateNestedTree(const WindowDesc *desc, bool fill_nested)
01268 {
01269   int biggest_index = -1;
01270   this->nested_root = MakeWindowNWidgetTree(desc->nwid_parts, desc->nwid_length, &biggest_index, &this->shade_select);
01271   this->nested_array_size = (uint)(biggest_index + 1);
01272 
01273   if (fill_nested) {
01274     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01275     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
01276   }
01277 }
01278 
01284 void Window::FinishInitNested(const WindowDesc *desc, WindowNumber window_number)
01285 {
01286   this->InitializeData(desc, window_number);
01287   Point pt = this->OnInitialPosition(desc, this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
01288   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
01289   this->FindWindowPlacementAndResize(desc->default_width, desc->default_height);
01290 }
01291 
01297 void Window::InitNested(const WindowDesc *desc, WindowNumber window_number)
01298 {
01299   this->CreateNestedTree(desc, false);
01300   this->FinishInitNested(desc, window_number);
01301 }
01302 
01304 Window::Window() : scrolling_scrollbar(-1)
01305 {
01306 }
01307 
01315 Window *FindWindowFromPt(int x, int y)
01316 {
01317   Window *w;
01318   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01319     if (IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
01320       return w;
01321     }
01322   }
01323 
01324   return NULL;
01325 }
01326 
01330 void InitWindowSystem()
01331 {
01332   IConsoleClose();
01333 
01334   _z_back_window = NULL;
01335   _z_front_window = NULL;
01336   _focused_window = NULL;
01337   _mouseover_last_w = NULL;
01338   _scrolling_viewport = false;
01339   _mouse_hovering = false;
01340 
01341   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
01342 }
01343 
01347 void UnInitWindowSystem()
01348 {
01349   Window *w;
01350   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
01351 
01352   for (w = _z_front_window; w != NULL; /* nothing */) {
01353     Window *to_del = w;
01354     w = w->z_back;
01355     free(to_del);
01356   }
01357 
01358   _z_front_window = NULL;
01359   _z_back_window = NULL;
01360 }
01361 
01365 void ResetWindowSystem()
01366 {
01367   UnInitWindowSystem();
01368   InitWindowSystem();
01369   _thd.Reset();
01370 }
01371 
01372 static void DecreaseWindowCounters()
01373 {
01374   Window *w;
01375   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01376     if (_scroller_click_timeout == 0) {
01377       /* Unclick scrollbar buttons if they are pressed. */
01378       for (uint i = 0; i < w->nested_array_size; i++) {
01379         NWidgetBase *nwid = w->nested_array[i];
01380         if (nwid != NULL && (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR)) {
01381           NWidgetScrollbar *sb = static_cast<NWidgetScrollbar*>(nwid);
01382           if (sb->disp_flags & (ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN)) {
01383             sb->disp_flags &= ~(ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN);
01384             sb->SetDirty(w);
01385           }
01386         }
01387       }
01388     }
01389     w->OnMouseLoop();
01390   }
01391 
01392   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01393     if ((w->flags4 & WF_TIMEOUT_MASK) && !(--w->flags4 & WF_TIMEOUT_MASK)) {
01394       w->OnTimeout();
01395       if (w->desc_flags & WDF_UNCLICK_BUTTONS) w->RaiseButtons(true);
01396     }
01397   }
01398 }
01399 
01400 static void HandlePlacePresize()
01401 {
01402   if (_special_mouse_mode != WSM_PRESIZE) return;
01403 
01404   Window *w = _thd.GetCallbackWnd();
01405   if (w == NULL) return;
01406 
01407   Point pt = GetTileBelowCursor();
01408   if (pt.x == -1) {
01409     _thd.selend.x = -1;
01410     return;
01411   }
01412 
01413   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
01414 }
01415 
01420 static EventState HandleDragDrop()
01421 {
01422   if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
01423   if (_left_button_down) return ES_HANDLED;
01424 
01425   Window *w = _thd.GetCallbackWnd();
01426 
01427   if (w != NULL) {
01428     /* send an event in client coordinates. */
01429     Point pt;
01430     pt.x = _cursor.pos.x - w->left;
01431     pt.y = _cursor.pos.y - w->top;
01432     w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
01433   }
01434 
01435   ResetObjectToPlace();
01436 
01437   return ES_HANDLED;
01438 }
01439 
01444 static EventState HandleMouseDrag()
01445 {
01446   if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
01447   if (!_left_button_down || (_cursor.delta.x == 0 && _cursor.delta.y == 0)) return ES_NOT_HANDLED;
01448 
01449   Window *w = _thd.GetCallbackWnd();
01450 
01451   if (w != NULL) {
01452     /* Send an event in client coordinates. */
01453     Point pt;
01454     pt.x = _cursor.pos.x - w->left;
01455     pt.y = _cursor.pos.y - w->top;
01456     w->OnMouseDrag(pt, GetWidgetFromPos(w, pt.x, pt.y));
01457   }
01458 
01459   return ES_HANDLED;
01460 }
01461 
01463 static void HandleMouseOver()
01464 {
01465   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01466 
01467   /* We changed window, put a MOUSEOVER event to the last window */
01468   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
01469     /* Reset mouse-over coordinates of previous window */
01470     Point pt = { -1, -1 };
01471     _mouseover_last_w->OnMouseOver(pt, 0);
01472   }
01473 
01474   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
01475   _mouseover_last_w = w;
01476 
01477   if (w != NULL) {
01478     /* send an event in client coordinates. */
01479     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
01480     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
01481     if (widget != NULL) w->OnMouseOver(pt, widget->index);
01482   }
01483 }
01484 
01486 static const int MIN_VISIBLE_TITLE_BAR = 13;
01487 
01489 enum PreventHideDirection {
01490   PHD_UP,   
01491   PHD_DOWN, 
01492 };
01493 
01504 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
01505 {
01506   if (v == NULL) return;
01507 
01508   int v_bottom = v->top + v->height;
01509   int v_right = v->left + v->width;
01510   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.
01511 
01512   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
01513   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
01514 
01515   /* Vertically, the rectangle is hidden behind v. */
01516   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
01517     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
01518     return;
01519   }
01520   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
01521     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
01522     return;
01523   }
01524 
01525   /* Horizontally also hidden, force movement to a safe area. */
01526   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
01527     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
01528   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
01529     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
01530   } else {
01531     *ny = safe_y;
01532   }
01533 }
01534 
01542 static void EnsureVisibleCaption(Window *w, int nx, int ny)
01543 {
01544   /* Search for the title bar rectangle. */
01545   Rect caption_rect;
01546   const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
01547   if (caption != NULL) {
01548     caption_rect.left   = caption->pos_x;
01549     caption_rect.right  = caption->pos_x + caption->current_x;
01550     caption_rect.top    = caption->pos_y;
01551     caption_rect.bottom = caption->pos_y + caption->current_y;
01552 
01553     /* Make sure the window doesn't leave the screen */
01554     nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
01555     ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
01556 
01557     /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
01558     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
01559     PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
01560 
01561     if (w->viewport != NULL) {
01562       w->viewport->left += nx - w->left;
01563       w->viewport->top  += ny - w->top;
01564     }
01565   }
01566   w->left = nx;
01567   w->top  = ny;
01568 }
01569 
01579 void ResizeWindow(Window *w, int delta_x, int delta_y)
01580 {
01581   if (delta_x != 0 || delta_y != 0) {
01582     w->SetDirty();
01583 
01584     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);
01585     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);
01586     assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
01587     assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
01588 
01589     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);
01590     w->width  = w->nested_root->current_x;
01591     w->height = w->nested_root->current_y;
01592   }
01593 
01594   EnsureVisibleCaption(w, w->left, w->top);
01595 
01596   /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */
01597   w->OnResize();
01598   w->SetDirty();
01599 }
01600 
01606 int GetMainViewTop()
01607 {
01608   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01609   return (w == NULL) ? 0 : w->top + w->height;
01610 }
01611 
01617 int GetMainViewBottom()
01618 {
01619   Window *w = FindWindowById(WC_STATUS_BAR, 0);
01620   return (w == NULL) ? _screen.height : w->top;
01621 }
01622 
01623 static bool _dragging_window; 
01624 
01629 static EventState HandleWindowDragging()
01630 {
01631   /* Get out immediately if no window is being dragged at all. */
01632   if (!_dragging_window) return ES_NOT_HANDLED;
01633 
01634   /* If button still down, but cursor hasn't moved, there is nothing to do. */
01635   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
01636 
01637   /* Otherwise find the window... */
01638   Window *w;
01639   FOR_ALL_WINDOWS_FROM_BACK(w) {
01640     if (w->flags4 & WF_DRAGGING) {
01641       /* Stop the dragging if the left mouse button was released */
01642       if (!_left_button_down) {
01643         w->flags4 &= ~WF_DRAGGING;
01644         break;
01645       }
01646 
01647       w->SetDirty();
01648 
01649       int x = _cursor.pos.x + _drag_delta.x;
01650       int y = _cursor.pos.y + _drag_delta.y;
01651       int nx = x;
01652       int ny = y;
01653 
01654       if (_settings_client.gui.window_snap_radius != 0) {
01655         const Window *v;
01656 
01657         int hsnap = _settings_client.gui.window_snap_radius;
01658         int vsnap = _settings_client.gui.window_snap_radius;
01659         int delta;
01660 
01661         FOR_ALL_WINDOWS_FROM_BACK(v) {
01662           if (v == w) continue; // Don't snap at yourself
01663 
01664           if (y + w->height > v->top && y < v->top + v->height) {
01665             /* Your left border <-> other right border */
01666             delta = abs(v->left + v->width - x);
01667             if (delta <= hsnap) {
01668               nx = v->left + v->width;
01669               hsnap = delta;
01670             }
01671 
01672             /* Your right border <-> other left border */
01673             delta = abs(v->left - x - w->width);
01674             if (delta <= hsnap) {
01675               nx = v->left - w->width;
01676               hsnap = delta;
01677             }
01678           }
01679 
01680           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
01681             /* Your left border <-> other left border */
01682             delta = abs(v->left - x);
01683             if (delta <= hsnap) {
01684               nx = v->left;
01685               hsnap = delta;
01686             }
01687 
01688             /* Your right border <-> other right border */
01689             delta = abs(v->left + v->width - x - w->width);
01690             if (delta <= hsnap) {
01691               nx = v->left + v->width - w->width;
01692               hsnap = delta;
01693             }
01694           }
01695 
01696           if (x + w->width > v->left && x < v->left + v->width) {
01697             /* Your top border <-> other bottom border */
01698             delta = abs(v->top + v->height - y);
01699             if (delta <= vsnap) {
01700               ny = v->top + v->height;
01701               vsnap = delta;
01702             }
01703 
01704             /* Your bottom border <-> other top border */
01705             delta = abs(v->top - y - w->height);
01706             if (delta <= vsnap) {
01707               ny = v->top - w->height;
01708               vsnap = delta;
01709             }
01710           }
01711 
01712           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
01713             /* Your top border <-> other top border */
01714             delta = abs(v->top - y);
01715             if (delta <= vsnap) {
01716               ny = v->top;
01717               vsnap = delta;
01718             }
01719 
01720             /* Your bottom border <-> other bottom border */
01721             delta = abs(v->top + v->height - y - w->height);
01722             if (delta <= vsnap) {
01723               ny = v->top + v->height - w->height;
01724               vsnap = delta;
01725             }
01726           }
01727         }
01728       }
01729 
01730       EnsureVisibleCaption(w, nx, ny);
01731 
01732       w->SetDirty();
01733       return ES_HANDLED;
01734     } else if (w->flags4 & WF_SIZING) {
01735       /* Stop the sizing if the left mouse button was released */
01736       if (!_left_button_down) {
01737         w->flags4 &= ~WF_SIZING;
01738         w->SetDirty();
01739         break;
01740       }
01741 
01742       /* Compute difference in pixels between cursor position and reference point in the window.
01743        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
01744        */
01745       int x, y = _cursor.pos.y - _drag_delta.y;
01746       if (w->flags4 & WF_SIZING_LEFT) {
01747         x = _drag_delta.x - _cursor.pos.x;
01748       } else {
01749         x = _cursor.pos.x - _drag_delta.x;
01750       }
01751 
01752       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
01753       if (w->resize.step_width  == 0) x = 0;
01754       if (w->resize.step_height == 0) y = 0;
01755 
01756       /* Check the resize button won't go past the bottom of the screen */
01757       if (w->top + w->height + y > _screen.height) {
01758         y = _screen.height - w->height - w->top;
01759       }
01760 
01761       /* X and Y has to go by step.. calculate it.
01762        * The cast to int is necessary else x/y are implicitly casted to
01763        * unsigned int, which won't work. */
01764       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
01765       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
01766 
01767       /* Check that we don't go below the minimum set size */
01768       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
01769         x = w->nested_root->smallest_x - w->width;
01770       }
01771       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
01772         y = w->nested_root->smallest_y - w->height;
01773       }
01774 
01775       /* Window already on size */
01776       if (x == 0 && y == 0) return ES_HANDLED;
01777 
01778       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
01779       _drag_delta.y += y;
01780       if ((w->flags4 & WF_SIZING_LEFT) && x != 0) {
01781         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
01782         w->SetDirty();
01783         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
01784         /* ResizeWindow() below ensures marking new position as dirty. */
01785       } else {
01786         _drag_delta.x += x;
01787       }
01788 
01789       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
01790       ResizeWindow(w, x, y);
01791       return ES_HANDLED;
01792     }
01793   }
01794 
01795   _dragging_window = false;
01796   return ES_HANDLED;
01797 }
01798 
01803 static void StartWindowDrag(Window *w)
01804 {
01805   w->flags4 |= WF_DRAGGING;
01806   w->flags4 &= ~WF_CENTERED;
01807   _dragging_window = true;
01808 
01809   _drag_delta.x = w->left - _cursor.pos.x;
01810   _drag_delta.y = w->top  - _cursor.pos.y;
01811 
01812   BringWindowToFront(w);
01813   DeleteWindowById(WC_DROPDOWN_MENU, 0);
01814 }
01815 
01821 static void StartWindowSizing(Window *w, bool to_left)
01822 {
01823   w->flags4 |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
01824   w->flags4 &= ~WF_CENTERED;
01825   _dragging_window = true;
01826 
01827   _drag_delta.x = _cursor.pos.x;
01828   _drag_delta.y = _cursor.pos.y;
01829 
01830   BringWindowToFront(w);
01831   DeleteWindowById(WC_DROPDOWN_MENU, 0);
01832 }
01833 
01838 static EventState HandleScrollbarScrolling()
01839 {
01840   Window *w;
01841   FOR_ALL_WINDOWS_FROM_BACK(w) {
01842     if (w->scrolling_scrollbar >= 0) {
01843       /* Abort if no button is clicked any more. */
01844       if (!_left_button_down) {
01845         w->scrolling_scrollbar = -1;
01846         w->SetDirty();
01847         return ES_HANDLED;
01848       }
01849 
01850       int i;
01851       NWidgetScrollbar *sb = w->GetWidget<NWidgetScrollbar>(w->scrolling_scrollbar);
01852       bool rtl = false;
01853 
01854       if (sb->type == NWID_HSCROLLBAR) {
01855         i = _cursor.pos.x - _cursorpos_drag_start.x;
01856         rtl = _current_text_dir == TD_RTL;
01857       } else {
01858         i = _cursor.pos.y - _cursorpos_drag_start.y;
01859       }
01860 
01861       if (sb->disp_flags & ND_SCROLLBAR_BTN) {
01862         if (_scroller_click_timeout == 1) {
01863           _scroller_click_timeout = 3;
01864           sb->UpdatePosition(rtl == HasBit(sb->disp_flags, NDB_SCROLLBAR_UP) ? 1 : -1);
01865           w->SetDirty();
01866         }
01867         return ES_HANDLED;
01868       }
01869 
01870       /* Find the item we want to move to and make sure it's inside bounds. */
01871       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
01872       if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
01873       if (pos != sb->GetPosition()) {
01874         sb->SetPosition(pos);
01875         w->SetDirty();
01876       }
01877       return ES_HANDLED;
01878     }
01879   }
01880 
01881   return ES_NOT_HANDLED;
01882 }
01883 
01888 static EventState HandleViewportScroll()
01889 {
01890   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
01891 
01892   if (!_scrolling_viewport) return ES_NOT_HANDLED;
01893 
01894   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01895 
01896   if (!(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) || w == NULL) {
01897     _cursor.fix_at = false;
01898     _scrolling_viewport = false;
01899     return ES_NOT_HANDLED;
01900   }
01901 
01902   if (w == FindWindowById(WC_MAIN_WINDOW, 0) && w->viewport->follow_vehicle != INVALID_VEHICLE) {
01903     /* If the main window is following a vehicle, then first let go of it! */
01904     const Vehicle *veh = Vehicle::Get(w->viewport->follow_vehicle);
01905     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
01906     return ES_NOT_HANDLED;
01907   }
01908 
01909   Point delta;
01910   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
01911     delta.x = -_cursor.delta.x;
01912     delta.y = -_cursor.delta.y;
01913   } else {
01914     delta.x = _cursor.delta.x;
01915     delta.y = _cursor.delta.y;
01916   }
01917 
01918   if (scrollwheel_scrolling) {
01919     /* We are using scrollwheels for scrolling */
01920     delta.x = _cursor.h_wheel;
01921     delta.y = _cursor.v_wheel;
01922     _cursor.v_wheel = 0;
01923     _cursor.h_wheel = 0;
01924   }
01925 
01926   /* Create a scroll-event and send it to the window */
01927   if (delta.x != 0 || delta.y != 0) w->OnScroll(delta);
01928 
01929   _cursor.delta.x = 0;
01930   _cursor.delta.y = 0;
01931   return ES_HANDLED;
01932 }
01933 
01944 static bool MaybeBringWindowToFront(Window *w)
01945 {
01946   bool bring_to_front = false;
01947 
01948   if (w->window_class == WC_MAIN_WINDOW ||
01949       IsVitalWindow(w) ||
01950       w->window_class == WC_TOOLTIPS ||
01951       w->window_class == WC_DROPDOWN_MENU) {
01952     return true;
01953   }
01954 
01955   /* Use unshaded window size rather than current size for shaded windows. */
01956   int w_width  = w->width;
01957   int w_height = w->height;
01958   if (w->IsShaded()) {
01959     w_width  = w->unshaded_size.width;
01960     w_height = w->unshaded_size.height;
01961   }
01962 
01963   Window *u;
01964   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
01965     /* A modal child will prevent the activation of the parent window */
01966     if (u->parent == w && (u->desc_flags & WDF_MODAL)) {
01967       u->flags4 |= WF_WHITE_BORDER_MASK;
01968       u->SetDirty();
01969       return false;
01970     }
01971 
01972     if (u->window_class == WC_MAIN_WINDOW ||
01973         IsVitalWindow(u) ||
01974         u->window_class == WC_TOOLTIPS ||
01975         u->window_class == WC_DROPDOWN_MENU) {
01976       continue;
01977     }
01978 
01979     /* Window sizes don't interfere, leave z-order alone */
01980     if (w->left + w_width <= u->left ||
01981         u->left + u->width <= w->left ||
01982         w->top  + w_height <= u->top ||
01983         u->top + u->height <= w->top) {
01984       continue;
01985     }
01986 
01987     bring_to_front = true;
01988   }
01989 
01990   if (bring_to_front) BringWindowToFront(w);
01991   return true;
01992 }
01993 
01998 void HandleKeypress(uint32 raw_key)
01999 {
02000   /* World generation is multithreaded and messes with companies.
02001    * But there is no company related window open anyway, so _current_company is not used. */
02002   assert(IsGeneratingWorld() || IsLocalCompany());
02003 
02004   /* Setup event */
02005   uint16 key     = GB(raw_key,  0, 16);
02006   uint16 keycode = GB(raw_key, 16, 16);
02007 
02008   /*
02009    * The Unicode standard defines an area called the private use area. Code points in this
02010    * area are reserved for private use and thus not portable between systems. For instance,
02011    * Apple defines code points for the arrow keys in this area, but these are only printable
02012    * on a system running OS X. We don't want these keys to show up in text fields and such,
02013    * and thus we have to clear the unicode character when we encounter such a key.
02014    */
02015   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
02016 
02017   /*
02018    * If both key and keycode is zero, we don't bother to process the event.
02019    */
02020   if (key == 0 && keycode == 0) return;
02021 
02022   /* Check if the focused window has a focused editbox */
02023   if (EditBoxInGlobalFocus()) {
02024     /* All input will in this case go to the focused window */
02025     if (_focused_window->OnKeyPress(key, keycode) == ES_HANDLED) return;
02026   }
02027 
02028   /* Call the event, start with the uppermost window, but ignore the toolbar. */
02029   Window *w;
02030   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02031     if (w->window_class == WC_MAIN_TOOLBAR) continue;
02032     if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02033   }
02034 
02035   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
02036   /* When there is no toolbar w is null, check for that */
02037   if (w != NULL && w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02038 
02039   HandleGlobalHotkeys(key, keycode);
02040 }
02041 
02045 void HandleCtrlChanged()
02046 {
02047   /* Call the event, start with the uppermost window. */
02048   Window *w;
02049   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02050     if (w->OnCTRLStateChange() == ES_HANDLED) return;
02051   }
02052 }
02053 
02060 static int _input_events_this_tick = 0;
02061 
02066 static void HandleAutoscroll()
02067 {
02068   if (_settings_client.gui.autoscroll && _game_mode != GM_MENU && !IsGeneratingWorld()) {
02069     int x = _cursor.pos.x;
02070     int y = _cursor.pos.y;
02071     Window *w = FindWindowFromPt(x, y);
02072     if (w == NULL || w->flags4 & WF_DISABLE_VP_SCROLL) return;
02073     ViewPort *vp = IsPtInWindowViewport(w, x, y);
02074     if (vp != NULL) {
02075       x -= vp->left;
02076       y -= vp->top;
02077 
02078       /* here allows scrolling in both x and y axis */
02079 #define scrollspeed 3
02080       if (x - 15 < 0) {
02081         w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
02082       } else if (15 - (vp->width - x) > 0) {
02083         w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
02084       }
02085       if (y - 15 < 0) {
02086         w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
02087       } else if (15 - (vp->height - y) > 0) {
02088         w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
02089       }
02090 #undef scrollspeed
02091     }
02092   }
02093 }
02094 
02095 enum MouseClick {
02096   MC_NONE = 0,
02097   MC_LEFT,
02098   MC_RIGHT,
02099   MC_DOUBLE_LEFT,
02100   MC_HOVER,
02101 
02102   MAX_OFFSET_DOUBLE_CLICK = 5,     
02103   TIME_BETWEEN_DOUBLE_CLICK = 500, 
02104   MAX_OFFSET_HOVER = 5,            
02105 };
02106 extern EventState VpHandlePlaceSizingDrag();
02107 
02108 static void ScrollMainViewport(int x, int y)
02109 {
02110   if (_game_mode != GM_MENU) {
02111     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
02112     assert(w);
02113 
02114     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
02115     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
02116   }
02117 }
02118 
02128 static const int8 scrollamt[16][2] = {
02129   { 0,  0}, 
02130   {-2,  0}, 
02131   { 0, -2}, 
02132   {-2, -1}, 
02133   { 2,  0}, 
02134   { 0,  0}, 
02135   { 2, -1}, 
02136   { 0, -2}, 
02137   { 0,  2}, 
02138   {-2,  1}, 
02139   { 0,  0}, 
02140   {-2,  0}, 
02141   { 2,  1}, 
02142   { 0,  2}, 
02143   { 2,  0}, 
02144   { 0,  0}, 
02145 };
02146 
02147 static void HandleKeyScrolling()
02148 {
02149   /*
02150    * Check that any of the dirkeys is pressed and that the focused window
02151    * dont has an edit-box as focused widget.
02152    */
02153   if (_dirkeys && !EditBoxInGlobalFocus()) {
02154     int factor = _shift_pressed ? 50 : 10;
02155     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
02156   }
02157 }
02158 
02159 static void MouseLoop(MouseClick click, int mousewheel)
02160 {
02161   /* World generation is multithreaded and messes with companies.
02162    * But there is no company related window open anyway, so _current_company is not used. */
02163   assert(IsGeneratingWorld() || IsLocalCompany());
02164 
02165   HandlePlacePresize();
02166   UpdateTileSelection();
02167 
02168   if (VpHandlePlaceSizingDrag()  == ES_HANDLED) return;
02169   if (HandleMouseDrag()          == ES_HANDLED) return;
02170   if (HandleDragDrop()           == ES_HANDLED) return;
02171   if (HandleWindowDragging()     == ES_HANDLED) return;
02172   if (HandleScrollbarScrolling() == ES_HANDLED) return;
02173   if (HandleViewportScroll()     == ES_HANDLED) return;
02174 
02175   HandleMouseOver();
02176 
02177   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02178   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
02179 
02180   int x = _cursor.pos.x;
02181   int y = _cursor.pos.y;
02182   Window *w = FindWindowFromPt(x, y);
02183   if (w == NULL) return;
02184 
02185   if (click != MC_HOVER && !MaybeBringWindowToFront(w)) return;
02186   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02187 
02188   /* Don't allow any action in a viewport if either in menu of in generating world */
02189   if (vp != NULL && (_game_mode == GM_MENU || IsGeneratingWorld())) return;
02190 
02191   if (mousewheel != 0) {
02192     if (_settings_client.gui.scrollwheel_scrolling == 0) {
02193       /* Send mousewheel event to window */
02194       w->OnMouseWheel(mousewheel);
02195     }
02196 
02197     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
02198     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
02199   }
02200 
02201   if (vp != NULL) {
02202     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
02203     switch (click) {
02204       case MC_DOUBLE_LEFT:
02205       case MC_LEFT:
02206         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
02207         if (!HandleViewportClicked(vp, x, y) &&
02208             !(w->flags4 & WF_DISABLE_VP_SCROLL) &&
02209             _settings_client.gui.left_mouse_btn_scrolling) {
02210           _scrolling_viewport = true;
02211           _cursor.fix_at = false;
02212         }
02213         break;
02214 
02215       case MC_RIGHT:
02216         if (!(w->flags4 & WF_DISABLE_VP_SCROLL)) {
02217           _scrolling_viewport = true;
02218           _cursor.fix_at = true;
02219 
02220           /* clear 2D scrolling caches before we start a 2D scroll */
02221           _cursor.h_wheel = 0;
02222           _cursor.v_wheel = 0;
02223         }
02224         break;
02225 
02226       default:
02227         break;
02228     }
02229   } else {
02230     switch (click) {
02231       case MC_LEFT:
02232       case MC_DOUBLE_LEFT:
02233         DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
02234         break;
02235 
02236       default:
02237         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
02238         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
02239          * Simulate a right button click so we can get started. */
02240         /* FALL THROUGH */
02241 
02242       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
02243 
02244       case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
02245     }
02246   }
02247 }
02248 
02252 void HandleMouseEvents()
02253 {
02254   /* World generation is multithreaded and messes with companies.
02255    * But there is no company related window open anyway, so _current_company is not used. */
02256   assert(IsGeneratingWorld() || IsLocalCompany());
02257 
02258   static int double_click_time = 0;
02259   static Point double_click_pos = {0, 0};
02260 
02261   /* Mouse event? */
02262   MouseClick click = MC_NONE;
02263   if (_left_button_down && !_left_button_clicked) {
02264     click = MC_LEFT;
02265     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
02266         double_click_pos.x != 0 && abs(_cursor.pos.x - double_click_pos.x) < MAX_OFFSET_DOUBLE_CLICK  &&
02267         double_click_pos.y != 0 && abs(_cursor.pos.y - double_click_pos.y) < MAX_OFFSET_DOUBLE_CLICK) {
02268       click = MC_DOUBLE_LEFT;
02269     }
02270     double_click_time = _realtime_tick;
02271     double_click_pos = _cursor.pos;
02272     _left_button_clicked = true;
02273     _input_events_this_tick++;
02274   } else if (_right_button_clicked) {
02275     _right_button_clicked = false;
02276     click = MC_RIGHT;
02277     _input_events_this_tick++;
02278   }
02279 
02280   int mousewheel = 0;
02281   if (_cursor.wheel) {
02282     mousewheel = _cursor.wheel;
02283     _cursor.wheel = 0;
02284     _input_events_this_tick++;
02285   }
02286 
02287   static uint32 hover_time = 0;
02288   static Point hover_pos = {0, 0};
02289 
02290   if (_settings_client.gui.hover_delay > 0) {
02291     if (!_cursor.in_window || click != MC_NONE || mousewheel != 0 || _left_button_down || _right_button_down ||
02292         hover_pos.x == 0 || abs(_cursor.pos.x - hover_pos.x) >= MAX_OFFSET_HOVER  ||
02293         hover_pos.y == 0 || abs(_cursor.pos.y - hover_pos.y) >= MAX_OFFSET_HOVER) {
02294       hover_pos = _cursor.pos;
02295       hover_time = _realtime_tick;
02296       _mouse_hovering = false;
02297     } else {
02298       if (hover_time != 0 && _realtime_tick > hover_time + _settings_client.gui.hover_delay * 1000) {
02299         click = MC_HOVER;
02300         _input_events_this_tick++;
02301         _mouse_hovering = true;
02302       }
02303     }
02304   }
02305 
02306   /* Handle sprite picker before any GUI interaction */
02307   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && _newgrf_debug_sprite_picker.click_time != _realtime_tick) {
02308     /* Next realtime tick? Then redraw has finished */
02309     _newgrf_debug_sprite_picker.mode = SPM_NONE;
02310     InvalidateWindowData(WC_SPRITE_ALIGNER, 0, 1);
02311   }
02312 
02313   if (click == MC_LEFT && _newgrf_debug_sprite_picker.mode == SPM_WAIT_CLICK) {
02314     /* Mark whole screen dirty, and wait for the next realtime tick, when drawing is finished. */
02315     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
02316     _newgrf_debug_sprite_picker.clicked_pixel = blitter->MoveTo(_screen.dst_ptr, _cursor.pos.x, _cursor.pos.y);
02317     _newgrf_debug_sprite_picker.click_time = _realtime_tick;
02318     _newgrf_debug_sprite_picker.sprites.Clear();
02319     _newgrf_debug_sprite_picker.mode = SPM_REDRAW;
02320     MarkWholeScreenDirty();
02321   } else {
02322     MouseLoop(click, mousewheel);
02323   }
02324 
02325   /* We have moved the mouse the required distance,
02326    * no need to move it at any later time. */
02327   _cursor.delta.x = 0;
02328   _cursor.delta.y = 0;
02329 }
02330 
02334 static void CheckSoftLimit()
02335 {
02336   if (_settings_client.gui.window_soft_limit == 0) return;
02337 
02338   for (;;) {
02339     uint deletable_count = 0;
02340     Window *w, *last_deletable = NULL;
02341     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02342       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags4 & WF_STICKY)) continue;
02343 
02344       last_deletable = w;
02345       deletable_count++;
02346     }
02347 
02348     /* We've ot reached the soft limit yet */
02349     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
02350 
02351     assert(last_deletable != NULL);
02352     delete last_deletable;
02353   }
02354 }
02355 
02359 void InputLoop()
02360 {
02361   /* World generation is multithreaded and messes with companies.
02362    * But there is no company related window open anyway, so _current_company is not used. */
02363   assert(IsGeneratingWorld() || IsLocalCompany());
02364 
02365   CheckSoftLimit();
02366   HandleKeyScrolling();
02367 
02368   /* Do the actual free of the deleted windows. */
02369   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
02370     Window *w = v;
02371     v = v->z_back;
02372 
02373     if (w->window_class != WC_INVALID) continue;
02374 
02375     /* Find the window in the z-array, and effectively remove it
02376      * by moving all windows after it one to the left. This must be
02377      * done before removing the child so we cannot cause recursion
02378      * between the deletion of the parent and the child. */
02379     if (w->z_front == NULL) {
02380       _z_front_window = w->z_back;
02381     } else {
02382       w->z_front->z_back = w->z_back;
02383     }
02384     if (w->z_back == NULL) {
02385       _z_back_window  = w->z_front;
02386     } else {
02387       w->z_back->z_front = w->z_front;
02388     }
02389     free(w);
02390   }
02391 
02392   if (_scroller_click_timeout != 0) _scroller_click_timeout--;
02393   DecreaseWindowCounters();
02394 
02395   if (_input_events_this_tick != 0) {
02396     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
02397     _input_events_this_tick = 0;
02398     /* there were some inputs this tick, don't scroll ??? */
02399     return;
02400   }
02401 
02402   /* HandleMouseEvents was already called for this tick */
02403   HandleMouseEvents();
02404   HandleAutoscroll();
02405 }
02406 
02410 void UpdateWindows()
02411 {
02412   Window *w;
02413   static int we4_timer = 0;
02414   int t = we4_timer + 1;
02415 
02416   if (t >= 100) {
02417     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02418       w->OnHundredthTick();
02419     }
02420     t = 0;
02421   }
02422   we4_timer = t;
02423 
02424   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02425     if (w->flags4 & WF_WHITE_BORDER_MASK) {
02426       w->flags4 -= WF_WHITE_BORDER_ONE;
02427 
02428       if (!(w->flags4 & WF_WHITE_BORDER_MASK)) w->SetDirty();
02429     }
02430   }
02431 
02432   DrawDirtyBlocks();
02433 
02434   FOR_ALL_WINDOWS_FROM_BACK(w) {
02435     /* Update viewport only if window is not shaded. */
02436     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
02437   }
02438   NetworkDrawChatMessage();
02439   /* Redraw mouse cursor in case it was hidden */
02440   DrawMouseCursor();
02441 }
02442 
02448 void SetWindowDirty(WindowClass cls, WindowNumber number)
02449 {
02450   const Window *w;
02451   FOR_ALL_WINDOWS_FROM_BACK(w) {
02452     if (w->window_class == cls && w->window_number == number) w->SetDirty();
02453   }
02454 }
02455 
02462 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
02463 {
02464   const Window *w;
02465   FOR_ALL_WINDOWS_FROM_BACK(w) {
02466     if (w->window_class == cls && w->window_number == number) {
02467       w->SetWidgetDirty(widget_index);
02468     }
02469   }
02470 }
02471 
02476 void SetWindowClassesDirty(WindowClass cls)
02477 {
02478   Window *w;
02479   FOR_ALL_WINDOWS_FROM_BACK(w) {
02480     if (w->window_class == cls) w->SetDirty();
02481   }
02482 }
02483 
02490 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data)
02491 {
02492   Window *w;
02493   FOR_ALL_WINDOWS_FROM_BACK(w) {
02494     if (w->window_class == cls && w->window_number == number) w->InvalidateData(data);
02495   }
02496 }
02497 
02503 void InvalidateWindowClassesData(WindowClass cls, int data)
02504 {
02505   Window *w;
02506 
02507   FOR_ALL_WINDOWS_FROM_BACK(w) {
02508     if (w->window_class == cls) w->InvalidateData(data);
02509   }
02510 }
02511 
02515 void CallWindowTickEvent()
02516 {
02517   Window *w;
02518   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02519     w->OnTick();
02520   }
02521 }
02522 
02529 void DeleteNonVitalWindows()
02530 {
02531   Window *w;
02532 
02533 restart_search:
02534   /* When we find the window to delete, we need to restart the search
02535    * as deleting this window could cascade in deleting (many) others
02536    * anywhere in the z-array */
02537   FOR_ALL_WINDOWS_FROM_BACK(w) {
02538     if (w->window_class != WC_MAIN_WINDOW &&
02539         w->window_class != WC_SELECT_GAME &&
02540         w->window_class != WC_MAIN_TOOLBAR &&
02541         w->window_class != WC_STATUS_BAR &&
02542         w->window_class != WC_TOOLBAR_MENU &&
02543         w->window_class != WC_TOOLTIPS &&
02544         (w->flags4 & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
02545 
02546       delete w;
02547       goto restart_search;
02548     }
02549   }
02550 }
02551 
02559 void DeleteAllNonVitalWindows()
02560 {
02561   Window *w;
02562 
02563   /* Delete every window except for stickied ones, then sticky ones as well */
02564   DeleteNonVitalWindows();
02565 
02566 restart_search:
02567   /* When we find the window to delete, we need to restart the search
02568    * as deleting this window could cascade in deleting (many) others
02569    * anywhere in the z-array */
02570   FOR_ALL_WINDOWS_FROM_BACK(w) {
02571     if (w->flags4 & WF_STICKY) {
02572       delete w;
02573       goto restart_search;
02574     }
02575   }
02576 }
02577 
02582 void DeleteConstructionWindows()
02583 {
02584   Window *w;
02585 
02586 restart_search:
02587   /* When we find the window to delete, we need to restart the search
02588    * as deleting this window could cascade in deleting (many) others
02589    * anywhere in the z-array */
02590   FOR_ALL_WINDOWS_FROM_BACK(w) {
02591     if (w->desc_flags & WDF_CONSTRUCTION) {
02592       delete w;
02593       goto restart_search;
02594     }
02595   }
02596 
02597   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
02598 }
02599 
02601 void HideVitalWindows()
02602 {
02603   DeleteWindowById(WC_TOOLBAR_MENU, 0);
02604   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
02605   DeleteWindowById(WC_STATUS_BAR, 0);
02606 }
02607 
02609 void ReInitAllWindows()
02610 {
02611   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
02612 
02613   Window *w;
02614   FOR_ALL_WINDOWS_FROM_BACK(w) {
02615     w->ReInit();
02616   }
02617 #ifdef ENABLE_NETWORK
02618   void NetworkReInitChatBoxSize();
02619   NetworkReInitChatBoxSize();
02620 #endif
02621 
02622   /* Make sure essential parts of all windows are visible */
02623   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
02624   MarkWholeScreenDirty();
02625 }
02626 
02634 static int PositionWindow(Window *w, WindowClass clss, int setting)
02635 {
02636   if (w == NULL || w->window_class != clss) {
02637     w = FindWindowById(clss, 0);
02638   }
02639   if (w == NULL) return 0;
02640 
02641   int old_left = w->left;
02642   switch (setting) {
02643     case 1:  w->left = (_screen.width - w->width) / 2; break;
02644     case 2:  w->left = _screen.width - w->width; break;
02645     default: w->left = 0; break;
02646   }
02647   if (w->viewport != NULL) w->viewport->left += w->left - old_left;
02648   SetDirtyBlocks(0, w->top, _screen.width, w->top + w->height); // invalidate the whole row
02649   return w->left;
02650 }
02651 
02657 int PositionMainToolbar(Window *w)
02658 {
02659   DEBUG(misc, 5, "Repositioning Main Toolbar...");
02660   return PositionWindow(w, WC_MAIN_TOOLBAR, _settings_client.gui.toolbar_pos);
02661 }
02662 
02668 int PositionStatusbar(Window *w)
02669 {
02670   DEBUG(misc, 5, "Repositioning statusbar...");
02671   return PositionWindow(w, WC_STATUS_BAR, _settings_client.gui.statusbar_pos);
02672 }
02673 
02679 int PositionNewsMessage(Window *w)
02680 {
02681   DEBUG(misc, 5, "Repositioning news message...");
02682   return PositionWindow(w, WC_NEWS_WINDOW, _settings_client.gui.statusbar_pos);
02683 }
02684 
02685 
02691 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
02692 {
02693   Window *w;
02694   FOR_ALL_WINDOWS_FROM_BACK(w) {
02695     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
02696       w->viewport->follow_vehicle = to_index;
02697       w->SetDirty();
02698     }
02699   }
02700 }
02701 
02702 
02708 void RelocateAllWindows(int neww, int newh)
02709 {
02710   Window *w;
02711 
02712   FOR_ALL_WINDOWS_FROM_BACK(w) {
02713     int left, top;
02714 
02715     if (w->window_class == WC_MAIN_WINDOW) {
02716       ViewPort *vp = w->viewport;
02717       vp->width = w->width = neww;
02718       vp->height = w->height = newh;
02719       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
02720       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
02721       continue; // don't modify top,left
02722     }
02723 
02724     /* XXX - this probably needs something more sane. For example specifying
02725      * in a 'backup'-desc that the window should always be centered. */
02726     switch (w->window_class) {
02727       case WC_MAIN_TOOLBAR:
02728         ResizeWindow(w, min(neww, *_preferred_toolbar_size) - w->width, 0);
02729 
02730         top = w->top;
02731         left = PositionMainToolbar(w); // changes toolbar orientation
02732         break;
02733 
02734       case WC_NEWS_WINDOW:
02735         top = newh - w->height;
02736         left = PositionNewsMessage(w);
02737         break;
02738 
02739       case WC_STATUS_BAR:
02740         ResizeWindow(w, min(neww, *_preferred_statusbar_size) - w->width, 0);
02741 
02742         top = newh - w->height;
02743         left = PositionStatusbar(w);
02744         break;
02745 
02746       case WC_SEND_NETWORK_MSG:
02747         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0);
02748         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
02749         left = (neww - w->width) >> 1;
02750         break;
02751 
02752       case WC_CONSOLE:
02753         IConsoleResize(w);
02754         continue;
02755 
02756       default: {
02757         if (w->flags4 & WF_CENTERED) {
02758           top = (newh - w->height) >> 1;
02759           left = (neww - w->width) >> 1;
02760           break;
02761         }
02762 
02763         left = w->left;
02764         if (left + (w->width >> 1) >= neww) left = neww - w->width;
02765         if (left < 0) left = 0;
02766 
02767         top = w->top;
02768         if (top + (w->height >> 1) >= newh) top = newh - w->height;
02769 
02770         const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
02771         if (wt != NULL) {
02772           if (top < wt->height && wt->left < (w->left + w->width) && (wt->left + wt->width) > w->left) top = wt->height;
02773           if (top >= newh) top = newh - 1;
02774         } else {
02775           if (top < 0) top = 0;
02776         }
02777         break;
02778       }
02779     }
02780 
02781     if (w->viewport != NULL) {
02782       w->viewport->left += left - w->left;
02783       w->viewport->top += top - w->top;
02784     }
02785 
02786     w->left = left;
02787     w->top = top;
02788   }
02789 }
02790 
02796 PickerWindowBase::~PickerWindowBase()
02797 {
02798   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
02799   ResetObjectToPlace();
02800 }

Generated on Sun Jan 9 16:02:05 2011 for OpenTTD by  doxygen 1.6.1