window.cpp

Go to the documentation of this file.
00001 /* $Id: window.cpp 21048 2010-10-27 20:17:45Z rubidium $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include <stdarg.h>
00014 #include "openttd.h"
00015 #include "company_func.h"
00016 #include "gfx_func.h"
00017 #include "console_func.h"
00018 #include "console_gui.h"
00019 #include "viewport_func.h"
00020 #include "variables.h"
00021 #include "genworld.h"
00022 #include "blitter/factory.hpp"
00023 #include "zoom_func.h"
00024 #include "map_func.h"
00025 #include "vehicle_base.h"
00026 #include "cheat_type.h"
00027 #include "window_func.h"
00028 #include "tilehighlight_func.h"
00029 #include "network/network.h"
00030 #include "querystring_gui.h"
00031 #include "widgets/dropdown_func.h"
00032 #include "strings_func.h"
00033 #include "settings_type.h"
00034 
00035 #include "table/sprites.h"
00036 
00037 static Point _drag_delta; 
00038 static Window *_mouseover_last_w = NULL; 
00039 
00041 Window *_z_front_window = NULL;
00043 Window *_z_back_window  = NULL;
00044 
00045 /*
00046  * Window that currently have focus. - The main purpose is to generate
00047  * FocusLost events, not to give next window in z-order focus when a
00048  * window is closed.
00049  */
00050 Window *_focused_window;
00051 
00052 Point _cursorpos_drag_start;
00053 
00054 int _scrollbar_start_pos;
00055 int _scrollbar_size;
00056 byte _scroller_click_timeout = 0;
00057 
00058 bool _scrolling_scrollbar;
00059 bool _scrolling_viewport;
00060 
00061 byte _special_mouse_mode;
00062 
00064 WindowDesc::WindowDesc(WindowPosition def_pos, int16 def_width, int16 def_height,
00065       WindowClass window_class, WindowClass parent_class, uint32 flags,
00066       const NWidgetPart *nwid_parts, int16 nwid_length) :
00067   default_pos(def_pos),
00068   default_width(def_width),
00069   default_height(def_height),
00070   cls(window_class),
00071   parent_cls(parent_class),
00072   flags(flags),
00073   nwid_parts(nwid_parts),
00074   nwid_length(nwid_length)
00075 {
00076 }
00077 
00078 WindowDesc::~WindowDesc()
00079 {
00080 }
00081 
00089 void Scrollbar::SetCapacityFromWidget(Window *w, int widget, int padding)
00090 {
00091   NWidgetBase *nwid = w->GetWidget<NWidgetBase>(widget);
00092   if (this->is_vertical) {
00093     this->SetCapacity(((int)nwid->current_y - padding) / (int)nwid->resize_y);
00094   } else {
00095     this->SetCapacity(((int)nwid->current_x - padding) / (int)nwid->resize_x);
00096   }
00097 }
00098 
00103 void SetFocusedWindow(Window *w)
00104 {
00105   if (_focused_window == w) return;
00106 
00107   /* Invalidate focused widget */
00108   if (_focused_window != NULL) {
00109     if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
00110   }
00111 
00112   /* Remember which window was previously focused */
00113   Window *old_focused = _focused_window;
00114   _focused_window = w;
00115 
00116   /* So we can inform it that it lost focus */
00117   if (old_focused != NULL) old_focused->OnFocusLost();
00118   if (_focused_window != NULL) _focused_window->OnFocus();
00119 }
00120 
00126 bool EditBoxInGlobalFocus()
00127 {
00128   if (_focused_window == NULL) return false;
00129 
00130   /* The console does not have an edit box so a special case is needed. */
00131   if (_focused_window->window_class == WC_CONSOLE) return true;
00132 
00133   return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
00134 }
00135 
00139 void Window::UnfocusFocusedWidget()
00140 {
00141   if (this->nested_focus != NULL) {
00142     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00143     this->nested_focus->SetDirty(this);
00144     this->nested_focus = NULL;
00145   }
00146 }
00147 
00153 bool Window::SetFocusedWidget(byte widget_index)
00154 {
00155   /* Do nothing if widget_index is already focused, or if it wasn't a valid widget. */
00156   if (widget_index >= this->nested_array_size) return false;
00157 
00158   assert(this->nested_array[widget_index] != NULL); // Setting focus to a non-existing widget is a bad idea.
00159   if (this->nested_focus != NULL) {
00160     if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
00161 
00162     /* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
00163     this->nested_focus->SetDirty(this);
00164   }
00165   this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
00166   return true;
00167 }
00168 
00176 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
00177 {
00178   va_list wdg_list;
00179 
00180   va_start(wdg_list, widgets);
00181 
00182   while (widgets != WIDGET_LIST_END) {
00183     SetWidgetDisabledState(widgets, disab_stat);
00184     widgets = va_arg(wdg_list, int);
00185   }
00186 
00187   va_end(wdg_list);
00188 }
00189 
00195 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
00196 {
00197   va_list wdg_list;
00198 
00199   va_start(wdg_list, widgets);
00200 
00201   while (widgets != WIDGET_LIST_END) {
00202     SetWidgetLoweredState(widgets, lowered_stat);
00203     widgets = va_arg(wdg_list, int);
00204   }
00205 
00206   va_end(wdg_list);
00207 }
00208 
00213 void Window::RaiseButtons(bool autoraise)
00214 {
00215   for (uint i = 0; i < this->nested_array_size; i++) {
00216     if (this->nested_array[i] != NULL && (this->nested_array[i]->type & ~WWB_PUSHBUTTON) < WWT_LAST &&
00217         (!autoraise || (this->nested_array[i]->type & WWB_PUSHBUTTON)) && this->IsWidgetLowered(i)) {
00218       this->RaiseWidget(i);
00219       this->SetWidgetDirty(i);
00220     }
00221   }
00222 }
00223 
00228 void Window::SetWidgetDirty(byte widget_index) const
00229 {
00230   /* Sometimes this function is called before the window is even fully initialized */
00231   if (this->nested_array == NULL) return;
00232 
00233   this->nested_array[widget_index]->SetDirty(this);
00234 }
00235 
00241 void Window::HandleButtonClick(byte widget)
00242 {
00243   this->LowerWidget(widget);
00244   this->flags4 |= WF_TIMEOUT_BEGIN;
00245   this->SetWidgetDirty(widget);
00246 }
00247 
00248 static void StartWindowDrag(Window *w);
00249 static void StartWindowSizing(Window *w, bool to_left);
00250 
00258 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
00259 {
00260   const NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
00261   WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
00262 
00263   bool focused_widget_changed = false;
00264   /* If clicked on a window that previously did dot have focus */
00265   if (_focused_window != w &&                 // We already have focus, right?
00266       (w->desc_flags & WDF_NO_FOCUS) == 0 &&  // Don't lose focus to toolbars
00267       widget_type != WWT_CLOSEBOX) {          // Don't change focused window if 'X' (close button) was clicked
00268     focused_widget_changed = true;
00269     if (_focused_window != NULL) {
00270       _focused_window->OnFocusLost();
00271 
00272       /* The window that lost focus may have had opened a OSK, window so close it, unless the user has clicked on the OSK window. */
00273       if (w->window_class != WC_OSK) DeleteWindowById(WC_OSK, 0);
00274     }
00275     SetFocusedWindow(w);
00276     w->OnFocus();
00277   }
00278 
00279   if (nw == NULL) return; // exit if clicked outside of widgets
00280 
00281   /* don't allow any interaction if the button has been disabled */
00282   if (nw->IsDisabled()) return;
00283 
00284   int widget_index = nw->index; 
00285 
00286   /* Clicked on a widget that is not disabled.
00287    * So unless the clicked widget is the caption bar, change focus to this widget */
00288   if (widget_type != WWT_CAPTION) {
00289     /* Close the OSK window if a edit box loses focus */
00290     if (w->nested_focus != NULL &&  w->nested_focus->type == WWT_EDITBOX && w->nested_focus != nw && w->window_class != WC_OSK) {
00291       DeleteWindowById(WC_OSK, 0);
00292     }
00293 
00294     /* focused_widget_changed is 'now' only true if the window this widget
00295      * is in gained focus. In that case it must remain true, also if the
00296      * local widget focus did not change. As such it's the logical-or of
00297      * both changed states.
00298      *
00299      * If this is not preserved, then the OSK window would be opened when
00300      * a user has the edit box focused and then click on another window and
00301      * then back again on the edit box (to type some text).
00302      */
00303     focused_widget_changed |= w->SetFocusedWidget(widget_index);
00304   }
00305 
00306   /* Close any child drop down menus. If the button pressed was the drop down
00307    * list's own button, then we should not process the click any further. */
00308   if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
00309 
00310   switch (widget_type) {
00311     /* special widget handling for buttons*/
00312     case WWT_PANEL   | WWB_PUSHBUTTON: // WWT_PUSHBTN
00313     case WWT_IMGBTN  | WWB_PUSHBUTTON: // WWT_PUSHIMGBTN
00314     case WWT_TEXTBTN | WWB_PUSHBUTTON: // WWT_PUSHTXTBTN
00315       w->HandleButtonClick(widget_index);
00316       break;
00317 
00318     case WWT_SCROLLBAR:
00319     case WWT_SCROLL2BAR:
00320     case WWT_HSCROLLBAR:
00321       ScrollbarClickHandler(w, nw, x, y);
00322       break;
00323 
00324     case WWT_EDITBOX:
00325       if (!focused_widget_changed) { // Only open the OSK window if clicking on an already focused edit box
00326         /* Open the OSK window if clicked on an edit box */
00327         QueryStringBaseWindow *qs = dynamic_cast<QueryStringBaseWindow *>(w);
00328         if (qs != NULL) {
00329           qs->OnOpenOSKWindow(widget_index);
00330         }
00331       }
00332       break;
00333 
00334     case WWT_CLOSEBOX: // 'X'
00335       delete w;
00336       return;
00337 
00338     case WWT_CAPTION: // 'Title bar'
00339       StartWindowDrag(w);
00340       return;
00341 
00342     case WWT_RESIZEBOX:
00343       /* When the resize widget is on the left size of the window
00344        * we assume that that button is used to resize to the left. */
00345       StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
00346       nw->SetDirty(w);
00347       return;
00348 
00349     case WWT_SHADEBOX:
00350       nw->SetDirty(w);
00351       w->SetShaded(!w->IsShaded());
00352       return;
00353 
00354     case WWT_STICKYBOX:
00355       w->flags4 ^= WF_STICKY;
00356       nw->SetDirty(w);
00357       return;
00358 
00359     default:
00360       break;
00361   }
00362 
00363   /* Widget has no index, so the window is not interested in it. */
00364   if (widget_index < 0) return;
00365 
00366   Point pt = { x, y };
00367   w->OnClick(pt, widget_index, click_count);
00368 }
00369 
00376 static void DispatchRightClickEvent(Window *w, int x, int y)
00377 {
00378   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00379 
00380   /* No widget to handle */
00381   if (wid == NULL) return;
00382 
00383   /* Show the tooltip if there is any */
00384   if (wid->tool_tip != 0) {
00385     GuiShowTooltips(wid->tool_tip);
00386     return;
00387   }
00388 
00389   /* Widget has no index, so the window is not interested in it. */
00390   if (wid->index < 0) return;
00391 
00392   Point pt = { x, y };
00393   w->OnRightClick(pt, wid->index);
00394 }
00395 
00403 static void DispatchMouseWheelEvent(Window *w, const NWidgetCore *nwid, int wheel)
00404 {
00405   if (nwid == NULL) return;
00406 
00407   /* Using wheel on caption/shade-box shades or unshades the window. */
00408   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
00409     w->SetShaded(!w->IsShaded());
00410     return;
00411   }
00412 
00413   /* Scroll the widget attached to the scrollbar. */
00414   Scrollbar *sb = nwid->FindScrollbar(w);
00415   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
00416     sb->UpdatePosition(wheel);
00417     w->SetDirty();
00418   }
00419 }
00420 
00433 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
00434 {
00435   const Window *v;
00436   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
00437     if (right > v->left &&
00438         bottom > v->top &&
00439         left < v->left + v->width &&
00440         top < v->top + v->height) {
00441       /* v and rectangle intersect with eeach other */
00442       int x;
00443 
00444       if (left < (x = v->left)) {
00445         DrawOverlappedWindow(w, left, top, x, bottom);
00446         DrawOverlappedWindow(w, x, top, right, bottom);
00447         return;
00448       }
00449 
00450       if (right > (x = v->left + v->width)) {
00451         DrawOverlappedWindow(w, left, top, x, bottom);
00452         DrawOverlappedWindow(w, x, top, right, bottom);
00453         return;
00454       }
00455 
00456       if (top < (x = v->top)) {
00457         DrawOverlappedWindow(w, left, top, right, x);
00458         DrawOverlappedWindow(w, left, x, right, bottom);
00459         return;
00460       }
00461 
00462       if (bottom > (x = v->top + v->height)) {
00463         DrawOverlappedWindow(w, left, top, right, x);
00464         DrawOverlappedWindow(w, left, x, right, bottom);
00465         return;
00466       }
00467 
00468       return;
00469     }
00470   }
00471 
00472   /* Setup blitter, and dispatch a repaint event to window *wz */
00473   DrawPixelInfo *dp = _cur_dpi;
00474   dp->width = right - left;
00475   dp->height = bottom - top;
00476   dp->left = left - w->left;
00477   dp->top = top - w->top;
00478   dp->pitch = _screen.pitch;
00479   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
00480   dp->zoom = ZOOM_LVL_NORMAL;
00481   w->OnPaint();
00482 }
00483 
00492 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
00493 {
00494   Window *w;
00495   DrawPixelInfo bk;
00496   _cur_dpi = &bk;
00497 
00498   FOR_ALL_WINDOWS_FROM_BACK(w) {
00499     if (right > w->left &&
00500         bottom > w->top &&
00501         left < w->left + w->width &&
00502         top < w->top + w->height) {
00503       /* Window w intersects with the rectangle => needs repaint */
00504       DrawOverlappedWindow(w, left, top, right, bottom);
00505     }
00506   }
00507 }
00508 
00513 void Window::SetDirty() const
00514 {
00515   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
00516 }
00517 
00523 void Window::ReInit(int rx, int ry)
00524 {
00525   this->SetDirty(); // Mark whole current window as dirty.
00526 
00527   /* Save current size. */
00528   int window_width  = this->width;
00529   int window_height = this->height;
00530 
00531   this->OnInit();
00532   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
00533   this->nested_root->SetupSmallestSize(this, false);
00534   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _dynlang.text_dir == TD_RTL);
00535   this->width  = this->nested_root->smallest_x;
00536   this->height = this->nested_root->smallest_y;
00537   this->resize.step_width  = this->nested_root->resize_x;
00538   this->resize.step_height = this->nested_root->resize_y;
00539 
00540   /* Resize as close to the original size + requested resize as possible. */
00541   window_width  = max(window_width  + rx, this->width);
00542   window_height = max(window_height + ry, this->height);
00543   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
00544   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
00545   /* dx and dy has to go by step.. calculate it.
00546    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
00547   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
00548   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
00549 
00550   ResizeWindow(this, dx, dy);
00551   /* ResizeWindow() does this->SetDirty() already, no need to do it again here. */
00552 }
00553 
00558 void Window::SetShaded(bool make_shaded)
00559 {
00560   if (this->shade_select == NULL) return;
00561 
00562   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
00563   if (this->shade_select->shown_plane != desired) {
00564     if (make_shaded) {
00565       this->unshaded_size.width  = this->width;
00566       this->unshaded_size.height = this->height;
00567       this->shade_select->SetDisplayedPlane(desired);
00568       this->ReInit(0, -this->height);
00569     } else {
00570       this->shade_select->SetDisplayedPlane(desired);
00571       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
00572       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
00573       this->ReInit(dx, dy);
00574     }
00575   }
00576 }
00577 
00583 static Window *FindChildWindow(const Window *w, WindowClass wc)
00584 {
00585   Window *v;
00586   FOR_ALL_WINDOWS_FROM_BACK(v) {
00587     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
00588   }
00589 
00590   return NULL;
00591 }
00592 
00597 void Window::DeleteChildWindows(WindowClass wc) const
00598 {
00599   Window *child = FindChildWindow(this, wc);
00600   while (child != NULL) {
00601     delete child;
00602     child = FindChildWindow(this, wc);
00603   }
00604 }
00605 
00609 Window::~Window()
00610 {
00611   if (_thd.place_mode != HT_NONE &&
00612       _thd.window_class == this->window_class &&
00613       _thd.window_number == this->window_number) {
00614     ResetObjectToPlace();
00615   }
00616 
00617   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
00618   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
00619 
00620   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
00621   if (_focused_window == this) _focused_window = NULL;
00622 
00623   this->DeleteChildWindows();
00624 
00625   if (this->viewport != NULL) DeleteWindowViewport(this);
00626 
00627   this->SetDirty();
00628 
00629   free(this->nested_array); // Contents is released through deletion of #nested_root.
00630   delete this->nested_root;
00631 
00632   this->window_class = WC_INVALID;
00633 }
00634 
00641 Window *FindWindowById(WindowClass cls, WindowNumber number)
00642 {
00643   Window *w;
00644   FOR_ALL_WINDOWS_FROM_BACK(w) {
00645     if (w->window_class == cls && w->window_number == number) return w;
00646   }
00647 
00648   return NULL;
00649 }
00650 
00657 Window *FindWindowByClass(WindowClass cls)
00658 {
00659   Window *w;
00660   FOR_ALL_WINDOWS_FROM_BACK(w) {
00661     if (w->window_class == cls) return w;
00662   }
00663 
00664   return NULL;
00665 }
00666 
00673 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
00674 {
00675   Window *w = FindWindowById(cls, number);
00676   if (force || w == NULL ||
00677       (w->flags4 & WF_STICKY) == 0) {
00678     delete w;
00679   }
00680 }
00681 
00686 void DeleteWindowByClass(WindowClass cls)
00687 {
00688   Window *w;
00689 
00690 restart_search:
00691   /* When we find the window to delete, we need to restart the search
00692    * as deleting this window could cascade in deleting (many) others
00693    * anywhere in the z-array */
00694   FOR_ALL_WINDOWS_FROM_BACK(w) {
00695     if (w->window_class == cls) {
00696       delete w;
00697       goto restart_search;
00698     }
00699   }
00700 }
00701 
00706 void DeleteCompanyWindows(CompanyID id)
00707 {
00708   Window *w;
00709 
00710 restart_search:
00711   /* When we find the window to delete, we need to restart the search
00712    * as deleting this window could cascade in deleting (many) others
00713    * anywhere in the z-array */
00714   FOR_ALL_WINDOWS_FROM_BACK(w) {
00715     if (w->owner == id) {
00716       delete w;
00717       goto restart_search;
00718     }
00719   }
00720 
00721   /* Also delete the company specific windows, that don't have a company-colour */
00722   DeleteWindowById(WC_BUY_COMPANY, id);
00723 }
00724 
00730 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
00731 {
00732   Window *w;
00733   FOR_ALL_WINDOWS_FROM_BACK(w) {
00734     if (w->owner != old_owner) continue;
00735 
00736     switch (w->window_class) {
00737       case WC_COMPANY_COLOUR:
00738       case WC_FINANCES:
00739       case WC_STATION_LIST:
00740       case WC_TRAINS_LIST:
00741       case WC_ROADVEH_LIST:
00742       case WC_SHIPS_LIST:
00743       case WC_AIRCRAFT_LIST:
00744       case WC_BUY_COMPANY:
00745       case WC_COMPANY:
00746         continue;
00747 
00748       default:
00749         w->owner = new_owner;
00750         break;
00751     }
00752   }
00753 }
00754 
00755 static void BringWindowToFront(Window *w);
00756 
00762 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
00763 {
00764   Window *w = FindWindowById(cls, number);
00765 
00766   if (w != NULL) {
00767     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
00768 
00769     w->flags4 |= WF_WHITE_BORDER_MASK;
00770     BringWindowToFront(w);
00771     w->SetDirty();
00772   }
00773 
00774   return w;
00775 }
00776 
00777 static inline bool IsVitalWindow(const Window *w)
00778 {
00779   switch (w->window_class) {
00780     case WC_MAIN_TOOLBAR:
00781     case WC_STATUS_BAR:
00782     case WC_NEWS_WINDOW:
00783     case WC_SEND_NETWORK_MSG:
00784       return true;
00785 
00786     default:
00787       return false;
00788   }
00789 }
00790 
00799 static void BringWindowToFront(Window *w)
00800 {
00801   Window *v = _z_front_window;
00802 
00803   /* Bring the window just below the vital windows */
00804   for (; v != NULL && v != w && IsVitalWindow(v); v = v->z_back) { }
00805 
00806   if (v == NULL || w == v) return; // window is already in the right position
00807 
00808   /* w cannot be at the top already! */
00809   assert(w != _z_front_window);
00810 
00811   if (w->z_back == NULL) {
00812     _z_back_window = w->z_front;
00813   } else {
00814     w->z_back->z_front = w->z_front;
00815   }
00816   w->z_front->z_back = w->z_back;
00817 
00818   w->z_front = v->z_front;
00819   w->z_back = v;
00820 
00821   if (v->z_front == NULL) {
00822     _z_front_window = w;
00823   } else {
00824     v->z_front->z_back = w;
00825   }
00826   v->z_front = w;
00827 
00828   w->SetDirty();
00829 }
00830 
00839 void Window::InitializeData(const WindowDesc *desc, WindowNumber window_number)
00840 {
00841   /* Set up window properties; some of them are needed to set up smallest size below */
00842   this->window_class = desc->cls;
00843   this->flags4 |= WF_WHITE_BORDER_MASK; // just opened windows have a white border
00844   if (desc->default_pos == WDP_CENTER) this->flags4 |= WF_CENTERED;
00845   this->owner = INVALID_OWNER;
00846   this->nested_focus = NULL;
00847   this->window_number = window_number;
00848   this->desc_flags = desc->flags;
00849 
00850   this->OnInit();
00851   /* Initialize nested widget tree. */
00852   if (this->nested_array == NULL) {
00853     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
00854     this->nested_root->SetupSmallestSize(this, true);
00855   } else {
00856     this->nested_root->SetupSmallestSize(this, false);
00857   }
00858   /* Initialize to smallest size. */
00859   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _dynlang.text_dir == TD_RTL);
00860 
00861   /* Further set up window properties,
00862    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
00863   this->resize.step_width  = this->nested_root->resize_x;
00864   this->resize.step_height = this->nested_root->resize_y;
00865 
00866   /* Give focus to the opened window unless it is the OSK window or a text box
00867    * of focused window has focus (so we don't interrupt typing). But if the new
00868    * window has a text box, then take focus anyway. */
00869   if (this->window_class != WC_OSK && (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL)) SetFocusedWindow(this);
00870 
00871   /* Hacky way of specifying always-on-top windows. These windows are
00872    * always above other windows because they are moved below them.
00873    * status-bar is above news-window because it has been created earlier.
00874    * Also, as the chat-window is excluded from this, it will always be
00875    * the last window, thus always on top.
00876    * XXX - Yes, ugly, probably needs something like w->always_on_top flag
00877    * to implement correctly, but even then you need some kind of distinction
00878    * between on-top of chat/news and status windows, because these conflict */
00879   Window *w = _z_front_window;
00880   if (w != NULL && this->window_class != WC_SEND_NETWORK_MSG && this->window_class != WC_HIGHSCORE && this->window_class != WC_ENDSCREEN) {
00881     if (FindWindowById(WC_MAIN_TOOLBAR, 0)     != NULL) w = w->z_back;
00882     if (FindWindowById(WC_STATUS_BAR, 0)       != NULL) w = w->z_back;
00883     if (FindWindowById(WC_NEWS_WINDOW, 0)      != NULL) w = w->z_back;
00884     if (FindWindowByClass(WC_SEND_NETWORK_MSG) != NULL) w = w->z_back;
00885 
00886     if (w == NULL) {
00887       _z_back_window->z_front = this;
00888       this->z_back = _z_back_window;
00889       _z_back_window = this;
00890     } else {
00891       if (w->z_front == NULL) {
00892         _z_front_window = this;
00893       } else {
00894         this->z_front = w->z_front;
00895         w->z_front->z_back = this;
00896       }
00897 
00898       this->z_back = w;
00899       w->z_front = this;
00900     }
00901   } else {
00902     this->z_back = _z_front_window;
00903     if (_z_front_window != NULL) {
00904       _z_front_window->z_front = this;
00905     } else {
00906       _z_back_window = this;
00907     }
00908     _z_front_window = this;
00909   }
00910 }
00911 
00919 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
00920 {
00921   this->left = x;
00922   this->top = y;
00923   this->width = sm_width;
00924   this->height = sm_height;
00925 }
00926 
00937 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
00938 {
00939   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
00940   def_height = max(def_height, this->height);
00941   /* Try to make windows smaller when our window is too small.
00942    * w->(width|height) is normally the same as min_(width|height),
00943    * but this way the GUIs can be made a little more dynamic;
00944    * one can use the same spec for multiple windows and those
00945    * can then determine the real minimum size of the window. */
00946   if (this->width != def_width || this->height != def_height) {
00947     /* Think about the overlapping toolbars when determining the minimum window size */
00948     int free_height = _screen.height;
00949     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
00950     if (wt != NULL) free_height -= wt->height;
00951     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
00952     if (wt != NULL) free_height -= wt->height;
00953 
00954     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
00955     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
00956 
00957     /* X and Y has to go by step.. calculate it.
00958      * The cast to int is necessary else x/y are implicitly casted to
00959      * unsigned int, which won't work. */
00960     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
00961     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
00962 
00963     ResizeWindow(this, enlarge_x, enlarge_y);
00964     /* ResizeWindow() calls this->OnResize(). */
00965   } else {
00966     /* Always call OnResize; that way the scrollbars and matrices get initialized. */
00967     this->OnResize();
00968   }
00969 
00970   int nx = this->left;
00971   int ny = this->top;
00972 
00973   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
00974 
00975   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
00976   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
00977   nx = max(nx, 0);
00978 
00979   if (this->viewport != NULL) {
00980     this->viewport->left += nx - this->left;
00981     this->viewport->top  += ny - this->top;
00982   }
00983   this->left = nx;
00984   this->top = ny;
00985 
00986   this->SetDirty();
00987 }
00988 
01000 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
01001 {
01002   int right  = width + left;
01003   int bottom = height + top;
01004 
01005   if (left < 0 || top < 22 || right > _screen.width || bottom > _screen.height) return false;
01006 
01007   /* Make sure it is not obscured by any window. */
01008   const Window *w;
01009   FOR_ALL_WINDOWS_FROM_BACK(w) {
01010     if (w->window_class == WC_MAIN_WINDOW) continue;
01011 
01012     if (right > w->left &&
01013         w->left + w->width > left &&
01014         bottom > w->top &&
01015         w->top + w->height > top) {
01016       return false;
01017     }
01018   }
01019 
01020   pos.x = left;
01021   pos.y = top;
01022   return true;
01023 }
01024 
01036 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
01037 {
01038   /* Left part of the rectangle may be at most 1/4 off-screen,
01039    * right part of the rectangle may be at most 1/2 off-screen
01040    */
01041   if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
01042   /* Bottom part of the rectangle may be at most 1/4 off-screen */
01043   if (top < 22 || top > _screen.height - (height >> 2)) return false;
01044 
01045   /* Make sure it is not obscured by any window. */
01046   const Window *w;
01047   FOR_ALL_WINDOWS_FROM_BACK(w) {
01048     if (w->window_class == WC_MAIN_WINDOW) continue;
01049 
01050     if (left + width > w->left &&
01051         w->left + w->width > left &&
01052         top + height > w->top &&
01053         w->top + w->height > top) {
01054       return false;
01055     }
01056   }
01057 
01058   pos.x = left;
01059   pos.y = top;
01060   return true;
01061 }
01062 
01069 static Point GetAutoPlacePosition(int width, int height)
01070 {
01071   Point pt;
01072 
01073   /* First attempt, try top-left of the screen */
01074   if (IsGoodAutoPlace1(0, 24, width, height, pt)) return pt;
01075 
01076   /* Second attempt, try around all existing windows with a distance of 2 pixels.
01077    * The new window must be entirely on-screen, and not overlap with an existing window.
01078    * Eight starting points are tried, two at each corner.
01079    */
01080   const Window *w;
01081   FOR_ALL_WINDOWS_FROM_BACK(w) {
01082     if (w->window_class == WC_MAIN_WINDOW) continue;
01083 
01084     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01085     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
01086     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01087     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
01088     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
01089     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
01090     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
01091     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
01092   }
01093 
01094   /* Third attempt, try around all existing windows with a distance of 2 pixels.
01095    * The new window may be partly off-screen, and must not overlap with an existing window.
01096    * Only four starting points are tried.
01097    */
01098   FOR_ALL_WINDOWS_FROM_BACK(w) {
01099     if (w->window_class == WC_MAIN_WINDOW) continue;
01100 
01101     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01102     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
01103     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01104     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
01105   }
01106 
01107   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
01108    * of (+5, +5)
01109    */
01110   int left = 0, top = 24;
01111 
01112 restart:
01113   FOR_ALL_WINDOWS_FROM_BACK(w) {
01114     if (w->left == left && w->top == top) {
01115       left += 5;
01116       top += 5;
01117       goto restart;
01118     }
01119   }
01120 
01121   pt.x = left;
01122   pt.y = top;
01123   return pt;
01124 }
01125 
01132 Point GetToolbarAlignedWindowPosition(int window_width)
01133 {
01134   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01135   assert(w != NULL);
01136   Point pt = { _dynlang.text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
01137   return pt;
01138 }
01139 
01157 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01158 {
01159   Point pt;
01160   const Window *w;
01161 
01162   int16 default_width  = max(desc->default_width,  sm_width);
01163   int16 default_height = max(desc->default_height, sm_height);
01164 
01165   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
01166       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
01167       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
01168 
01169     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
01170     if (pt.x > _screen.width + 10 - default_width) {
01171       pt.x = (_screen.width + 10 - default_width) - 20;
01172     }
01173     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
01174     return pt;
01175   }
01176 
01177   switch (desc->default_pos) {
01178     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
01179       return GetToolbarAlignedWindowPosition(default_width);
01180 
01181     case WDP_AUTO: // Find a good automatic position for the window
01182       return GetAutoPlacePosition(default_width, default_height);
01183 
01184     case WDP_CENTER: // Centre the window horizontally
01185       pt.x = (_screen.width - default_width) / 2;
01186       pt.y = (_screen.height - default_height) / 2;
01187       break;
01188 
01189     case WDP_MANUAL:
01190       pt.x = 0;
01191       pt.y = 0;
01192       break;
01193 
01194     default:
01195       NOT_REACHED();
01196   }
01197 
01198   return pt;
01199 }
01200 
01201 /* virtual */ Point Window::OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01202 {
01203   return LocalGetWindowPlacement(desc, sm_width, sm_height, window_number);
01204 }
01205 
01214 void Window::CreateNestedTree(const WindowDesc *desc, bool fill_nested)
01215 {
01216   int biggest_index = -1;
01217   this->nested_root = MakeWindowNWidgetTree(desc->nwid_parts, desc->nwid_length, &biggest_index, &this->shade_select);
01218   this->nested_array_size = (uint)(biggest_index + 1);
01219 
01220   if (fill_nested) {
01221     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01222     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
01223   }
01224 }
01225 
01231 void Window::FinishInitNested(const WindowDesc *desc, WindowNumber window_number)
01232 {
01233   this->InitializeData(desc, window_number);
01234   Point pt = this->OnInitialPosition(desc, this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
01235   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
01236   this->FindWindowPlacementAndResize(desc->default_width, desc->default_height);
01237 }
01238 
01244 void Window::InitNested(const WindowDesc *desc, WindowNumber window_number)
01245 {
01246   this->CreateNestedTree(desc, false);
01247   this->FinishInitNested(desc, window_number);
01248 }
01249 
01251 Window::Window() : hscroll(false), vscroll(true), vscroll2(true)
01252 {
01253 }
01254 
01260 Window *FindWindowFromPt(int x, int y)
01261 {
01262   Window *w;
01263   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01264     if (IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
01265       return w;
01266     }
01267   }
01268 
01269   return NULL;
01270 }
01271 
01275 void InitWindowSystem()
01276 {
01277   IConsoleClose();
01278 
01279   _z_back_window = NULL;
01280   _z_front_window = NULL;
01281   _focused_window = NULL;
01282   _mouseover_last_w = NULL;
01283   _scrolling_viewport = 0;
01284 
01285   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
01286 }
01287 
01291 void UnInitWindowSystem()
01292 {
01293   Window *w;
01294   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
01295 
01296   for (w = _z_front_window; w != NULL; /* nothing */) {
01297     Window *to_del = w;
01298     w = w->z_back;
01299     free(to_del);
01300   }
01301 
01302   _z_front_window = NULL;
01303   _z_back_window = NULL;
01304 }
01305 
01309 void ResetWindowSystem()
01310 {
01311   UnInitWindowSystem();
01312   InitWindowSystem();
01313   _thd.pos.x = 0;
01314   _thd.pos.y = 0;
01315   _thd.new_pos.x = 0;
01316   _thd.new_pos.y = 0;
01317 }
01318 
01319 static void DecreaseWindowCounters()
01320 {
01321   Window *w;
01322   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01323     /* Unclick scrollbar buttons if they are pressed. */
01324     if (_scroller_click_timeout == 0 && w->flags4 & (WF_SCROLL_DOWN | WF_SCROLL_UP)) {
01325       w->flags4 &= ~(WF_SCROLL_DOWN | WF_SCROLL_UP);
01326       w->SetDirty();
01327     }
01328     w->OnMouseLoop();
01329   }
01330 
01331   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01332     if ((w->flags4 & WF_TIMEOUT_MASK) && !(--w->flags4 & WF_TIMEOUT_MASK)) {
01333       w->OnTimeout();
01334       if (w->desc_flags & WDF_UNCLICK_BUTTONS) w->RaiseButtons(true);
01335     }
01336   }
01337 }
01338 
01339 Window *GetCallbackWnd()
01340 {
01341   return FindWindowById(_thd.window_class, _thd.window_number);
01342 }
01343 
01344 static void HandlePlacePresize()
01345 {
01346   if (_special_mouse_mode != WSM_PRESIZE) return;
01347 
01348   Window *w = GetCallbackWnd();
01349   if (w == NULL) return;
01350 
01351   Point pt = GetTileBelowCursor();
01352   if (pt.x == -1) {
01353     _thd.selend.x = -1;
01354     return;
01355   }
01356 
01357   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
01358 }
01359 
01360 static bool HandleDragDrop()
01361 {
01362   if (_special_mouse_mode != WSM_DRAGDROP) return true;
01363   if (_left_button_down) return false;
01364 
01365   Window *w = GetCallbackWnd();
01366 
01367   if (w != NULL) {
01368     /* send an event in client coordinates. */
01369     Point pt;
01370     pt.x = _cursor.pos.x - w->left;
01371     pt.y = _cursor.pos.y - w->top;
01372     w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
01373   }
01374 
01375   ResetObjectToPlace();
01376 
01377   return false;
01378 }
01379 
01380 static bool HandleMouseOver()
01381 {
01382   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01383 
01384   /* We changed window, put a MOUSEOVER event to the last window */
01385   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
01386     /* Reset mouse-over coordinates of previous window */
01387     Point pt = { -1, -1 };
01388     _mouseover_last_w->OnMouseOver(pt, 0);
01389   }
01390 
01391   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
01392   _mouseover_last_w = w;
01393 
01394   if (w != NULL) {
01395     /* send an event in client coordinates. */
01396     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
01397     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
01398     if (widget != NULL) w->OnMouseOver(pt, widget->index);
01399   }
01400 
01401   /* Mouseover never stops execution */
01402   return true;
01403 }
01404 
01414 void ResizeWindow(Window *w, int delta_x, int delta_y)
01415 {
01416   if (delta_x != 0 || delta_y != 0) {
01417     w->SetDirty();
01418 
01419     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);
01420     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);
01421     assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
01422     assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
01423 
01424     w->nested_root->AssignSizePosition(ST_RESIZE, 0, 0, w->nested_root->smallest_x + new_xinc, w->nested_root->smallest_y + new_yinc, _dynlang.text_dir == TD_RTL);
01425     w->width  = w->nested_root->current_x;
01426     w->height = w->nested_root->current_y;
01427   }
01428 
01429   /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */
01430   w->OnResize();
01431   w->SetDirty();
01432 }
01433 
01438 int GetMainViewTop()
01439 {
01440   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01441   return (w == NULL) ? 0 : w->top + w->height;
01442 }
01443 
01448 int GetMainViewBottom()
01449 {
01450   Window *w = FindWindowById(WC_STATUS_BAR, 0);
01451   return (w == NULL) ? _screen.height : w->top;
01452 }
01453 
01455 static const int MIN_VISIBLE_TITLE_BAR = 13;
01456 
01458 enum PreventHideDirection {
01459   PHD_UP,   
01460   PHD_DOWN, 
01461 };
01462 
01473 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
01474 {
01475   if (v == NULL) return;
01476 
01477   int v_bottom = v->top + v->height;
01478   int v_right = v->left + v->width;
01479   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.
01480 
01481   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
01482   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
01483 
01484   /* Vertically, the rectangle is hidden behind v. */
01485   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
01486     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
01487     return;
01488   }
01489   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
01490     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
01491     return;
01492   }
01493 
01494   /* Horizontally also hidden, force movement to a safe area. */
01495   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
01496     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
01497   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
01498     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
01499   } else {
01500     *ny = safe_y;
01501   }
01502 }
01503 
01504 static bool _dragging_window; 
01505 
01506 static bool HandleWindowDragging()
01507 {
01508   /* Get out immediately if no window is being dragged at all. */
01509   if (!_dragging_window) return true;
01510 
01511   /* If button still down, but cursor hasn't moved, there is nothing to do. */
01512   if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return false;
01513 
01514   /* Otherwise find the window... */
01515   Window *w;
01516   FOR_ALL_WINDOWS_FROM_BACK(w) {
01517     if (w->flags4 & WF_DRAGGING) {
01518       /* Stop the dragging if the left mouse button was released */
01519       if (!_left_button_down) {
01520         w->flags4 &= ~WF_DRAGGING;
01521         break;
01522       }
01523 
01524       w->SetDirty();
01525 
01526       int x = _cursor.pos.x + _drag_delta.x;
01527       int y = _cursor.pos.y + _drag_delta.y;
01528       int nx = x;
01529       int ny = y;
01530 
01531       if (_settings_client.gui.window_snap_radius != 0) {
01532         const Window *v;
01533 
01534         int hsnap = _settings_client.gui.window_snap_radius;
01535         int vsnap = _settings_client.gui.window_snap_radius;
01536         int delta;
01537 
01538         FOR_ALL_WINDOWS_FROM_BACK(v) {
01539           if (v == w) continue; // Don't snap at yourself
01540 
01541           if (y + w->height > v->top && y < v->top + v->height) {
01542             /* Your left border <-> other right border */
01543             delta = abs(v->left + v->width - x);
01544             if (delta <= hsnap) {
01545               nx = v->left + v->width;
01546               hsnap = delta;
01547             }
01548 
01549             /* Your right border <-> other left border */
01550             delta = abs(v->left - x - w->width);
01551             if (delta <= hsnap) {
01552               nx = v->left - w->width;
01553               hsnap = delta;
01554             }
01555           }
01556 
01557           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
01558             /* Your left border <-> other left border */
01559             delta = abs(v->left - x);
01560             if (delta <= hsnap) {
01561               nx = v->left;
01562               hsnap = delta;
01563             }
01564 
01565             /* Your right border <-> other right border */
01566             delta = abs(v->left + v->width - x - w->width);
01567             if (delta <= hsnap) {
01568               nx = v->left + v->width - w->width;
01569               hsnap = delta;
01570             }
01571           }
01572 
01573           if (x + w->width > v->left && x < v->left + v->width) {
01574             /* Your top border <-> other bottom border */
01575             delta = abs(v->top + v->height - y);
01576             if (delta <= vsnap) {
01577               ny = v->top + v->height;
01578               vsnap = delta;
01579             }
01580 
01581             /* Your bottom border <-> other top border */
01582             delta = abs(v->top - y - w->height);
01583             if (delta <= vsnap) {
01584               ny = v->top - w->height;
01585               vsnap = delta;
01586             }
01587           }
01588 
01589           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
01590             /* Your top border <-> other top border */
01591             delta = abs(v->top - y);
01592             if (delta <= vsnap) {
01593               ny = v->top;
01594               vsnap = delta;
01595             }
01596 
01597             /* Your bottom border <-> other bottom border */
01598             delta = abs(v->top + v->height - y - w->height);
01599             if (delta <= vsnap) {
01600               ny = v->top + v->height - w->height;
01601               vsnap = delta;
01602             }
01603           }
01604         }
01605       }
01606 
01607       /* Search for the title bar rectangle. */
01608       Rect caption_rect;
01609       const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
01610       assert(caption != NULL);
01611       caption_rect.left   = caption->pos_x;
01612       caption_rect.right  = caption->pos_x + caption->current_x;
01613       caption_rect.top    = caption->pos_y;
01614       caption_rect.bottom = caption->pos_y + caption->current_y;
01615 
01616       /* Make sure the window doesn't leave the screen */
01617       nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
01618       ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
01619 
01620       /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
01621       PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
01622       PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
01623 
01624       if (w->viewport != NULL) {
01625         w->viewport->left += nx - w->left;
01626         w->viewport->top  += ny - w->top;
01627       }
01628       w->left = nx;
01629       w->top  = ny;
01630 
01631       w->SetDirty();
01632       return false;
01633     } else if (w->flags4 & WF_SIZING) {
01634       /* Stop the sizing if the left mouse button was released */
01635       if (!_left_button_down) {
01636         w->flags4 &= ~WF_SIZING;
01637         w->SetDirty();
01638         break;
01639       }
01640 
01641       /* Compute difference in pixels between cursor position and reference point in the window.
01642        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
01643        */
01644       int x, y = _cursor.pos.y - _drag_delta.y;
01645       if (w->flags4 & WF_SIZING_LEFT) {
01646         x = _drag_delta.x - _cursor.pos.x;
01647       } else {
01648         x = _cursor.pos.x - _drag_delta.x;
01649       }
01650 
01651       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
01652       if (w->resize.step_width  == 0) x = 0;
01653       if (w->resize.step_height == 0) y = 0;
01654 
01655       /* Check the resize button won't go past the bottom of the screen */
01656       if (w->top + w->height + y > _screen.height) {
01657         y = _screen.height - w->height - w->top;
01658       }
01659 
01660       /* X and Y has to go by step.. calculate it.
01661        * The cast to int is necessary else x/y are implicitly casted to
01662        * unsigned int, which won't work. */
01663       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
01664       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
01665 
01666       /* Check that we don't go below the minimum set size */
01667       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
01668         x = w->nested_root->smallest_x - w->width;
01669       }
01670       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
01671         y = w->nested_root->smallest_y - w->height;
01672       }
01673 
01674       /* Window already on size */
01675       if (x == 0 && y == 0) return false;
01676 
01677       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
01678       _drag_delta.y += y;
01679       if ((w->flags4 & WF_SIZING_LEFT) && x != 0) {
01680         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
01681         w->SetDirty();
01682         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
01683         /* ResizeWindow() below ensures marking new position as dirty. */
01684       } else {
01685         _drag_delta.x += x;
01686       }
01687 
01688       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
01689       ResizeWindow(w, x, y);
01690       return false;
01691     }
01692   }
01693 
01694   _dragging_window = false;
01695   return false;
01696 }
01697 
01702 static void StartWindowDrag(Window *w)
01703 {
01704   w->flags4 |= WF_DRAGGING;
01705   w->flags4 &= ~WF_CENTERED;
01706   _dragging_window = true;
01707 
01708   _drag_delta.x = w->left - _cursor.pos.x;
01709   _drag_delta.y = w->top  - _cursor.pos.y;
01710 
01711   BringWindowToFront(w);
01712   DeleteWindowById(WC_DROPDOWN_MENU, 0);
01713 }
01714 
01720 static void StartWindowSizing(Window *w, bool to_left)
01721 {
01722   w->flags4 |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
01723   w->flags4 &= ~WF_CENTERED;
01724   _dragging_window = true;
01725 
01726   _drag_delta.x = _cursor.pos.x;
01727   _drag_delta.y = _cursor.pos.y;
01728 
01729   BringWindowToFront(w);
01730   DeleteWindowById(WC_DROPDOWN_MENU, 0);
01731 }
01732 
01733 
01734 static bool HandleScrollbarScrolling()
01735 {
01736   Window *w;
01737 
01738   /* Get out quickly if no item is being scrolled */
01739   if (!_scrolling_scrollbar) return true;
01740 
01741   /* Find the scrolling window */
01742   FOR_ALL_WINDOWS_FROM_BACK(w) {
01743     if (w->flags4 & WF_SCROLL_MIDDLE) {
01744       /* Abort if no button is clicked any more. */
01745       if (!_left_button_down) {
01746         w->flags4 &= ~WF_SCROLL_MIDDLE;
01747         w->SetDirty();
01748         break;
01749       }
01750 
01751       int i;
01752       Scrollbar *sb;
01753       bool rtl = false;
01754 
01755       if (w->flags4 & WF_HSCROLL) {
01756         sb = &w->hscroll;
01757         i = _cursor.pos.x - _cursorpos_drag_start.x;
01758         rtl = _dynlang.text_dir == TD_RTL;
01759       } else if (w->flags4 & WF_SCROLL2) {
01760         sb = &w->vscroll2;
01761         i = _cursor.pos.y - _cursorpos_drag_start.y;
01762       } else {
01763         sb = &w->vscroll;
01764         i = _cursor.pos.y - _cursorpos_drag_start.y;
01765       }
01766 
01767       /* Find the item we want to move to and make sure it's inside bounds. */
01768       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
01769       if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
01770       if (pos != sb->GetPosition()) {
01771         sb->SetPosition(pos);
01772         w->SetDirty();
01773       }
01774       return false;
01775     }
01776   }
01777 
01778   _scrolling_scrollbar = false;
01779   return false;
01780 }
01781 
01782 static bool HandleViewportScroll()
01783 {
01784   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
01785 
01786   if (!_scrolling_viewport) return true;
01787 
01788   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01789 
01790   if (!(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) || w == NULL) {
01791     _cursor.fix_at = false;
01792     _scrolling_viewport = false;
01793     return true;
01794   }
01795 
01796   if (w == FindWindowById(WC_MAIN_WINDOW, 0) && w->viewport->follow_vehicle != INVALID_VEHICLE) {
01797     /* If the main window is following a vehicle, then first let go of it! */
01798     const Vehicle *veh = Vehicle::Get(w->viewport->follow_vehicle);
01799     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
01800     return true;
01801   }
01802 
01803   Point delta;
01804   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
01805     delta.x = -_cursor.delta.x;
01806     delta.y = -_cursor.delta.y;
01807   } else {
01808     delta.x = _cursor.delta.x;
01809     delta.y = _cursor.delta.y;
01810   }
01811 
01812   if (scrollwheel_scrolling) {
01813     /* We are using scrollwheels for scrolling */
01814     delta.x = _cursor.h_wheel;
01815     delta.y = _cursor.v_wheel;
01816     _cursor.v_wheel = 0;
01817     _cursor.h_wheel = 0;
01818   }
01819 
01820   /* Create a scroll-event and send it to the window */
01821   if (delta.x != 0 || delta.y != 0) w->OnScroll(delta);
01822 
01823   _cursor.delta.x = 0;
01824   _cursor.delta.y = 0;
01825   return false;
01826 }
01827 
01836 static bool MaybeBringWindowToFront(Window *w)
01837 {
01838   bool bring_to_front = false;
01839 
01840   if (w->window_class == WC_MAIN_WINDOW ||
01841       IsVitalWindow(w) ||
01842       w->window_class == WC_TOOLTIPS ||
01843       w->window_class == WC_DROPDOWN_MENU) {
01844     return true;
01845   }
01846 
01847   /* Use unshaded window size rather than current size for shaded windows. */
01848   int w_width  = w->width;
01849   int w_height = w->height;
01850   if (w->IsShaded()) {
01851     w_width  = w->unshaded_size.width;
01852     w_height = w->unshaded_size.height;
01853   }
01854 
01855   Window *u;
01856   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
01857     /* A modal child will prevent the activation of the parent window */
01858     if (u->parent == w && (u->desc_flags & WDF_MODAL)) {
01859       u->flags4 |= WF_WHITE_BORDER_MASK;
01860       u->SetDirty();
01861       return false;
01862     }
01863 
01864     if (u->window_class == WC_MAIN_WINDOW ||
01865         IsVitalWindow(u) ||
01866         u->window_class == WC_TOOLTIPS ||
01867         u->window_class == WC_DROPDOWN_MENU) {
01868       continue;
01869     }
01870 
01871     /* Window sizes don't interfere, leave z-order alone */
01872     if (w->left + w_width <= u->left ||
01873         u->left + u->width <= w->left ||
01874         w->top  + w_height <= u->top ||
01875         u->top + u->height <= w->top) {
01876       continue;
01877     }
01878 
01879     bring_to_front = true;
01880   }
01881 
01882   if (bring_to_front) BringWindowToFront(w);
01883   return true;
01884 }
01885 
01889 void HandleKeypress(uint32 raw_key)
01890 {
01891   /*
01892    * During the generation of the world, there might be
01893    * another thread that is currently building for example
01894    * a road. To not interfere with those tasks, we should
01895    * NOT change the _current_company here.
01896    *
01897    * This is not necessary either, as the only events that
01898    * can be handled are the 'close application' events
01899    */
01900   if (!IsGeneratingWorld()) _current_company = _local_company;
01901 
01902   /* Setup event */
01903   uint16 key     = GB(raw_key,  0, 16);
01904   uint16 keycode = GB(raw_key, 16, 16);
01905 
01906   /*
01907    * The Unicode standard defines an area called the private use area. Code points in this
01908    * area are reserved for private use and thus not portable between systems. For instance,
01909    * Apple defines code points for the arrow keys in this area, but these are only printable
01910    * on a system running OS X. We don't want these keys to show up in text fields and such,
01911    * and thus we have to clear the unicode character when we encounter such a key.
01912    */
01913   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
01914 
01915   /*
01916    * If both key and keycode is zero, we don't bother to process the event.
01917    */
01918   if (key == 0 && keycode == 0) return;
01919 
01920   /* Check if the focused window has a focused editbox */
01921   if (EditBoxInGlobalFocus()) {
01922     /* All input will in this case go to the focused window */
01923     if (_focused_window->OnKeyPress(key, keycode) == Window::ES_HANDLED) return;
01924   }
01925 
01926   /* Call the event, start with the uppermost window, but ignore the toolbar. */
01927   Window *w;
01928   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01929     if (w->window_class == WC_MAIN_TOOLBAR) continue;
01930     if (w->OnKeyPress(key, keycode) == Window::ES_HANDLED) return;
01931   }
01932 
01933   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01934   /* When there is no toolbar w is null, check for that */
01935   if (w != NULL) w->OnKeyPress(key, keycode);
01936 }
01937 
01941 void HandleCtrlChanged()
01942 {
01943   /* Call the event, start with the uppermost window. */
01944   Window *w;
01945   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01946     if (w->OnCTRLStateChange() == Window::ES_HANDLED) return;
01947   }
01948 }
01949 
01956 static int _input_events_this_tick = 0;
01957 
01962 static void HandleAutoscroll()
01963 {
01964   if (_settings_client.gui.autoscroll && _game_mode != GM_MENU && !IsGeneratingWorld()) {
01965     int x = _cursor.pos.x;
01966     int y = _cursor.pos.y;
01967     Window *w = FindWindowFromPt(x, y);
01968     if (w == NULL || w->flags4 & WF_DISABLE_VP_SCROLL) return;
01969     ViewPort *vp = IsPtInWindowViewport(w, x, y);
01970     if (vp != NULL) {
01971       x -= vp->left;
01972       y -= vp->top;
01973 
01974       /* here allows scrolling in both x and y axis */
01975 #define scrollspeed 3
01976       if (x - 15 < 0) {
01977         w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
01978       } else if (15 - (vp->width - x) > 0) {
01979         w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
01980       }
01981       if (y - 15 < 0) {
01982         w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
01983       } else if (15 - (vp->height - y) > 0) {
01984         w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
01985       }
01986 #undef scrollspeed
01987     }
01988   }
01989 }
01990 
01991 enum MouseClick {
01992   MC_NONE = 0,
01993   MC_LEFT,
01994   MC_RIGHT,
01995   MC_DOUBLE_LEFT,
01996 
01997   MAX_OFFSET_DOUBLE_CLICK = 5,     
01998   TIME_BETWEEN_DOUBLE_CLICK = 500, 
01999 };
02000 
02001 extern bool VpHandlePlaceSizingDrag();
02002 
02003 static void ScrollMainViewport(int x, int y)
02004 {
02005   if (_game_mode != GM_MENU) {
02006     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
02007     assert(w);
02008 
02009     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
02010     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
02011   }
02012 }
02013 
02023 static const int8 scrollamt[16][2] = {
02024   { 0,  0}, 
02025   {-2,  0}, 
02026   { 0, -2}, 
02027   {-2, -1}, 
02028   { 2,  0}, 
02029   { 0,  0}, 
02030   { 2, -1}, 
02031   { 0, -2}, 
02032   { 0,  2}, 
02033   {-2,  1}, 
02034   { 0,  0}, 
02035   {-2,  0}, 
02036   { 2,  1}, 
02037   { 0,  2}, 
02038   { 2,  0}, 
02039   { 0,  0}, 
02040 };
02041 
02042 static void HandleKeyScrolling()
02043 {
02044   /*
02045    * Check that any of the dirkeys is pressed and that the focused window
02046    * dont has an edit-box as focused widget.
02047    */
02048   if (_dirkeys && !EditBoxInGlobalFocus()) {
02049     int factor = _shift_pressed ? 50 : 10;
02050     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
02051   }
02052 }
02053 
02054 static void MouseLoop(MouseClick click, int mousewheel)
02055 {
02056   HandlePlacePresize();
02057   UpdateTileSelection();
02058 
02059   if (!VpHandlePlaceSizingDrag())  return;
02060   if (!HandleDragDrop())           return;
02061   if (!HandleWindowDragging())     return;
02062   if (!HandleScrollbarScrolling()) return;
02063   if (!HandleViewportScroll())     return;
02064   if (!HandleMouseOver())          return;
02065 
02066   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02067   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
02068 
02069   int x = _cursor.pos.x;
02070   int y = _cursor.pos.y;
02071   Window *w = FindWindowFromPt(x, y);
02072   if (w == NULL) return;
02073 
02074   if (!MaybeBringWindowToFront(w)) return;
02075   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02076 
02077   /* Don't allow any action in a viewport if either in menu of in generating world */
02078   if (vp != NULL && (_game_mode == GM_MENU || IsGeneratingWorld())) return;
02079 
02080   if (mousewheel != 0) {
02081     if (_settings_client.gui.scrollwheel_scrolling == 0) {
02082       /* Send mousewheel event to window */
02083       w->OnMouseWheel(mousewheel);
02084     }
02085 
02086     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
02087     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
02088   }
02089 
02090   if (vp != NULL) {
02091     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
02092     switch (click) {
02093       case MC_DOUBLE_LEFT:
02094       case MC_LEFT:
02095         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
02096         if (_thd.place_mode != HT_NONE &&
02097             /* query button and place sign button work in pause mode */
02098             _cursor.sprite != SPR_CURSOR_QUERY &&
02099             _cursor.sprite != SPR_CURSOR_SIGN &&
02100             _pause_mode != PM_UNPAUSED &&
02101             !_cheats.build_in_pause.value) {
02102           return;
02103         }
02104 
02105         if (_thd.place_mode == HT_NONE) {
02106           if (!HandleViewportClicked(vp, x, y) &&
02107               !(w->flags4 & WF_DISABLE_VP_SCROLL) &&
02108               _settings_client.gui.left_mouse_btn_scrolling) {
02109             _scrolling_viewport = true;
02110             _cursor.fix_at = false;
02111           }
02112         } else {
02113           PlaceObject();
02114         }
02115         break;
02116 
02117       case MC_RIGHT:
02118         if (!(w->flags4 & WF_DISABLE_VP_SCROLL)) {
02119           _scrolling_viewport = true;
02120           _cursor.fix_at = true;
02121 
02122           /* clear 2D scrolling caches before we start a 2D scroll */
02123           _cursor.h_wheel = 0;
02124           _cursor.v_wheel = 0;
02125         }
02126         break;
02127 
02128       default:
02129         break;
02130     }
02131   } else {
02132     switch (click) {
02133       case MC_LEFT:
02134       case MC_DOUBLE_LEFT:
02135         DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
02136         break;
02137 
02138       default:
02139         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
02140         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
02141          * Simulate a right button click so we can get started. */
02142 
02143         /* fallthough */
02144       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
02145     }
02146   }
02147 }
02148 
02152 void HandleMouseEvents()
02153 {
02154   static int double_click_time = 0;
02155   static int double_click_x = 0;
02156   static int double_click_y = 0;
02157 
02158   /*
02159    * During the generation of the world, there might be
02160    * another thread that is currently building for example
02161    * a road. To not interfere with those tasks, we should
02162    * NOT change the _current_company here.
02163    *
02164    * This is not necessary either, as the only events that
02165    * can be handled are the 'close application' events
02166    */
02167   if (!IsGeneratingWorld()) _current_company = _local_company;
02168 
02169   /* Mouse event? */
02170   MouseClick click = MC_NONE;
02171   if (_left_button_down && !_left_button_clicked) {
02172     click = MC_LEFT;
02173     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
02174         double_click_x != 0    && abs(_cursor.pos.x - double_click_x) < MAX_OFFSET_DOUBLE_CLICK  &&
02175         double_click_y != 0    && abs(_cursor.pos.y - double_click_y) < MAX_OFFSET_DOUBLE_CLICK) {
02176       click = MC_DOUBLE_LEFT;
02177     }
02178     double_click_time = _realtime_tick;
02179     double_click_x = _cursor.pos.x;
02180     double_click_y = _cursor.pos.y;
02181     _left_button_clicked = true;
02182     _input_events_this_tick++;
02183   } else if (_right_button_clicked) {
02184     _right_button_clicked = false;
02185     click = MC_RIGHT;
02186     _input_events_this_tick++;
02187   }
02188 
02189   int mousewheel = 0;
02190   if (_cursor.wheel) {
02191     mousewheel = _cursor.wheel;
02192     _cursor.wheel = 0;
02193     _input_events_this_tick++;
02194   }
02195 
02196   MouseLoop(click, mousewheel);
02197 
02198   /* We have moved the mouse the required distance,
02199    * no need to move it at any later time. */
02200   _cursor.delta.x = 0;
02201   _cursor.delta.y = 0;
02202 }
02203 
02207 static void CheckSoftLimit()
02208 {
02209   if (_settings_client.gui.window_soft_limit == 0) return;
02210 
02211   for (;;) {
02212     uint deletable_count = 0;
02213     Window *w, *last_deletable = NULL;
02214     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02215       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags4 & WF_STICKY)) continue;
02216 
02217       last_deletable = w;
02218       deletable_count++;
02219     }
02220 
02221     /* We've ot reached the soft limit yet */
02222     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
02223 
02224     assert(last_deletable != NULL);
02225     delete last_deletable;
02226   }
02227 }
02228 
02232 void InputLoop()
02233 {
02234   /*
02235    * During the generation of the world, there might be
02236    * another thread that is currently building for example
02237    * a road. To not interfere with those tasks, we should
02238    * NOT change the _current_company here.
02239    *
02240    * This is not necessary either, as the only events that
02241    * can be handled are the 'close application' events
02242    */
02243   if (!IsGeneratingWorld()) _current_company = _local_company;
02244 
02245   CheckSoftLimit();
02246   HandleKeyScrolling();
02247 
02248   /* Do the actual free of the deleted windows. */
02249   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
02250     Window *w = v;
02251     v = v->z_back;
02252 
02253     if (w->window_class != WC_INVALID) continue;
02254 
02255     /* Find the window in the z-array, and effectively remove it
02256      * by moving all windows after it one to the left. This must be
02257      * done before removing the child so we cannot cause recursion
02258      * between the deletion of the parent and the child. */
02259     if (w->z_front == NULL) {
02260       _z_front_window = w->z_back;
02261     } else {
02262       w->z_front->z_back = w->z_back;
02263     }
02264     if (w->z_back == NULL) {
02265       _z_back_window  = w->z_front;
02266     } else {
02267       w->z_back->z_front = w->z_front;
02268     }
02269     free(w);
02270   }
02271 
02272   DecreaseWindowCounters();
02273 
02274   if (_input_events_this_tick != 0) {
02275     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
02276     _input_events_this_tick = 0;
02277     /* there were some inputs this tick, don't scroll ??? */
02278     return;
02279   }
02280 
02281   /* HandleMouseEvents was already called for this tick */
02282   HandleMouseEvents();
02283   HandleAutoscroll();
02284 }
02285 
02289 void UpdateWindows()
02290 {
02291   Window *w;
02292   static int we4_timer = 0;
02293   int t = we4_timer + 1;
02294 
02295   if (t >= 100) {
02296     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02297       w->OnHundredthTick();
02298     }
02299     t = 0;
02300   }
02301   we4_timer = t;
02302 
02303   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02304     if (w->flags4 & WF_WHITE_BORDER_MASK) {
02305       w->flags4 -= WF_WHITE_BORDER_ONE;
02306 
02307       if (!(w->flags4 & WF_WHITE_BORDER_MASK)) w->SetDirty();
02308     }
02309   }
02310 
02311   DrawDirtyBlocks();
02312 
02313   FOR_ALL_WINDOWS_FROM_BACK(w) {
02314     /* Update viewport only if window is not shaded. */
02315     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
02316   }
02317   NetworkDrawChatMessage();
02318   /* Redraw mouse cursor in case it was hidden */
02319   DrawMouseCursor();
02320 }
02321 
02327 void SetWindowDirty(WindowClass cls, WindowNumber number)
02328 {
02329   const Window *w;
02330   FOR_ALL_WINDOWS_FROM_BACK(w) {
02331     if (w->window_class == cls && w->window_number == number) w->SetDirty();
02332   }
02333 }
02334 
02341 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
02342 {
02343   const Window *w;
02344   FOR_ALL_WINDOWS_FROM_BACK(w) {
02345     if (w->window_class == cls && w->window_number == number) {
02346       w->SetWidgetDirty(widget_index);
02347     }
02348   }
02349 }
02350 
02355 void SetWindowClassesDirty(WindowClass cls)
02356 {
02357   Window *w;
02358   FOR_ALL_WINDOWS_FROM_BACK(w) {
02359     if (w->window_class == cls) w->SetDirty();
02360   }
02361 }
02362 
02369 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data)
02370 {
02371   Window *w;
02372   FOR_ALL_WINDOWS_FROM_BACK(w) {
02373     if (w->window_class == cls && w->window_number == number) w->InvalidateData(data);
02374   }
02375 }
02376 
02382 void InvalidateWindowClassesData(WindowClass cls, int data)
02383 {
02384   Window *w;
02385 
02386   FOR_ALL_WINDOWS_FROM_BACK(w) {
02387     if (w->window_class == cls) w->InvalidateData(data);
02388   }
02389 }
02390 
02394 void CallWindowTickEvent()
02395 {
02396   if (_scroller_click_timeout != 0) _scroller_click_timeout--;
02397 
02398   Window *w;
02399   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02400     w->OnTick();
02401   }
02402 }
02403 
02410 void DeleteNonVitalWindows()
02411 {
02412   Window *w;
02413 
02414 restart_search:
02415   /* When we find the window to delete, we need to restart the search
02416    * as deleting this window could cascade in deleting (many) others
02417    * anywhere in the z-array */
02418   FOR_ALL_WINDOWS_FROM_BACK(w) {
02419     if (w->window_class != WC_MAIN_WINDOW &&
02420         w->window_class != WC_SELECT_GAME &&
02421         w->window_class != WC_MAIN_TOOLBAR &&
02422         w->window_class != WC_STATUS_BAR &&
02423         w->window_class != WC_TOOLBAR_MENU &&
02424         w->window_class != WC_TOOLTIPS &&
02425         (w->flags4 & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
02426 
02427       delete w;
02428       goto restart_search;
02429     }
02430   }
02431 }
02432 
02438 void DeleteAllNonVitalWindows()
02439 {
02440   Window *w;
02441 
02442   /* Delete every window except for stickied ones, then sticky ones as well */
02443   DeleteNonVitalWindows();
02444 
02445 restart_search:
02446   /* When we find the window to delete, we need to restart the search
02447    * as deleting this window could cascade in deleting (many) others
02448    * anywhere in the z-array */
02449   FOR_ALL_WINDOWS_FROM_BACK(w) {
02450     if (w->flags4 & WF_STICKY) {
02451       delete w;
02452       goto restart_search;
02453     }
02454   }
02455 }
02456 
02461 void DeleteConstructionWindows()
02462 {
02463   Window *w;
02464 
02465 restart_search:
02466   /* When we find the window to delete, we need to restart the search
02467    * as deleting this window could cascade in deleting (many) others
02468    * anywhere in the z-array */
02469   FOR_ALL_WINDOWS_FROM_BACK(w) {
02470     if (w->desc_flags & WDF_CONSTRUCTION) {
02471       delete w;
02472       goto restart_search;
02473     }
02474   }
02475 
02476   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
02477 }
02478 
02480 void HideVitalWindows()
02481 {
02482   DeleteWindowById(WC_TOOLBAR_MENU, 0);
02483   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
02484   DeleteWindowById(WC_STATUS_BAR, 0);
02485 }
02486 
02488 void ReInitAllWindows()
02489 {
02490   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
02491 
02492   Window *w;
02493   FOR_ALL_WINDOWS_FROM_BACK(w) {
02494     w->ReInit();
02495   }
02496 #ifdef ENABLE_NETWORK
02497   void NetworkReInitChatBoxSize();
02498   NetworkReInitChatBoxSize();
02499 #endif
02500 
02501   /* Make sure essential parts of all windows are visible */
02502   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
02503   MarkWholeScreenDirty();
02504 }
02505 
02511 int PositionMainToolbar(Window *w)
02512 {
02513   DEBUG(misc, 5, "Repositioning Main Toolbar...");
02514 
02515   if (w == NULL || w->window_class != WC_MAIN_TOOLBAR) {
02516     w = FindWindowById(WC_MAIN_TOOLBAR, 0);
02517   }
02518 
02519   switch (_settings_client.gui.toolbar_pos) {
02520     case 1:  w->left = (_screen.width - w->width) / 2; break;
02521     case 2:  w->left = _screen.width - w->width; break;
02522     default: w->left = 0;
02523   }
02524   SetDirtyBlocks(0, 0, _screen.width, w->height); // invalidate the whole top part
02525   return w->left;
02526 }
02527 
02528 
02534 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
02535 {
02536   Window *w;
02537   FOR_ALL_WINDOWS_FROM_BACK(w) {
02538     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
02539       w->viewport->follow_vehicle = to_index;
02540       w->SetDirty();
02541     }
02542   }
02543 }
02544 
02545 
02551 void RelocateAllWindows(int neww, int newh)
02552 {
02553   Window *w;
02554 
02555   FOR_ALL_WINDOWS_FROM_BACK(w) {
02556     int left, top;
02557 
02558     if (w->window_class == WC_MAIN_WINDOW) {
02559       ViewPort *vp = w->viewport;
02560       vp->width = w->width = neww;
02561       vp->height = w->height = newh;
02562       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
02563       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
02564       continue; // don't modify top,left
02565     }
02566 
02567     /* XXX - this probably needs something more sane. For example specying
02568      * in a 'backup'-desc that the window should always be centred. */
02569     switch (w->window_class) {
02570       case WC_MAIN_TOOLBAR:
02571         if (neww - w->width != 0) ResizeWindow(w, min(neww, 640) - w->width, 0);
02572 
02573         top = w->top;
02574         left = PositionMainToolbar(w); // changes toolbar orientation
02575         break;
02576 
02577       case WC_NEWS_WINDOW:
02578         top = newh - w->height;
02579         left = (neww - w->width) >> 1;
02580         break;
02581 
02582       case WC_STATUS_BAR:
02583         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0);
02584         top = newh - w->height;
02585         left = (neww - w->width) >> 1;
02586         break;
02587 
02588       case WC_SEND_NETWORK_MSG:
02589         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0);
02590         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
02591         left = (neww - w->width) >> 1;
02592         break;
02593 
02594       case WC_CONSOLE:
02595         IConsoleResize(w);
02596         continue;
02597 
02598       default: {
02599         if (w->flags4 & WF_CENTERED) {
02600           top = (newh - w->height) >> 1;
02601           left = (neww - w->width) >> 1;
02602           break;
02603         }
02604 
02605         left = w->left;
02606         if (left + (w->width >> 1) >= neww) left = neww - w->width;
02607         if (left < 0) left = 0;
02608 
02609         top = w->top;
02610         if (top + (w->height >> 1) >= newh) top = newh - w->height;
02611 
02612         const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
02613         if (wt != NULL) {
02614           if (top < wt->height && wt->left < (w->left + w->width) && (wt->left + wt->width) > w->left) top = wt->height;
02615           if (top >= newh) top = newh - 1;
02616         } else {
02617           if (top < 0) top = 0;
02618         }
02619       } break;
02620     }
02621 
02622     if (w->viewport != NULL) {
02623       w->viewport->left += left - w->left;
02624       w->viewport->top += top - w->top;
02625     }
02626 
02627     w->left = left;
02628     w->top = top;
02629   }
02630 }
02631 
02636 PickerWindowBase::~PickerWindowBase()
02637 {
02638   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
02639   ResetObjectToPlace();
02640 }

Generated on Sun Nov 14 14:42:00 2010 for OpenTTD by  doxygen 1.6.1