00001
00002
00003
00004
00005
00006
00007
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
00047
00048
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
00108 if (_focused_window != NULL) {
00109 if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
00110 }
00111
00112
00113 Window *old_focused = _focused_window;
00114 _focused_window = w;
00115
00116
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
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
00143 this->nested_focus->SetDirty(this);
00144 this->nested_focus = NULL;
00145 }
00146 }
00147
00153 bool Window::SetFocusedWidget(byte widget_index)
00154 {
00155
00156 if (widget_index >= this->nested_array_size) return false;
00157
00158 assert(this->nested_array[widget_index] != NULL);
00159 if (this->nested_focus != NULL) {
00160 if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
00161
00162
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
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
00265 if (_focused_window != w &&
00266 (w->desc_flags & WDF_NO_FOCUS) == 0 &&
00267 widget_type != WWT_CLOSEBOX) {
00268 focused_widget_changed = true;
00269 if (_focused_window != NULL) {
00270 _focused_window->OnFocusLost();
00271
00272
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;
00280
00281
00282 if (nw->IsDisabled()) return;
00283
00284 int widget_index = nw->index;
00285
00286
00287 if (!double_click) {
00288
00289
00290 if (widget_type != WWT_CAPTION) {
00291
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
00297
00298
00299
00300
00301
00302
00303
00304
00305 focused_widget_changed |= w->SetFocusedWidget(widget_index);
00306 }
00307
00308
00309
00310 if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
00311
00312 switch (widget_type) {
00313
00314 case WWT_PANEL | WWB_PUSHBUTTON:
00315 case WWT_IMGBTN | WWB_PUSHBUTTON:
00316 case WWT_TEXTBTN | WWB_PUSHBUTTON:
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) {
00328
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:
00337 delete w;
00338 return;
00339
00340 case WWT_CAPTION:
00341 StartWindowDrag(w);
00342 return;
00343
00344 case WWT_RESIZEBOX:
00345
00346
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
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
00389 if (wid == NULL) return;
00390
00391
00392 if (wid->tool_tip != 0) {
00393 GuiShowTooltips(wid->tool_tip);
00394 return;
00395 }
00396
00397
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
00416 if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
00417 w->SetShaded(!w->IsShaded());
00418 return;
00419 }
00420
00421
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
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
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
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();
00534
00535
00536 int window_width = this->width;
00537 int window_height = this->height;
00538
00539 this->OnInit();
00540
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
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
00554
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
00627 if (_mouseover_last_w == this) _mouseover_last_w = NULL;
00628
00629
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);
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
00701
00702
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
00721
00722
00723 FOR_ALL_WINDOWS_FROM_BACK(w) {
00724 if (w->owner == id) {
00725 delete w;
00726 goto restart_search;
00727 }
00728 }
00729
00730
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);
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
00813 for (; v != NULL && v != w && IsVitalWindow(v); v = v->z_back) { }
00814
00815 if (v == NULL || w == v) return;
00816
00817
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
00852 this->window_class = cls;
00853 this->flags4 |= WF_WHITE_BORDER_MASK;
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
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
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
00871
00872 this->resize.step_width = this->nested_root->resize_x;
00873 this->resize.step_height = this->nested_root->resize_y;
00874
00875
00876
00877
00878 if (this->window_class != WC_OSK && (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL)) SetFocusedWindow(this);
00879
00880
00881
00882
00883
00884
00885
00886
00887
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);
00949 def_height = max(def_height, this->height);
00950
00951
00952
00953
00954
00955 if (this->width != def_width || this->height != def_height) {
00956
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
00967
00968
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
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
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
01047
01048
01049 if (left < -(width>>2) || left > _screen.width - (width>>1)) return false;
01050
01051 if (top < 22 || top > _screen.height - (height>>2)) return false;
01052
01053
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
01082 if (IsGoodAutoPlace1(0, 24, width, height, pt)) return pt;
01083
01084
01085
01086
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
01103
01104
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
01116
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 &&
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:
01187 return GetToolbarAlignedWindowPosition(default_width);
01188
01189 case WDP_AUTO:
01190 return GetAutoPlacePosition(default_width, default_height);
01191
01192 case WDP_CENTER:
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 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();
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; ) {
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
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
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
01393 if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
01394
01395 Point pt = { -1, -1 };
01396 _mouseover_last_w->OnMouseOver(pt, 0);
01397 }
01398
01399
01400 _mouseover_last_w = w;
01401
01402 if (w != NULL) {
01403
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
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);
01485
01486 if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return;
01487 if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return;
01488
01489
01490 if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) {
01491 if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y;
01492 return;
01493 }
01494 if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) {
01495 if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y;
01496 return;
01497 }
01498
01499
01500 if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) {
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) {
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
01514 if (!_dragging_window) return true;
01515
01516
01517 Window *w;
01518 FOR_ALL_WINDOWS_FROM_BACK(w) {
01519 if (w->flags4 & WF_DRAGGING) {
01520
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;
01542
01543 if (y + w->height > v->top && y < v->top + v->height) {
01544
01545 delta = abs(v->left + v->width - x);
01546 if (delta <= hsnap) {
01547 nx = v->left + v->width;
01548 hsnap = delta;
01549 }
01550
01551
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
01561 delta = abs(v->left - x);
01562 if (delta <= hsnap) {
01563 nx = v->left;
01564 hsnap = delta;
01565 }
01566
01567
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
01577 delta = abs(v->top + v->height - y);
01578 if (delta <= vsnap) {
01579 ny = v->top + v->height;
01580 vsnap = delta;
01581 }
01582
01583
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
01593 delta = abs(v->top - y);
01594 if (delta <= vsnap) {
01595 ny = v->top;
01596 vsnap = delta;
01597 }
01598
01599
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
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
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
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
01637 if (!_left_button_down) {
01638 w->flags4 &= ~WF_SIZING;
01639 w->SetDirty();
01640 break;
01641 }
01642
01643
01644
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
01654 if (w->resize.step_width == 0) x = 0;
01655 if (w->resize.step_height == 0) y = 0;
01656
01657
01658
01659
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
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
01672 if (x == 0 && y == 0) return false;
01673
01674
01675 _drag_delta.y += y;
01676 if ((w->flags4 & WF_SIZING_LEFT) && x != 0) {
01677 _drag_delta.x -= x;
01678 w->SetDirty();
01679 w->left -= x;
01680
01681 } else {
01682 _drag_delta.x += x;
01683 }
01684
01685
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
01735 if (!_scrolling_scrollbar) return true;
01736
01737
01738 FOR_ALL_WINDOWS_FROM_BACK(w) {
01739 if (w->flags4 & WF_SCROLL_MIDDLE) {
01740
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
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
01794 const Vehicle *veh = Vehicle::Get(w->viewport->follow_vehicle);
01795 ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true);
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
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
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
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
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
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
01889
01890
01891
01892
01893
01894
01895
01896 if (!IsGeneratingWorld()) _current_company = _local_company;
01897
01898
01899 uint16 key = GB(raw_key, 0, 16);
01900 uint16 keycode = GB(raw_key, 16, 16);
01901
01902
01903
01904
01905
01906
01907
01908
01909 if (key >= 0xE000 && key <= 0xF8FF) key = 0;
01910
01911
01912
01913
01914 if (key == 0 && keycode == 0) return;
01915
01916
01917 if (EditBoxInGlobalFocus()) {
01918
01919 if (_focused_window->OnKeyPress(key, keycode) == Window::ES_HANDLED) return;
01920 }
01921
01922
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
01931 if (w != NULL) w->OnKeyPress(key, keycode);
01932 }
01933
01937 void HandleCtrlChanged()
01938 {
01939
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
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
02042
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
02074 if (vp != NULL && (_game_mode == GM_MENU || IsGeneratingWorld())) return;
02075
02076 if (mousewheel != 0) {
02077 if (_settings_client.gui.scrollwheel_scrolling == 0) {
02078
02079 w->OnMouseWheel(mousewheel);
02080 }
02081
02082
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;
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
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
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
02137
02138
02139
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
02156
02157
02158
02159
02160
02161
02162
02163 if (!IsGeneratingWorld()) _current_company = _local_company;
02164
02165
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
02195
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
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
02234 for (Window *v = _z_front_window; v != NULL; ) {
02235 Window *w = v;
02236 v = v->z_back;
02237
02238 if (w->window_class != WC_INVALID) continue;
02239
02240
02241
02242
02243
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
02261 _input_events_this_tick = 0;
02262
02263 return;
02264 }
02265
02266
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
02300 if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
02301 }
02302 NetworkDrawChatMessage();
02303
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
02405
02406
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) {
02415
02416 delete w;
02417 goto restart_search;
02418 }
02419 }
02420 }
02421
02427 void DeleteAllNonVitalWindows()
02428 {
02429 Window *w;
02430
02431
02432 DeleteNonVitalWindows();
02433
02434 restart_search:
02435
02436
02437
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
02456
02457
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();
02480
02481 Window *w;
02482 FOR_ALL_WINDOWS_FROM_BACK(w) {
02483 w->ReInit();
02484 }
02485
02486
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);
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;
02550 }
02551
02552
02553
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);
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;
02628 ResetObjectToPlace();
02629 }