window.cpp

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

Generated on Sat Dec 26 20:06:08 2009 for OpenTTD by  doxygen 1.5.6