window.cpp

Go to the documentation of this file.
00001 /* $Id: window.cpp 18809 2010-01-15 16:41:15Z 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;
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, bool double_click)
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   /* Process special buttons (only single clicks) */
00287   if (!double_click) {
00288     /* Clicked on a widget that is not disabled.
00289      * So unless the clicked widget is the caption bar, change focus to this widget */
00290     if (widget_type != WWT_CAPTION) {
00291       /* Close the OSK window if a edit box loses focus */
00292       if (w->nested_focus != NULL &&  w->nested_focus->type == WWT_EDITBOX && w->nested_focus != nw && w->window_class != WC_OSK) {
00293         DeleteWindowById(WC_OSK, 0);
00294       }
00295 
00296       /* focused_widget_changed is 'now' only true if the window this widget
00297        * is in gained focus. In that case it must remain true, also if the
00298        * local widget focus did not change. As such it's the logical-or of
00299        * both changed states.
00300        *
00301        * If this is not preserved, then the OSK window would be opened when
00302        * a user has the edit box focused and then click on another window and
00303        * then back again on the edit box (to type some text).
00304        */
00305       focused_widget_changed |= w->SetFocusedWidget(widget_index);
00306     }
00307 
00308     /* Close any child drop down menus. If the button pressed was the drop down
00309      * list's own button, then we should not process the click any further. */
00310     if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
00311 
00312     switch (widget_type) {
00313       /* special widget handling for buttons*/
00314       case WWT_PANEL   | WWB_PUSHBUTTON: // WWT_PUSHBTN
00315       case WWT_IMGBTN  | WWB_PUSHBUTTON: // WWT_PUSHIMGBTN
00316       case WWT_TEXTBTN | WWB_PUSHBUTTON: // WWT_PUSHTXTBTN
00317         w->HandleButtonClick(widget_index);
00318         break;
00319 
00320       case WWT_SCROLLBAR:
00321       case WWT_SCROLL2BAR:
00322       case WWT_HSCROLLBAR:
00323         ScrollbarClickHandler(w, nw, x, y);
00324         break;
00325 
00326       case WWT_EDITBOX:
00327         if (!focused_widget_changed) { // Only open the OSK window if clicking on an already focused edit box
00328           /* Open the OSK window if clicked on an edit box */
00329           QueryStringBaseWindow *qs = dynamic_cast<QueryStringBaseWindow *>(w);
00330           if (qs != NULL) {
00331             qs->OnOpenOSKWindow(widget_index);
00332           }
00333         }
00334         break;
00335 
00336       case WWT_CLOSEBOX: // 'X'
00337         delete w;
00338         return;
00339 
00340       case WWT_CAPTION: // 'Title bar'
00341         StartWindowDrag(w);
00342         return;
00343 
00344       case WWT_RESIZEBOX:
00345         /* When the resize widget is on the left size of the window
00346          * we assume that that button is used to resize to the left. */
00347         StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
00348         nw->SetDirty(w);
00349         return;
00350 
00351       case WWT_SHADEBOX:
00352         nw->SetDirty(w);
00353         w->SetShaded(!w->IsShaded());
00354         return;
00355 
00356       case WWT_STICKYBOX:
00357         w->flags4 ^= WF_STICKY;
00358         nw->SetDirty(w);
00359         return;
00360 
00361       default:
00362         break;
00363     }
00364   }
00365 
00366   /* Widget has no index, so the window is not interested in it. */
00367   if (widget_index < 0) return;
00368 
00369   Point pt = { x, y };
00370 
00371   if (double_click) {
00372     w->OnDoubleClick(pt, widget_index);
00373   } else {
00374     w->OnClick(pt, widget_index);
00375   }
00376 }
00377 
00384 static void DispatchRightClickEvent(Window *w, int x, int y)
00385 {
00386   NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00387 
00388   /* No widget to handle */
00389   if (wid == NULL) return;
00390 
00391   /* Show the tooltip if there is any */
00392   if (wid->tool_tip != 0) {
00393     GuiShowTooltips(wid->tool_tip);
00394     return;
00395   }
00396 
00397   /* Widget has no index, so the window is not interested in it. */
00398   if (wid->index < 0) return;
00399 
00400   Point pt = { x, y };
00401   w->OnRightClick(pt, wid->index);
00402 }
00403 
00411 static void DispatchMouseWheelEvent(Window *w, const NWidgetCore *nwid, int wheel)
00412 {
00413   if (nwid == NULL) return;
00414 
00415   /* Using wheel on caption/shade-box shades or unshades the window. */
00416   if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
00417     w->SetShaded(!w->IsShaded());
00418     return;
00419   }
00420 
00421   /* Scroll the widget attached to the scrollbar. */
00422   Scrollbar *sb = nwid->FindScrollbar(w);
00423   if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
00424     sb->UpdatePosition(wheel);
00425     w->SetDirty();
00426   }
00427 }
00428 
00441 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
00442 {
00443   const Window *v;
00444   FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
00445     if (right > v->left &&
00446         bottom > v->top &&
00447         left < v->left + v->width &&
00448         top < v->top + v->height) {
00449       /* v and rectangle intersect with eeach other */
00450       int x;
00451 
00452       if (left < (x = v->left)) {
00453         DrawOverlappedWindow(w, left, top, x, bottom);
00454         DrawOverlappedWindow(w, x, top, right, bottom);
00455         return;
00456       }
00457 
00458       if (right > (x = v->left + v->width)) {
00459         DrawOverlappedWindow(w, left, top, x, bottom);
00460         DrawOverlappedWindow(w, x, top, right, bottom);
00461         return;
00462       }
00463 
00464       if (top < (x = v->top)) {
00465         DrawOverlappedWindow(w, left, top, right, x);
00466         DrawOverlappedWindow(w, left, x, right, bottom);
00467         return;
00468       }
00469 
00470       if (bottom > (x = v->top + v->height)) {
00471         DrawOverlappedWindow(w, left, top, right, x);
00472         DrawOverlappedWindow(w, left, x, right, bottom);
00473         return;
00474       }
00475 
00476       return;
00477     }
00478   }
00479 
00480   /* Setup blitter, and dispatch a repaint event to window *wz */
00481   DrawPixelInfo *dp = _cur_dpi;
00482   dp->width = right - left;
00483   dp->height = bottom - top;
00484   dp->left = left - w->left;
00485   dp->top = top - w->top;
00486   dp->pitch = _screen.pitch;
00487   dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
00488   dp->zoom = ZOOM_LVL_NORMAL;
00489   w->OnPaint();
00490 }
00491 
00500 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
00501 {
00502   Window *w;
00503   DrawPixelInfo bk;
00504   _cur_dpi = &bk;
00505 
00506   FOR_ALL_WINDOWS_FROM_BACK(w) {
00507     if (right > w->left &&
00508         bottom > w->top &&
00509         left < w->left + w->width &&
00510         top < w->top + w->height) {
00511       /* Window w intersects with the rectangle => needs repaint */
00512       DrawOverlappedWindow(w, left, top, right, bottom);
00513     }
00514   }
00515 }
00516 
00521 void Window::SetDirty() const
00522 {
00523   SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
00524 }
00525 
00531 void Window::ReInit(int rx, int ry)
00532 {
00533   this->SetDirty(); // Mark whole current window as dirty.
00534 
00535   /* Save current size. */
00536   int window_width  = this->width;
00537   int window_height = this->height;
00538 
00539   this->OnInit();
00540   /* Re-initialize the window from the ground up. No need to change the nested_array, as all widgets stay where they are. */
00541   this->nested_root->SetupSmallestSize(this, false);
00542   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _dynlang.text_dir == TD_RTL);
00543   this->width  = this->nested_root->smallest_x;
00544   this->height = this->nested_root->smallest_y;
00545   this->resize.step_width  = this->nested_root->resize_x;
00546   this->resize.step_height = this->nested_root->resize_y;
00547 
00548   /* Resize as close to the original size + requested resize as possible. */
00549   window_width  = max(window_width  + rx, this->width);
00550   window_height = max(window_height + ry, this->height);
00551   int dx = (this->resize.step_width  == 0) ? 0 : window_width  - this->width;
00552   int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
00553   /* dx and dy has to go by step.. calculate it.
00554    * The cast to int is necessary else dx/dy are implicitly casted to unsigned int, which won't work. */
00555   if (this->resize.step_width  > 1) dx -= dx % (int)this->resize.step_width;
00556   if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
00557 
00558   ResizeWindow(this, dx, dy);
00559   this->OnResize();
00560   this->SetDirty();
00561 }
00562 
00567 void Window::SetShaded(bool make_shaded)
00568 {
00569   if (this->shade_select == NULL) return;
00570 
00571   int desired = make_shaded ? SZSP_HORIZONTAL : 0;
00572   if (this->shade_select->shown_plane != desired) {
00573     if (make_shaded) {
00574       this->unshaded_size.width  = this->width;
00575       this->unshaded_size.height = this->height;
00576       this->shade_select->SetDisplayedPlane(desired);
00577       this->ReInit(0, -this->height);
00578     } else {
00579       this->shade_select->SetDisplayedPlane(desired);
00580       int dx = ((int)this->unshaded_size.width  > this->width)  ? (int)this->unshaded_size.width  - this->width  : 0;
00581       int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
00582       this->ReInit(dx, dy);
00583     }
00584   }
00585 }
00586 
00592 static Window *FindChildWindow(const Window *w, WindowClass wc)
00593 {
00594   Window *v;
00595   FOR_ALL_WINDOWS_FROM_BACK(v) {
00596     if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
00597   }
00598 
00599   return NULL;
00600 }
00601 
00606 void Window::DeleteChildWindows(WindowClass wc) const
00607 {
00608   Window *child = FindChildWindow(this, wc);
00609   while (child != NULL) {
00610     delete child;
00611     child = FindChildWindow(this, wc);
00612   }
00613 }
00614 
00618 Window::~Window()
00619 {
00620   if (_thd.place_mode != HT_NONE &&
00621       _thd.window_class == this->window_class &&
00622       _thd.window_number == this->window_number) {
00623     ResetObjectToPlace();
00624   }
00625 
00626   /* Prevent Mouseover() from resetting mouse-over coordinates on a non-existing window */
00627   if (_mouseover_last_w == this) _mouseover_last_w = NULL;
00628 
00629   /* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
00630   if (_focused_window == this) _focused_window = NULL;
00631 
00632   this->DeleteChildWindows();
00633 
00634   if (this->viewport != NULL) DeleteWindowViewport(this);
00635 
00636   this->SetDirty();
00637 
00638   free(this->nested_array); // Contents is released through deletion of #nested_root.
00639   delete this->nested_root;
00640 
00641   this->window_class = WC_INVALID;
00642 }
00643 
00650 Window *FindWindowById(WindowClass cls, WindowNumber number)
00651 {
00652   Window *w;
00653   FOR_ALL_WINDOWS_FROM_BACK(w) {
00654     if (w->window_class == cls && w->window_number == number) return w;
00655   }
00656 
00657   return NULL;
00658 }
00659 
00666 Window *FindWindowByClass(WindowClass cls)
00667 {
00668   Window *w;
00669   FOR_ALL_WINDOWS_FROM_BACK(w) {
00670     if (w->window_class == cls) return w;
00671   }
00672 
00673   return NULL;
00674 }
00675 
00682 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
00683 {
00684   Window *w = FindWindowById(cls, number);
00685   if (force || w == NULL ||
00686       (w->flags4 & WF_STICKY) == 0) {
00687     delete w;
00688   }
00689 }
00690 
00695 void DeleteWindowByClass(WindowClass cls)
00696 {
00697   Window *w;
00698 
00699 restart_search:
00700   /* When we find the window to delete, we need to restart the search
00701    * as deleting this window could cascade in deleting (many) others
00702    * anywhere in the z-array */
00703   FOR_ALL_WINDOWS_FROM_BACK(w) {
00704     if (w->window_class == cls) {
00705       delete w;
00706       goto restart_search;
00707     }
00708   }
00709 }
00710 
00715 void DeleteCompanyWindows(CompanyID id)
00716 {
00717   Window *w;
00718 
00719 restart_search:
00720   /* When we find the window to delete, we need to restart the search
00721    * as deleting this window could cascade in deleting (many) others
00722    * anywhere in the z-array */
00723   FOR_ALL_WINDOWS_FROM_BACK(w) {
00724     if (w->owner == id) {
00725       delete w;
00726       goto restart_search;
00727     }
00728   }
00729 
00730   /* Also delete the company specific windows, that don't have a company-colour */
00731   DeleteWindowById(WC_BUY_COMPANY, id);
00732 }
00733 
00739 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
00740 {
00741   Window *w;
00742   FOR_ALL_WINDOWS_FROM_BACK(w) {
00743     if (w->owner != old_owner) continue;
00744 
00745     switch (w->window_class) {
00746       case WC_COMPANY_COLOUR:
00747       case WC_FINANCES:
00748       case WC_STATION_LIST:
00749       case WC_TRAINS_LIST:
00750       case WC_ROADVEH_LIST:
00751       case WC_SHIPS_LIST:
00752       case WC_AIRCRAFT_LIST:
00753       case WC_BUY_COMPANY:
00754       case WC_COMPANY:
00755         continue;
00756 
00757       default:
00758         w->owner = new_owner;
00759         break;
00760     }
00761   }
00762 }
00763 
00764 static void BringWindowToFront(Window *w);
00765 
00771 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
00772 {
00773   Window *w = FindWindowById(cls, number);
00774 
00775   if (w != NULL) {
00776     if (w->IsShaded()) w->SetShaded(false); // Restore original window size if it was shaded.
00777 
00778     w->flags4 |= WF_WHITE_BORDER_MASK;
00779     BringWindowToFront(w);
00780     w->SetDirty();
00781   }
00782 
00783   return w;
00784 }
00785 
00786 static inline bool IsVitalWindow(const Window *w)
00787 {
00788   switch (w->window_class) {
00789     case WC_MAIN_TOOLBAR:
00790     case WC_STATUS_BAR:
00791     case WC_NEWS_WINDOW:
00792     case WC_SEND_NETWORK_MSG:
00793       return true;
00794 
00795     default:
00796       return false;
00797   }
00798 }
00799 
00808 static void BringWindowToFront(Window *w)
00809 {
00810   Window *v = _z_front_window;
00811 
00812   /* Bring the window just below the vital windows */
00813   for (; v != NULL && v != w && IsVitalWindow(v); v = v->z_back) { }
00814 
00815   if (v == NULL || w == v) return; // window is already in the right position
00816 
00817   /* w cannot be at the top already! */
00818   assert(w != _z_front_window);
00819 
00820   if (w->z_back == NULL) {
00821     _z_back_window = w->z_front;
00822   } else {
00823     w->z_back->z_front = w->z_front;
00824   }
00825   w->z_front->z_back = w->z_back;
00826 
00827   w->z_front = v->z_front;
00828   w->z_back = v;
00829 
00830   if (v->z_front == NULL) {
00831     _z_front_window = w;
00832   } else {
00833     v->z_front->z_back = w;
00834   }
00835   v->z_front = w;
00836 
00837   w->SetDirty();
00838 }
00839 
00849 void Window::InitializeData(WindowClass cls, int window_number, uint32 desc_flags)
00850 {
00851   /* Set up window properties; some of them are needed to set up smallest size below */
00852   this->window_class = cls;
00853   this->flags4 |= WF_WHITE_BORDER_MASK; // just opened windows have a white border
00854   this->owner = INVALID_OWNER;
00855   this->nested_focus = NULL;
00856   this->window_number = window_number;
00857   this->desc_flags = desc_flags;
00858 
00859   this->OnInit();
00860   /* Initialize nested widget tree. */
00861   if (this->nested_array == NULL) {
00862     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
00863     this->nested_root->SetupSmallestSize(this, true);
00864   } else {
00865     this->nested_root->SetupSmallestSize(this, false);
00866   }
00867   /* Initialize to smallest size. */
00868   this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _dynlang.text_dir == TD_RTL);
00869 
00870   /* Further set up window properties,
00871    * this->left, this->top, this->width, this->height, this->resize.width, and this->resize.height are initialized later. */
00872   this->resize.step_width  = this->nested_root->resize_x;
00873   this->resize.step_height = this->nested_root->resize_y;
00874 
00875   /* Give focus to the opened window unless it is the OSK window or a text box
00876    * of focused window has focus (so we don't interrupt typing). But if the new
00877    * window has a text box, then take focus anyway. */
00878   if (this->window_class != WC_OSK && (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL)) SetFocusedWindow(this);
00879 
00880   /* Hacky way of specifying always-on-top windows. These windows are
00881    * always above other windows because they are moved below them.
00882    * status-bar is above news-window because it has been created earlier.
00883    * Also, as the chat-window is excluded from this, it will always be
00884    * the last window, thus always on top.
00885    * XXX - Yes, ugly, probably needs something like w->always_on_top flag
00886    * to implement correctly, but even then you need some kind of distinction
00887    * between on-top of chat/news and status windows, because these conflict */
00888   Window *w = _z_front_window;
00889   if (w != NULL && this->window_class != WC_SEND_NETWORK_MSG && this->window_class != WC_HIGHSCORE && this->window_class != WC_ENDSCREEN) {
00890     if (FindWindowById(WC_MAIN_TOOLBAR, 0)     != NULL) w = w->z_back;
00891     if (FindWindowById(WC_STATUS_BAR, 0)       != NULL) w = w->z_back;
00892     if (FindWindowById(WC_NEWS_WINDOW, 0)      != NULL) w = w->z_back;
00893     if (FindWindowByClass(WC_SEND_NETWORK_MSG) != NULL) w = w->z_back;
00894 
00895     if (w == NULL) {
00896       _z_back_window->z_front = this;
00897       this->z_back = _z_back_window;
00898       _z_back_window = this;
00899     } else {
00900       if (w->z_front == NULL) {
00901         _z_front_window = this;
00902       } else {
00903         this->z_front = w->z_front;
00904         w->z_front->z_back = this;
00905       }
00906 
00907       this->z_back = w;
00908       w->z_front = this;
00909     }
00910   } else {
00911     this->z_back = _z_front_window;
00912     if (_z_front_window != NULL) {
00913       _z_front_window->z_front = this;
00914     } else {
00915       _z_back_window = this;
00916     }
00917     _z_front_window = this;
00918   }
00919 }
00920 
00928 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
00929 {
00930   this->left = x;
00931   this->top = y;
00932   this->width = sm_width;
00933   this->height = sm_height;
00934 }
00935 
00946 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
00947 {
00948   def_width  = max(def_width,  this->width); // Don't allow default size to be smaller than smallest size
00949   def_height = max(def_height, this->height);
00950   /* Try to make windows smaller when our window is too small.
00951    * w->(width|height) is normally the same as min_(width|height),
00952    * but this way the GUIs can be made a little more dynamic;
00953    * one can use the same spec for multiple windows and those
00954    * can then determine the real minimum size of the window. */
00955   if (this->width != def_width || this->height != def_height) {
00956     /* Think about the overlapping toolbars when determining the minimum window size */
00957     int free_height = _screen.height;
00958     const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
00959     if (wt != NULL) free_height -= wt->height;
00960     wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
00961     if (wt != NULL) free_height -= wt->height;
00962 
00963     int enlarge_x = max(min(def_width  - this->width,  _screen.width - this->width),  0);
00964     int enlarge_y = max(min(def_height - this->height, free_height   - this->height), 0);
00965 
00966     /* X and Y has to go by step.. calculate it.
00967      * The cast to int is necessary else x/y are implicitly casted to
00968      * unsigned int, which won't work. */
00969     if (this->resize.step_width  > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
00970     if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
00971 
00972     ResizeWindow(this, enlarge_x, enlarge_y);
00973   }
00974 
00975   /* Always call OnResize; that way the scrollbars and matrices get initialized */
00976   this->OnResize();
00977 
00978   int nx = this->left;
00979   int ny = this->top;
00980 
00981   if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
00982 
00983   const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
00984   ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
00985   nx = max(nx, 0);
00986 
00987   if (this->viewport != NULL) {
00988     this->viewport->left += nx - this->left;
00989     this->viewport->top  += ny - this->top;
00990   }
00991   this->left = nx;
00992   this->top = ny;
00993 
00994   this->SetDirty();
00995 }
00996 
01008 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
01009 {
01010   int right  = width + left;
01011   int bottom = height + top;
01012 
01013   if (left < 0 || top < 22 || right > _screen.width || bottom > _screen.height) return false;
01014 
01015   /* Make sure it is not obscured by any window. */
01016   const Window *w;
01017   FOR_ALL_WINDOWS_FROM_BACK(w) {
01018     if (w->window_class == WC_MAIN_WINDOW) continue;
01019 
01020     if (right > w->left &&
01021         w->left + w->width > left &&
01022         bottom > w->top &&
01023         w->top + w->height > top) {
01024       return false;
01025     }
01026   }
01027 
01028   pos.x = left;
01029   pos.y = top;
01030   return true;
01031 }
01032 
01044 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
01045 {
01046   /* Left part of the rectangle may be at most 1/4 off-screen,
01047    * right part of the rectangle may be at most 1/2 off-screen
01048    */
01049   if (left < -(width>>2) || left > _screen.width - (width>>1)) return false;
01050   /* Bottom part of the rectangle may be at most 1/4 off-screen */
01051   if (top < 22 || top > _screen.height - (height>>2)) return false;
01052 
01053   /* Make sure it is not obscured by any window. */
01054   const Window *w;
01055   FOR_ALL_WINDOWS_FROM_BACK(w) {
01056     if (w->window_class == WC_MAIN_WINDOW) continue;
01057 
01058     if (left + width > w->left &&
01059         w->left + w->width > left &&
01060         top + height > w->top &&
01061         w->top + w->height > top) {
01062       return false;
01063     }
01064   }
01065 
01066   pos.x = left;
01067   pos.y = top;
01068   return true;
01069 }
01070 
01077 static Point GetAutoPlacePosition(int width, int height)
01078 {
01079   Point pt;
01080 
01081   /* First attempt, try top-left of the screen */
01082   if (IsGoodAutoPlace1(0, 24, width, height, pt)) return pt;
01083 
01084   /* Second attempt, try around all existing windows with a distance of 2 pixels.
01085    * The new window must be entirely on-screen, and not overlap with an existing window.
01086    * Eight starting points are tried, two at each corner.
01087    */
01088   const Window *w;
01089   FOR_ALL_WINDOWS_FROM_BACK(w) {
01090     if (w->window_class == WC_MAIN_WINDOW) continue;
01091 
01092     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01093     if (IsGoodAutoPlace1(w->left - width - 2,    w->top, width, height, pt)) return pt;
01094     if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01095     if (IsGoodAutoPlace1(w->left, w->top - height - 2,    width, height, pt)) return pt;
01096     if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
01097     if (IsGoodAutoPlace1(w->left - width - 2,    w->top + w->height - height, width, height, pt)) return pt;
01098     if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
01099     if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2,    width, height, pt)) return pt;
01100   }
01101 
01102   /* Third attempt, try around all existing windows with a distance of 2 pixels.
01103    * The new window may be partly off-screen, and must not overlap with an existing window.
01104    * Only four starting points are tried.
01105    */
01106   FOR_ALL_WINDOWS_FROM_BACK(w) {
01107     if (w->window_class == WC_MAIN_WINDOW) continue;
01108 
01109     if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01110     if (IsGoodAutoPlace2(w->left - width - 2,    w->top, width, height, pt)) return pt;
01111     if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01112     if (IsGoodAutoPlace2(w->left, w->top - height - 2,    width, height, pt)) return pt;
01113   }
01114 
01115   /* Fourth and final attempt, put window at diagonal starting from (0, 24), try multiples
01116    * of (+5, +5)
01117    */
01118   int left = 0, top = 24;
01119 
01120 restart:
01121   FOR_ALL_WINDOWS_FROM_BACK(w) {
01122     if (w->left == left && w->top == top) {
01123       left += 5;
01124       top += 5;
01125       goto restart;
01126     }
01127   }
01128 
01129   pt.x = left;
01130   pt.y = top;
01131   return pt;
01132 }
01133 
01140 Point GetToolbarAlignedWindowPosition(int window_width)
01141 {
01142   const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01143   assert(w != NULL);
01144   Point pt = { _dynlang.text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
01145   return pt;
01146 }
01147 
01165 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01166 {
01167   Point pt;
01168   const Window *w;
01169 
01170   int16 default_width  = max(desc->default_width,  sm_width);
01171   int16 default_height = max(desc->default_height, sm_height);
01172 
01173   if (desc->parent_cls != 0 /* WC_MAIN_WINDOW */ &&
01174       (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
01175       w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
01176 
01177     pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
01178     if (pt.x > _screen.width + 10 - default_width) {
01179       pt.x = (_screen.width + 10 - default_width) - 20;
01180     }
01181     pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
01182     return pt;
01183   }
01184 
01185   switch (desc->default_pos) {
01186     case WDP_ALIGN_TOOLBAR: // Align to the toolbar
01187       return GetToolbarAlignedWindowPosition(default_width);
01188 
01189     case WDP_AUTO: // Find a good automatic position for the window
01190       return GetAutoPlacePosition(default_width, default_height);
01191 
01192     case WDP_CENTER: // Centre the window horizontally
01193       pt.x = (_screen.width - default_width) / 2;
01194       pt.y = (_screen.height - default_height) / 2;
01195       break;
01196 
01197     case WDP_MANUAL:
01198       pt.x = 0;
01199       pt.y = 0;
01200       break;
01201 
01202     default:
01203       NOT_REACHED();
01204   }
01205 
01206   return pt;
01207 }
01208 
01209 /* virtual */ Point Window::OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01210 {
01211   return LocalGetWindowPlacement(desc, sm_width, sm_height, window_number);
01212 }
01213 
01222 void Window::CreateNestedTree(const WindowDesc *desc, bool fill_nested)
01223 {
01224   int biggest_index = -1;
01225   this->nested_root = MakeWindowNWidgetTree(desc->nwid_parts, desc->nwid_length, &biggest_index, &this->shade_select);
01226   this->nested_array_size = (uint)(biggest_index + 1);
01227 
01228   if (fill_nested) {
01229     this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01230     this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
01231   }
01232 }
01233 
01239 void Window::FinishInitNested(const WindowDesc *desc, WindowNumber window_number)
01240 {
01241   this->InitializeData(desc->cls, window_number, desc->flags);
01242   Point pt = this->OnInitialPosition(desc, this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
01243   this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
01244   this->FindWindowPlacementAndResize(desc->default_width, desc->default_height);
01245 }
01246 
01252 void Window::InitNested(const WindowDesc *desc, WindowNumber window_number)
01253 {
01254   this->CreateNestedTree(desc, false);
01255   this->FinishInitNested(desc, window_number);
01256 }
01257 
01259 Window::Window() : hscroll(false), vscroll(true), vscroll2(true)
01260 {
01261 }
01262 
01268 Window *FindWindowFromPt(int x, int y)
01269 {
01270   Window *w;
01271   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01272     if (IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
01273       return w;
01274     }
01275   }
01276 
01277   return NULL;
01278 }
01279 
01283 void InitWindowSystem()
01284 {
01285   IConsoleClose();
01286 
01287   _z_back_window = NULL;
01288   _z_front_window = NULL;
01289   _focused_window = NULL;
01290   _mouseover_last_w = NULL;
01291   _scrolling_viewport = 0;
01292 
01293   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
01294 }
01295 
01299 void UnInitWindowSystem()
01300 {
01301   Window *w;
01302   FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
01303 
01304   for (w = _z_front_window; w != NULL; /* nothing */) {
01305     Window *to_del = w;
01306     w = w->z_back;
01307     free(to_del);
01308   }
01309 
01310   _z_front_window = NULL;
01311   _z_back_window = NULL;
01312 }
01313 
01317 void ResetWindowSystem()
01318 {
01319   UnInitWindowSystem();
01320   InitWindowSystem();
01321   _thd.pos.x = 0;
01322   _thd.pos.y = 0;
01323   _thd.new_pos.x = 0;
01324   _thd.new_pos.y = 0;
01325 }
01326 
01327 static void DecreaseWindowCounters()
01328 {
01329   Window *w;
01330   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01331     /* Unclick scrollbar buttons if they are pressed. */
01332     if (w->flags4 & (WF_SCROLL_DOWN | WF_SCROLL_UP)) {
01333       w->flags4 &= ~(WF_SCROLL_DOWN | WF_SCROLL_UP);
01334       w->SetDirty();
01335     }
01336     w->OnMouseLoop();
01337   }
01338 
01339   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01340     if ((w->flags4 & WF_TIMEOUT_MASK) && !(--w->flags4 & WF_TIMEOUT_MASK)) {
01341       w->OnTimeout();
01342       if (w->desc_flags & WDF_UNCLICK_BUTTONS) w->RaiseButtons(true);
01343     }
01344   }
01345 }
01346 
01347 Window *GetCallbackWnd()
01348 {
01349   return FindWindowById(_thd.window_class, _thd.window_number);
01350 }
01351 
01352 static void HandlePlacePresize()
01353 {
01354   if (_special_mouse_mode != WSM_PRESIZE) return;
01355 
01356   Window *w = GetCallbackWnd();
01357   if (w == NULL) return;
01358 
01359   Point pt = GetTileBelowCursor();
01360   if (pt.x == -1) {
01361     _thd.selend.x = -1;
01362     return;
01363   }
01364 
01365   w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
01366 }
01367 
01368 static bool HandleDragDrop()
01369 {
01370   if (_special_mouse_mode != WSM_DRAGDROP) return true;
01371   if (_left_button_down) return false;
01372 
01373   Window *w = GetCallbackWnd();
01374 
01375   if (w != NULL) {
01376     /* send an event in client coordinates. */
01377     Point pt;
01378     pt.x = _cursor.pos.x - w->left;
01379     pt.y = _cursor.pos.y - w->top;
01380     w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
01381   }
01382 
01383   ResetObjectToPlace();
01384 
01385   return false;
01386 }
01387 
01388 static bool HandleMouseOver()
01389 {
01390   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01391 
01392   /* We changed window, put a MOUSEOVER event to the last window */
01393   if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
01394     /* Reset mouse-over coordinates of previous window */
01395     Point pt = { -1, -1 };
01396     _mouseover_last_w->OnMouseOver(pt, 0);
01397   }
01398 
01399   /* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
01400   _mouseover_last_w = w;
01401 
01402   if (w != NULL) {
01403     /* send an event in client coordinates. */
01404     Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
01405     const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
01406     if (widget != NULL) w->OnMouseOver(pt, widget->index);
01407   }
01408 
01409   /* Mouseover never stops execution */
01410   return true;
01411 }
01412 
01422 void ResizeWindow(Window *w, int delta_x, int delta_y)
01423 {
01424   if (delta_x == 0 && delta_y == 0) return;
01425 
01426   w->SetDirty();
01427 
01428   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);
01429   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);
01430   assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
01431   assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
01432 
01433   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);
01434   w->width  = w->nested_root->current_x;
01435   w->height = w->nested_root->current_y;
01436   w->SetDirty();
01437 }
01438 
01443 int GetMainViewTop()
01444 {
01445   Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01446   return (w == NULL) ? 0 : w->top + w->height;
01447 }
01448 
01453 int GetMainViewBottom()
01454 {
01455   Window *w = FindWindowById(WC_STATUS_BAR, 0);
01456   return (w == NULL) ? _screen.height : w->top;
01457 }
01458 
01460 static const int MIN_VISIBLE_TITLE_BAR = 13;
01461 
01463 enum PreventHideDirection {
01464   PHD_UP,   
01465   PHD_DOWN, 
01466 };
01467 
01478 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
01479 {
01480   if (v == NULL) return;
01481 
01482   int v_bottom = v->top + v->height;
01483   int v_right = v->left + v->width;
01484   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.
01485 
01486   if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return; // Above v is enough space
01487   if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return; // Below v is enough space
01488 
01489   /* Vertically, the rectangle is hidden behind v. */
01490   if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) { // At left of v.
01491     if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // But enough room, force it to a safe position.
01492     return;
01493   }
01494   if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) { // At right of v.
01495     if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y; // Not enough room, force it to a safe position.
01496     return;
01497   }
01498 
01499   /* Horizontally also hidden, force movement to a safe area. */
01500   if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) { // Coming from the left, and enough room there.
01501     *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
01502   } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) { // Coming from the right, and enough room there.
01503     *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
01504   } else {
01505     *ny = safe_y;
01506   }
01507 }
01508 
01509 static bool _dragging_window; 
01510 
01511 static bool HandleWindowDragging()
01512 {
01513   /* Get out immediately if no window is being dragged at all. */
01514   if (!_dragging_window) return true;
01515 
01516   /* Otherwise find the window... */
01517   Window *w;
01518   FOR_ALL_WINDOWS_FROM_BACK(w) {
01519     if (w->flags4 & WF_DRAGGING) {
01520       /* Stop the dragging if the left mouse button was released */
01521       if (!_left_button_down) {
01522         w->flags4 &= ~WF_DRAGGING;
01523         break;
01524       }
01525 
01526       w->SetDirty();
01527 
01528       int x = _cursor.pos.x + _drag_delta.x;
01529       int y = _cursor.pos.y + _drag_delta.y;
01530       int nx = x;
01531       int ny = y;
01532 
01533       if (_settings_client.gui.window_snap_radius != 0) {
01534         const Window *v;
01535 
01536         int hsnap = _settings_client.gui.window_snap_radius;
01537         int vsnap = _settings_client.gui.window_snap_radius;
01538         int delta;
01539 
01540         FOR_ALL_WINDOWS_FROM_BACK(v) {
01541           if (v == w) continue; // Don't snap at yourself
01542 
01543           if (y + w->height > v->top && y < v->top + v->height) {
01544             /* Your left border <-> other right border */
01545             delta = abs(v->left + v->width - x);
01546             if (delta <= hsnap) {
01547               nx = v->left + v->width;
01548               hsnap = delta;
01549             }
01550 
01551             /* Your right border <-> other left border */
01552             delta = abs(v->left - x - w->width);
01553             if (delta <= hsnap) {
01554               nx = v->left - w->width;
01555               hsnap = delta;
01556             }
01557           }
01558 
01559           if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
01560             /* Your left border <-> other left border */
01561             delta = abs(v->left - x);
01562             if (delta <= hsnap) {
01563               nx = v->left;
01564               hsnap = delta;
01565             }
01566 
01567             /* Your right border <-> other right border */
01568             delta = abs(v->left + v->width - x - w->width);
01569             if (delta <= hsnap) {
01570               nx = v->left + v->width - w->width;
01571               hsnap = delta;
01572             }
01573           }
01574 
01575           if (x + w->width > v->left && x < v->left + v->width) {
01576             /* Your top border <-> other bottom border */
01577             delta = abs(v->top + v->height - y);
01578             if (delta <= vsnap) {
01579               ny = v->top + v->height;
01580               vsnap = delta;
01581             }
01582 
01583             /* Your bottom border <-> other top border */
01584             delta = abs(v->top - y - w->height);
01585             if (delta <= vsnap) {
01586               ny = v->top - w->height;
01587               vsnap = delta;
01588             }
01589           }
01590 
01591           if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
01592             /* Your top border <-> other top border */
01593             delta = abs(v->top - y);
01594             if (delta <= vsnap) {
01595               ny = v->top;
01596               vsnap = delta;
01597             }
01598 
01599             /* Your bottom border <-> other bottom border */
01600             delta = abs(v->top + v->height - y - w->height);
01601             if (delta <= vsnap) {
01602               ny = v->top + v->height - w->height;
01603               vsnap = delta;
01604             }
01605           }
01606         }
01607       }
01608 
01609       /* Search for the title bar rectangle. */
01610       Rect caption_rect;
01611       const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
01612       assert(caption != NULL);
01613       caption_rect.left   = caption->pos_x;
01614       caption_rect.right  = caption->pos_x + caption->current_x;
01615       caption_rect.top    = caption->pos_y;
01616       caption_rect.bottom = caption->pos_y + caption->current_y;
01617 
01618       /* Make sure the window doesn't leave the screen */
01619       nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
01620       ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
01621 
01622       /* Make sure the title bar isn't hidden behind the main tool bar or the status bar. */
01623       PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
01624       PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR,   0), w->left, PHD_UP);
01625 
01626       if (w->viewport != NULL) {
01627         w->viewport->left += nx - w->left;
01628         w->viewport->top  += ny - w->top;
01629       }
01630       w->left = nx;
01631       w->top  = ny;
01632 
01633       w->SetDirty();
01634       return false;
01635     } else if (w->flags4 & WF_SIZING) {
01636       /* Stop the sizing if the left mouse button was released */
01637       if (!_left_button_down) {
01638         w->flags4 &= ~WF_SIZING;
01639         w->SetDirty();
01640         break;
01641       }
01642 
01643       /* Compute difference in pixels between cursor position and reference point in the window.
01644        * If resizing the left edge of the window, moving to the left makes the window bigger not smaller.
01645        */
01646       int x, y = _cursor.pos.y - _drag_delta.y;
01647       if (w->flags4 & WF_SIZING_LEFT) {
01648         x = _drag_delta.x - _cursor.pos.x;
01649       } else {
01650         x = _cursor.pos.x - _drag_delta.x;
01651       }
01652 
01653       /* resize.step_width and/or resize.step_height may be 0, which means no resize is possible. */
01654       if (w->resize.step_width  == 0) x = 0;
01655       if (w->resize.step_height == 0) y = 0;
01656 
01657       /* X and Y has to go by step.. calculate it.
01658        * The cast to int is necessary else x/y are implicitly casted to
01659        * unsigned int, which won't work. */
01660       if (w->resize.step_width  > 1) x -= x % (int)w->resize.step_width;
01661       if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
01662 
01663       /* Check that we don't go below the minimum set size */
01664       if ((int)w->width + x < (int)w->nested_root->smallest_x) {
01665         x = w->nested_root->smallest_x - w->width;
01666       }
01667       if ((int)w->height + y < (int)w->nested_root->smallest_y) {
01668         y = w->nested_root->smallest_y - w->height;
01669       }
01670 
01671       /* Window already on size */
01672       if (x == 0 && y == 0) return false;
01673 
01674       /* Now find the new cursor pos.. this is NOT _cursor, because we move in steps. */
01675       _drag_delta.y += y;
01676       if ((w->flags4 & WF_SIZING_LEFT) && x != 0) {
01677         _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position.
01678         w->SetDirty();
01679         w->left -= x;  // If dragging left edge, move left window edge in opposite direction by the same amount.
01680         /* ResizeWindow() below ensures marking new position as dirty. */
01681       } else {
01682         _drag_delta.x += x;
01683       }
01684 
01685       /* ResizeWindow sets both pre- and after-size to dirty for redrawal */
01686       ResizeWindow(w, x, y);
01687       w->OnResize();
01688       return false;
01689     }
01690   }
01691 
01692   _dragging_window = false;
01693   return false;
01694 }
01695 
01700 static void StartWindowDrag(Window *w)
01701 {
01702   w->flags4 |= WF_DRAGGING;
01703   _dragging_window = true;
01704 
01705   _drag_delta.x = w->left - _cursor.pos.x;
01706   _drag_delta.y = w->top  - _cursor.pos.y;
01707 
01708   BringWindowToFront(w);
01709   DeleteWindowById(WC_DROPDOWN_MENU, 0);
01710 }
01711 
01717 static void StartWindowSizing(Window *w, bool to_left)
01718 {
01719   w->flags4 |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
01720   _dragging_window = true;
01721 
01722   _drag_delta.x = _cursor.pos.x;
01723   _drag_delta.y = _cursor.pos.y;
01724 
01725   BringWindowToFront(w);
01726   DeleteWindowById(WC_DROPDOWN_MENU, 0);
01727 }
01728 
01729 
01730 static bool HandleScrollbarScrolling()
01731 {
01732   Window *w;
01733 
01734   /* Get out quickly if no item is being scrolled */
01735   if (!_scrolling_scrollbar) return true;
01736 
01737   /* Find the scrolling window */
01738   FOR_ALL_WINDOWS_FROM_BACK(w) {
01739     if (w->flags4 & WF_SCROLL_MIDDLE) {
01740       /* Abort if no button is clicked any more. */
01741       if (!_left_button_down) {
01742         w->flags4 &= ~WF_SCROLL_MIDDLE;
01743         w->SetDirty();
01744         break;
01745       }
01746 
01747       int i;
01748       Scrollbar *sb;
01749       bool rtl = false;
01750 
01751       if (w->flags4 & WF_HSCROLL) {
01752         sb = &w->hscroll;
01753         i = _cursor.pos.x - _cursorpos_drag_start.x;
01754         rtl = _dynlang.text_dir == TD_RTL;
01755       } else if (w->flags4 & WF_SCROLL2) {
01756         sb = &w->vscroll2;
01757         i = _cursor.pos.y - _cursorpos_drag_start.y;
01758       } else {
01759         sb = &w->vscroll;
01760         i = _cursor.pos.y - _cursorpos_drag_start.y;
01761       }
01762 
01763       /* Find the item we want to move to and make sure it's inside bounds. */
01764       int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
01765       if (rtl) pos = sb->GetCount() - sb->GetCapacity() - pos;
01766       if (pos != sb->GetPosition()) {
01767         sb->SetPosition(pos);
01768         w->SetDirty();
01769       }
01770       return false;
01771     }
01772   }
01773 
01774   _scrolling_scrollbar = false;
01775   return false;
01776 }
01777 
01778 static bool HandleViewportScroll()
01779 {
01780   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
01781 
01782   if (!_scrolling_viewport) return true;
01783 
01784   Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01785 
01786   if (!(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) || w == NULL) {
01787     _cursor.fix_at = false;
01788     _scrolling_viewport = false;
01789     return true;
01790   }
01791 
01792   if (w == FindWindowById(WC_MAIN_WINDOW, 0) && w->viewport->follow_vehicle != INVALID_VEHICLE) {
01793     /* If the main window is following a vehicle, then first let go of it! */
01794     const Vehicle *veh = Vehicle::Get(w->viewport->follow_vehicle);
01795     ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
01796     return true;
01797   }
01798 
01799   Point delta;
01800   if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
01801     delta.x = -_cursor.delta.x;
01802     delta.y = -_cursor.delta.y;
01803   } else {
01804     delta.x = _cursor.delta.x;
01805     delta.y = _cursor.delta.y;
01806   }
01807 
01808   if (scrollwheel_scrolling) {
01809     /* We are using scrollwheels for scrolling */
01810     delta.x = _cursor.h_wheel;
01811     delta.y = _cursor.v_wheel;
01812     _cursor.v_wheel = 0;
01813     _cursor.h_wheel = 0;
01814   }
01815 
01816   /* Create a scroll-event and send it to the window */
01817   if (delta.x != 0 || delta.y != 0) w->OnScroll(delta);
01818 
01819   _cursor.delta.x = 0;
01820   _cursor.delta.y = 0;
01821   return false;
01822 }
01823 
01832 static bool MaybeBringWindowToFront(Window *w)
01833 {
01834   bool bring_to_front = false;
01835 
01836   if (w->window_class == WC_MAIN_WINDOW ||
01837       IsVitalWindow(w) ||
01838       w->window_class == WC_TOOLTIPS ||
01839       w->window_class == WC_DROPDOWN_MENU) {
01840     return true;
01841   }
01842 
01843   /* Use unshaded window size rather than current size for shaded windows. */
01844   int w_width  = w->width;
01845   int w_height = w->height;
01846   if (w->IsShaded()) {
01847     w_width  = w->unshaded_size.width;
01848     w_height = w->unshaded_size.height;
01849   }
01850 
01851   Window *u;
01852   FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
01853     /* A modal child will prevent the activation of the parent window */
01854     if (u->parent == w && (u->desc_flags & WDF_MODAL)) {
01855       u->flags4 |= WF_WHITE_BORDER_MASK;
01856       u->SetDirty();
01857       return false;
01858     }
01859 
01860     if (u->window_class == WC_MAIN_WINDOW ||
01861         IsVitalWindow(u) ||
01862         u->window_class == WC_TOOLTIPS ||
01863         u->window_class == WC_DROPDOWN_MENU) {
01864       continue;
01865     }
01866 
01867     /* Window sizes don't interfere, leave z-order alone */
01868     if (w->left + w_width <= u->left ||
01869         u->left + u->width <= w->left ||
01870         w->top  + w_height <= u->top ||
01871         u->top + u->height <= w->top) {
01872       continue;
01873     }
01874 
01875     bring_to_front = true;
01876   }
01877 
01878   if (bring_to_front) BringWindowToFront(w);
01879   return true;
01880 }
01881 
01885 void HandleKeypress(uint32 raw_key)
01886 {
01887   /*
01888    * During the generation of the world, there might be
01889    * another thread that is currently building for example
01890    * a road. To not interfere with those tasks, we should
01891    * NOT change the _current_company here.
01892    *
01893    * This is not necessary either, as the only events that
01894    * can be handled are the 'close application' events
01895    */
01896   if (!IsGeneratingWorld()) _current_company = _local_company;
01897 
01898   /* Setup event */
01899   uint16 key     = GB(raw_key,  0, 16);
01900   uint16 keycode = GB(raw_key, 16, 16);
01901 
01902   /*
01903    * The Unicode standard defines an area called the private use area. Code points in this
01904    * area are reserved for private use and thus not portable between systems. For instance,
01905    * Apple defines code points for the arrow keys in this area, but these are only printable
01906    * on a system running OS X. We don't want these keys to show up in text fields and such,
01907    * and thus we have to clear the unicode character when we encounter such a key.
01908    */
01909   if (key >= 0xE000 && key <= 0xF8FF) key = 0;
01910 
01911   /*
01912    * If both key and keycode is zero, we don't bother to process the event.
01913    */
01914   if (key == 0 && keycode == 0) return;
01915 
01916   /* Check if the focused window has a focused editbox */
01917   if (EditBoxInGlobalFocus()) {
01918     /* All input will in this case go to the focused window */
01919     if (_focused_window->OnKeyPress(key, keycode) == Window::ES_HANDLED) return;
01920   }
01921 
01922   /* Call the event, start with the uppermost window, but ignore the toolbar. */
01923   Window *w;
01924   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01925     if (w->window_class == WC_MAIN_TOOLBAR) continue;
01926     if (w->OnKeyPress(key, keycode) == Window::ES_HANDLED) return;
01927   }
01928 
01929   w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01930   /* When there is no toolbar w is null, check for that */
01931   if (w != NULL) w->OnKeyPress(key, keycode);
01932 }
01933 
01937 void HandleCtrlChanged()
01938 {
01939   /* Call the event, start with the uppermost window. */
01940   Window *w;
01941   FOR_ALL_WINDOWS_FROM_FRONT(w) {
01942     if (w->OnCTRLStateChange() == Window::ES_HANDLED) return;
01943   }
01944 }
01945 
01952 static int _input_events_this_tick = 0;
01953 
01958 static void HandleAutoscroll()
01959 {
01960   if (_settings_client.gui.autoscroll && _game_mode != GM_MENU && !IsGeneratingWorld()) {
01961     int x = _cursor.pos.x;
01962     int y = _cursor.pos.y;
01963     Window *w = FindWindowFromPt(x, y);
01964     if (w == NULL || w->flags4 & WF_DISABLE_VP_SCROLL) return;
01965     ViewPort *vp = IsPtInWindowViewport(w, x, y);
01966     if (vp != NULL) {
01967       x -= vp->left;
01968       y -= vp->top;
01969 
01970       /* here allows scrolling in both x and y axis */
01971 #define scrollspeed 3
01972       if (x - 15 < 0) {
01973         w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
01974       } else if (15 - (vp->width - x) > 0) {
01975         w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
01976       }
01977       if (y - 15 < 0) {
01978         w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
01979       } else if (15 - (vp->height - y) > 0) {
01980         w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
01981       }
01982 #undef scrollspeed
01983     }
01984   }
01985 }
01986 
01987 enum MouseClick {
01988   MC_NONE = 0,
01989   MC_LEFT,
01990   MC_RIGHT,
01991   MC_DOUBLE_LEFT,
01992 
01993   MAX_OFFSET_DOUBLE_CLICK = 5,     
01994   TIME_BETWEEN_DOUBLE_CLICK = 500, 
01995 };
01996 
01997 extern bool VpHandlePlaceSizingDrag();
01998 
01999 static void ScrollMainViewport(int x, int y)
02000 {
02001   if (_game_mode != GM_MENU) {
02002     Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
02003     assert(w);
02004 
02005     w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
02006     w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
02007   }
02008 }
02009 
02019 static const int8 scrollamt[16][2] = {
02020   { 0,  0}, 
02021   {-2,  0}, 
02022   { 0, -2}, 
02023   {-2, -1}, 
02024   { 2,  0}, 
02025   { 0,  0}, 
02026   { 2, -1}, 
02027   { 0, -2}, 
02028   { 0,  2}, 
02029   {-2,  1}, 
02030   { 0,  0}, 
02031   {-2,  0}, 
02032   { 2,  1}, 
02033   { 0,  2}, 
02034   { 2,  0}, 
02035   { 0,  0}, 
02036 };
02037 
02038 static void HandleKeyScrolling()
02039 {
02040   /*
02041    * Check that any of the dirkeys is pressed and that the focused window
02042    * dont has an edit-box as focused widget.
02043    */
02044   if (_dirkeys && !EditBoxInGlobalFocus()) {
02045     int factor = _shift_pressed ? 50 : 10;
02046     ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
02047   }
02048 }
02049 
02050 static void MouseLoop(MouseClick click, int mousewheel)
02051 {
02052   HandlePlacePresize();
02053   UpdateTileSelection();
02054 
02055   if (!VpHandlePlaceSizingDrag())  return;
02056   if (!HandleDragDrop())           return;
02057   if (!HandleWindowDragging())     return;
02058   if (!HandleScrollbarScrolling()) return;
02059   if (!HandleViewportScroll())     return;
02060   if (!HandleMouseOver())          return;
02061 
02062   bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02063   if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
02064 
02065   int x = _cursor.pos.x;
02066   int y = _cursor.pos.y;
02067   Window *w = FindWindowFromPt(x, y);
02068   if (w == NULL) return;
02069 
02070   if (!MaybeBringWindowToFront(w)) return;
02071   ViewPort *vp = IsPtInWindowViewport(w, x, y);
02072 
02073   /* Don't allow any action in a viewport if either in menu of in generating world */
02074   if (vp != NULL && (_game_mode == GM_MENU || IsGeneratingWorld())) return;
02075 
02076   if (mousewheel != 0) {
02077     if (_settings_client.gui.scrollwheel_scrolling == 0) {
02078       /* Send mousewheel event to window */
02079       w->OnMouseWheel(mousewheel);
02080     }
02081 
02082     /* Dispatch a MouseWheelEvent for widgets if it is not a viewport */
02083     if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
02084   }
02085 
02086   if (vp != NULL) {
02087     if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
02088     switch (click) {
02089       case MC_DOUBLE_LEFT:
02090       case MC_LEFT:
02091         DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
02092         if (_thd.place_mode != HT_NONE &&
02093             /* query button and place sign button work in pause mode */
02094             _cursor.sprite != SPR_CURSOR_QUERY &&
02095             _cursor.sprite != SPR_CURSOR_SIGN &&
02096             _pause_mode != PM_UNPAUSED &&
02097             !_cheats.build_in_pause.value) {
02098           return;
02099         }
02100 
02101         if (_thd.place_mode == HT_NONE) {
02102           if (!HandleViewportClicked(vp, x, y) &&
02103               !(w->flags4 & WF_DISABLE_VP_SCROLL) &&
02104               _settings_client.gui.left_mouse_btn_scrolling) {
02105             _scrolling_viewport = true;
02106             _cursor.fix_at = false;
02107           }
02108         } else {
02109           PlaceObject();
02110         }
02111         break;
02112 
02113       case MC_RIGHT:
02114         if (!(w->flags4 & WF_DISABLE_VP_SCROLL)) {
02115           _scrolling_viewport = true;
02116           _cursor.fix_at = true;
02117         }
02118         break;
02119 
02120       default:
02121         break;
02122     }
02123   } else {
02124     switch (click) {
02125       case MC_LEFT:
02126       case MC_DOUBLE_LEFT:
02127         DispatchLeftClickEvent(w, x - w->left, y - w->top, false);
02128         if (click == MC_DOUBLE_LEFT && _mouseover_last_w != NULL) {
02129           /* Issue the doubleclick, if the window was not removed */
02130           DispatchLeftClickEvent(w, x - w->left, y - w->top, true);
02131         }
02132         break;
02133 
02134       default:
02135         if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
02136         /* We try to use the scrollwheel to scroll since we didn't touch any of the buttons.
02137          * Simulate a right button click so we can get started. */
02138 
02139         /* fallthough */
02140       case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
02141     }
02142   }
02143 }
02144 
02148 void HandleMouseEvents()
02149 {
02150   static int double_click_time = 0;
02151   static int double_click_x = 0;
02152   static int double_click_y = 0;
02153 
02154   /*
02155    * During the generation of the world, there might be
02156    * another thread that is currently building for example
02157    * a road. To not interfere with those tasks, we should
02158    * NOT change the _current_company here.
02159    *
02160    * This is not necessary either, as the only events that
02161    * can be handled are the 'close application' events
02162    */
02163   if (!IsGeneratingWorld()) _current_company = _local_company;
02164 
02165   /* Mouse event? */
02166   MouseClick click = MC_NONE;
02167   if (_left_button_down && !_left_button_clicked) {
02168     click = MC_LEFT;
02169     if (double_click_time != 0 && _realtime_tick - double_click_time   < TIME_BETWEEN_DOUBLE_CLICK &&
02170         double_click_x != 0    && abs(_cursor.pos.x - double_click_x) < MAX_OFFSET_DOUBLE_CLICK  &&
02171         double_click_y != 0    && abs(_cursor.pos.y - double_click_y) < MAX_OFFSET_DOUBLE_CLICK) {
02172       click = MC_DOUBLE_LEFT;
02173     }
02174     double_click_time = _realtime_tick;
02175     double_click_x = _cursor.pos.x;
02176     double_click_y = _cursor.pos.y;
02177     _left_button_clicked = true;
02178     _input_events_this_tick++;
02179   } else if (_right_button_clicked) {
02180     _right_button_clicked = false;
02181     click = MC_RIGHT;
02182     _input_events_this_tick++;
02183   }
02184 
02185   int mousewheel = 0;
02186   if (_cursor.wheel) {
02187     mousewheel = _cursor.wheel;
02188     _cursor.wheel = 0;
02189     _input_events_this_tick++;
02190   }
02191 
02192   MouseLoop(click, mousewheel);
02193 
02194   /* We have moved the mouse the required distance,
02195    * no need to move it at any later time. */
02196   _cursor.delta.x = 0;
02197   _cursor.delta.y = 0;
02198 }
02199 
02203 static void CheckSoftLimit()
02204 {
02205   if (_settings_client.gui.window_soft_limit == 0) return;
02206 
02207   for (;;) {
02208     uint deletable_count = 0;
02209     Window *w, *last_deletable = NULL;
02210     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02211       if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags4 & WF_STICKY)) continue;
02212 
02213       last_deletable = w;
02214       deletable_count++;
02215     }
02216 
02217     /* We've ot reached the soft limit yet */
02218     if (deletable_count <= _settings_client.gui.window_soft_limit) break;
02219 
02220     assert(last_deletable != NULL);
02221     delete last_deletable;
02222   }
02223 }
02224 
02228 void InputLoop()
02229 {
02230   CheckSoftLimit();
02231   HandleKeyScrolling();
02232 
02233   /* Do the actual free of the deleted windows. */
02234   for (Window *v = _z_front_window; v != NULL; /* nothing */) {
02235     Window *w = v;
02236     v = v->z_back;
02237 
02238     if (w->window_class != WC_INVALID) continue;
02239 
02240     /* Find the window in the z-array, and effectively remove it
02241      * by moving all windows after it one to the left. This must be
02242      * done before removing the child so we cannot cause recursion
02243      * between the deletion of the parent and the child. */
02244     if (w->z_front == NULL) {
02245       _z_front_window = w->z_back;
02246     } else {
02247       w->z_front->z_back = w->z_back;
02248     }
02249     if (w->z_back == NULL) {
02250       _z_back_window  = w->z_front;
02251     } else {
02252       w->z_back->z_front = w->z_front;
02253     }
02254     free(w);
02255   }
02256 
02257   DecreaseWindowCounters();
02258 
02259   if (_input_events_this_tick != 0) {
02260     /* The input loop is called only once per GameLoop() - so we can clear the counter here */
02261     _input_events_this_tick = 0;
02262     /* there were some inputs this tick, don't scroll ??? */
02263     return;
02264   }
02265 
02266   /* HandleMouseEvents was already called for this tick */
02267   HandleMouseEvents();
02268   HandleAutoscroll();
02269 }
02270 
02274 void UpdateWindows()
02275 {
02276   Window *w;
02277   static int we4_timer = 0;
02278   int t = we4_timer + 1;
02279 
02280   if (t >= 100) {
02281     FOR_ALL_WINDOWS_FROM_FRONT(w) {
02282       w->OnHundredthTick();
02283     }
02284     t = 0;
02285   }
02286   we4_timer = t;
02287 
02288   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02289     if (w->flags4 & WF_WHITE_BORDER_MASK) {
02290       w->flags4 -= WF_WHITE_BORDER_ONE;
02291 
02292       if (!(w->flags4 & WF_WHITE_BORDER_MASK)) w->SetDirty();
02293     }
02294   }
02295 
02296   DrawDirtyBlocks();
02297 
02298   FOR_ALL_WINDOWS_FROM_BACK(w) {
02299     /* Update viewport only if window is not shaded. */
02300     if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
02301   }
02302   NetworkDrawChatMessage();
02303   /* Redraw mouse cursor in case it was hidden */
02304   DrawMouseCursor();
02305 }
02306 
02312 void SetWindowDirty(WindowClass cls, WindowNumber number)
02313 {
02314   const Window *w;
02315   FOR_ALL_WINDOWS_FROM_BACK(w) {
02316     if (w->window_class == cls && w->window_number == number) w->SetDirty();
02317   }
02318 }
02319 
02326 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
02327 {
02328   const Window *w;
02329   FOR_ALL_WINDOWS_FROM_BACK(w) {
02330     if (w->window_class == cls && w->window_number == number) {
02331       w->SetWidgetDirty(widget_index);
02332     }
02333   }
02334 }
02335 
02340 void SetWindowClassesDirty(WindowClass cls)
02341 {
02342   Window *w;
02343   FOR_ALL_WINDOWS_FROM_BACK(w) {
02344     if (w->window_class == cls) w->SetDirty();
02345   }
02346 }
02347 
02354 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data)
02355 {
02356   Window *w;
02357   FOR_ALL_WINDOWS_FROM_BACK(w) {
02358     if (w->window_class == cls && w->window_number == number) w->InvalidateData(data);
02359   }
02360 }
02361 
02367 void InvalidateWindowClassesData(WindowClass cls, int data)
02368 {
02369   Window *w;
02370 
02371   FOR_ALL_WINDOWS_FROM_BACK(w) {
02372     if (w->window_class == cls) w->InvalidateData(data);
02373   }
02374 }
02375 
02379 void CallWindowTickEvent()
02380 {
02381   if (_scroller_click_timeout > 3) {
02382     _scroller_click_timeout -= 3;
02383   } else {
02384     _scroller_click_timeout = 0;
02385   }
02386 
02387   Window *w;
02388   FOR_ALL_WINDOWS_FROM_FRONT(w) {
02389     w->OnTick();
02390   }
02391 }
02392 
02399 void DeleteNonVitalWindows()
02400 {
02401   Window *w;
02402 
02403 restart_search:
02404   /* When we find the window to delete, we need to restart the search
02405    * as deleting this window could cascade in deleting (many) others
02406    * anywhere in the z-array */
02407   FOR_ALL_WINDOWS_FROM_BACK(w) {
02408     if (w->window_class != WC_MAIN_WINDOW &&
02409         w->window_class != WC_SELECT_GAME &&
02410         w->window_class != WC_MAIN_TOOLBAR &&
02411         w->window_class != WC_STATUS_BAR &&
02412         w->window_class != WC_TOOLBAR_MENU &&
02413         w->window_class != WC_TOOLTIPS &&
02414         (w->flags4 & WF_STICKY) == 0) { // do not delete windows which are 'pinned'
02415 
02416       delete w;
02417       goto restart_search;
02418     }
02419   }
02420 }
02421 
02427 void DeleteAllNonVitalWindows()
02428 {
02429   Window *w;
02430 
02431   /* Delete every window except for stickied ones, then sticky ones as well */
02432   DeleteNonVitalWindows();
02433 
02434 restart_search:
02435   /* When we find the window to delete, we need to restart the search
02436    * as deleting this window could cascade in deleting (many) others
02437    * anywhere in the z-array */
02438   FOR_ALL_WINDOWS_FROM_BACK(w) {
02439     if (w->flags4 & WF_STICKY) {
02440       delete w;
02441       goto restart_search;
02442     }
02443   }
02444 }
02445 
02450 void DeleteConstructionWindows()
02451 {
02452   Window *w;
02453 
02454 restart_search:
02455   /* When we find the window to delete, we need to restart the search
02456    * as deleting this window could cascade in deleting (many) others
02457    * anywhere in the z-array */
02458   FOR_ALL_WINDOWS_FROM_BACK(w) {
02459     if (w->desc_flags & WDF_CONSTRUCTION) {
02460       delete w;
02461       goto restart_search;
02462     }
02463   }
02464 
02465   FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
02466 }
02467 
02469 void HideVitalWindows()
02470 {
02471   DeleteWindowById(WC_TOOLBAR_MENU, 0);
02472   DeleteWindowById(WC_MAIN_TOOLBAR, 0);
02473   DeleteWindowById(WC_STATUS_BAR, 0);
02474 }
02475 
02477 void ReInitAllWindows()
02478 {
02479   NWidgetLeaf::InvalidateDimensionCache(); // Reset cached sizes of several widgets.
02480 
02481   Window *w;
02482   FOR_ALL_WINDOWS_FROM_BACK(w) {
02483     w->ReInit();
02484   }
02485 
02486   /* Make sure essential parts of all windows are visible */
02487   RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
02488   MarkWholeScreenDirty();
02489 }
02490 
02496 int PositionMainToolbar(Window *w)
02497 {
02498   DEBUG(misc, 5, "Repositioning Main Toolbar...");
02499 
02500   if (w == NULL || w->window_class != WC_MAIN_TOOLBAR) {
02501     w = FindWindowById(WC_MAIN_TOOLBAR, 0);
02502   }
02503 
02504   switch (_settings_client.gui.toolbar_pos) {
02505     case 1:  w->left = (_screen.width - w->width) / 2; break;
02506     case 2:  w->left = _screen.width - w->width; break;
02507     default: w->left = 0;
02508   }
02509   SetDirtyBlocks(0, 0, _screen.width, w->height); // invalidate the whole top part
02510   return w->left;
02511 }
02512 
02513 
02519 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
02520 {
02521   Window *w;
02522   FOR_ALL_WINDOWS_FROM_BACK(w) {
02523     if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
02524       w->viewport->follow_vehicle = to_index;
02525       w->SetDirty();
02526     }
02527   }
02528 }
02529 
02530 
02536 void RelocateAllWindows(int neww, int newh)
02537 {
02538   Window *w;
02539 
02540   FOR_ALL_WINDOWS_FROM_BACK(w) {
02541     int left, top;
02542 
02543     if (w->window_class == WC_MAIN_WINDOW) {
02544       ViewPort *vp = w->viewport;
02545       vp->width = w->width = neww;
02546       vp->height = w->height = newh;
02547       vp->virtual_width = ScaleByZoom(neww, vp->zoom);
02548       vp->virtual_height = ScaleByZoom(newh, vp->zoom);
02549       continue; // don't modify top,left
02550     }
02551 
02552     /* XXX - this probably needs something more sane. For example specying
02553      * in a 'backup'-desc that the window should always be centred. */
02554     switch (w->window_class) {
02555       case WC_MAIN_TOOLBAR:
02556         if (neww - w->width != 0) {
02557           ResizeWindow(w, min(neww, 640) - w->width, 0);
02558           w->OnResize();
02559         }
02560 
02561         top = w->top;
02562         left = PositionMainToolbar(w); // changes toolbar orientation
02563         break;
02564 
02565       case WC_SELECT_GAME:
02566       case WC_GAME_OPTIONS:
02567       case WC_NETWORK_WINDOW:
02568         top = (newh - w->height) >> 1;
02569         left = (neww - w->width) >> 1;
02570         break;
02571 
02572       case WC_NEWS_WINDOW:
02573         top = newh - w->height;
02574         left = (neww - w->width) >> 1;
02575         break;
02576 
02577       case WC_STATUS_BAR:
02578         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0);
02579         top = newh - w->height;
02580         left = (neww - w->width) >> 1;
02581         break;
02582 
02583       case WC_SEND_NETWORK_MSG:
02584         ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0);
02585         top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
02586         left = (neww - w->width) >> 1;
02587         break;
02588 
02589       case WC_CONSOLE:
02590         IConsoleResize(w);
02591         continue;
02592 
02593       default: {
02594         left = w->left;
02595         if (left + (w->width >> 1) >= neww) left = neww - w->width;
02596         if (left < 0) left = 0;
02597 
02598         top = w->top;
02599         if (top + (w->height >> 1) >= newh) top = newh - w->height;
02600 
02601         const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
02602         if (wt != NULL) {
02603           if (top < wt->height && wt->left < (w->left + w->width) && (wt->left + wt->width) > w->left) top = wt->height;
02604           if (top >= newh) top = newh - 1;
02605         } else {
02606           if (top < 0) top = 0;
02607         }
02608       } break;
02609     }
02610 
02611     if (w->viewport != NULL) {
02612       w->viewport->left += left - w->left;
02613       w->viewport->top += top - w->top;
02614     }
02615 
02616     w->left = left;
02617     w->top = top;
02618   }
02619 }
02620 
02625 PickerWindowBase::~PickerWindowBase()
02626 {
02627   this->window_class = WC_INVALID; // stop the ancestor from freeing the already (to be) child
02628   ResetObjectToPlace();
02629 }

Generated on Wed Jan 20 23:38:42 2010 for OpenTTD by  doxygen 1.5.6