00001
00002
00003
00004
00005
00006
00007
00008
00009
00012 #include "stdafx.h"
00013 #include <stdarg.h>
00014 #include "company_func.h"
00015 #include "gfx_func.h"
00016 #include "console_func.h"
00017 #include "console_gui.h"
00018 #include "viewport_func.h"
00019 #include "progress.h"
00020 #include "blitter/factory.hpp"
00021 #include "zoom_func.h"
00022 #include "vehicle_base.h"
00023 #include "window_func.h"
00024 #include "tilehighlight_func.h"
00025 #include "network/network.h"
00026 #include "querystring_gui.h"
00027 #include "widgets/dropdown_func.h"
00028 #include "strings_func.h"
00029 #include "settings_type.h"
00030 #include "settings_func.h"
00031 #include "ini_type.h"
00032 #include "newgrf_debug.h"
00033 #include "hotkeys.h"
00034 #include "toolbar_gui.h"
00035 #include "statusbar_gui.h"
00036 #include "error.h"
00037 #include "game/game.hpp"
00038
00040 enum ViewportAutoscrolling {
00041 VA_DISABLED,
00042 VA_MAIN_VIEWPORT_FULLSCREEN,
00043 VA_MAIN_VIEWPORT,
00044 VA_EVERY_VIEWPORT,
00045 };
00046
00047 static Point _drag_delta;
00048 static Window *_mouseover_last_w = NULL;
00049 static Window *_last_scroll_window = NULL;
00050
00052 Window *_z_front_window = NULL;
00054 Window *_z_back_window = NULL;
00055
00057 bool _window_highlight_colour = false;
00058
00059
00060
00061
00062
00063
00064 Window *_focused_window;
00065
00066 Point _cursorpos_drag_start;
00067
00068 int _scrollbar_start_pos;
00069 int _scrollbar_size;
00070 byte _scroller_click_timeout = 0;
00071
00072 bool _scrolling_viewport;
00073 bool _mouse_hovering;
00074
00075 SpecialMouseMode _special_mouse_mode;
00076
00081 static SmallVector<WindowDesc*, 16> *_window_descs = NULL;
00082
00084 char *_windows_file;
00085
00087 WindowDesc::WindowDesc(WindowPosition def_pos, const char *ini_key, int16 def_width, int16 def_height,
00088 WindowClass window_class, WindowClass parent_class, uint32 flags,
00089 const NWidgetPart *nwid_parts, int16 nwid_length) :
00090 default_pos(def_pos),
00091 default_width(def_width),
00092 default_height(def_height),
00093 cls(window_class),
00094 parent_cls(parent_class),
00095 ini_key(ini_key),
00096 flags(flags),
00097 nwid_parts(nwid_parts),
00098 nwid_length(nwid_length),
00099 pref_sticky(false),
00100 pref_width(0),
00101 pref_height(0)
00102 {
00103 if (_window_descs == NULL) _window_descs = new SmallVector<WindowDesc*, 16>();
00104 *_window_descs->Append() = this;
00105 }
00106
00107 WindowDesc::~WindowDesc()
00108 {
00109 }
00110
00114 void WindowDesc::LoadFromConfig()
00115 {
00116 IniFile *ini = new IniFile();
00117 ini->LoadFromDisk(_windows_file, BASE_DIR);
00118 for (WindowDesc **it = _window_descs->Begin(); it != _window_descs->End(); ++it) {
00119 if ((*it)->ini_key == NULL) continue;
00120 IniLoadWindowSettings(ini, (*it)->ini_key, *it);
00121 }
00122 delete ini;
00123 }
00124
00128 static int CDECL DescSorter(WindowDesc * const *a, WindowDesc * const *b)
00129 {
00130 if ((*a)->ini_key != NULL && (*b)->ini_key != NULL) return strcmp((*a)->ini_key, (*b)->ini_key);
00131 return ((*b)->ini_key != NULL ? 1 : 0) - ((*a)->ini_key != NULL ? 1 : 0);
00132 }
00133
00137 void WindowDesc::SaveToConfig()
00138 {
00139
00140 QSortT(_window_descs->Begin(), _window_descs->Length(), DescSorter);
00141
00142 IniFile *ini = new IniFile();
00143 ini->LoadFromDisk(_windows_file, BASE_DIR);
00144 for (WindowDesc **it = _window_descs->Begin(); it != _window_descs->End(); ++it) {
00145 if ((*it)->ini_key == NULL) continue;
00146 IniSaveWindowSettings(ini, (*it)->ini_key, *it);
00147 }
00148 ini->SaveToDisk(_windows_file);
00149 delete ini;
00150 }
00151
00155 void Window::ApplyDefaults()
00156 {
00157 if (this->nested_root != NULL && this->nested_root->GetWidgetOfType(WWT_STICKYBOX) != NULL) {
00158 if (this->window_desc->pref_sticky) this->flags |= WF_STICKY;
00159 } else {
00160
00161 this->window_desc->pref_sticky = false;
00162 }
00163 }
00164
00174 int Window::GetRowFromWidget(int clickpos, int widget, int padding, int line_height) const
00175 {
00176 const NWidgetBase *wid = this->GetWidget<NWidgetBase>(widget);
00177 if (line_height < 0) line_height = wid->resize_y;
00178 if (clickpos < (int)wid->pos_y + padding) return INT_MAX;
00179 return (clickpos - (int)wid->pos_y - padding) / line_height;
00180 }
00181
00185 void Window::DisableAllWidgetHighlight()
00186 {
00187 for (uint i = 0; i < this->nested_array_size; i++) {
00188 NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
00189 if (nwid == NULL) continue;
00190
00191 if (nwid->IsHighlighted()) {
00192 nwid->SetHighlighted(TC_INVALID);
00193 this->SetWidgetDirty(i);
00194 }
00195 }
00196
00197 CLRBITS(this->flags, WF_HIGHLIGHTED);
00198 }
00199
00205 void Window::SetWidgetHighlight(byte widget_index, TextColour highlighted_colour)
00206 {
00207 assert(widget_index < this->nested_array_size);
00208
00209 NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
00210 if (nwid == NULL) return;
00211
00212 nwid->SetHighlighted(highlighted_colour);
00213 this->SetWidgetDirty(widget_index);
00214
00215 if (highlighted_colour != TC_INVALID) {
00216
00217 this->flags |= WF_HIGHLIGHTED;
00218 } else {
00219
00220 bool valid = false;
00221 for (uint i = 0; i < this->nested_array_size; i++) {
00222 NWidgetBase *nwid = this->GetWidget<NWidgetBase>(i);
00223 if (nwid == NULL) continue;
00224 if (!nwid->IsHighlighted()) continue;
00225
00226 valid = true;
00227 }
00228
00229 if (!valid) CLRBITS(this->flags, WF_HIGHLIGHTED);
00230 }
00231 }
00232
00238 bool Window::IsWidgetHighlighted(byte widget_index) const
00239 {
00240 assert(widget_index < this->nested_array_size);
00241
00242 const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget_index);
00243 if (nwid == NULL) return false;
00244
00245 return nwid->IsHighlighted();
00246 }
00247
00255 void Window::OnDropdownClose(Point pt, int widget, int index, bool instant_close)
00256 {
00257 if (widget < 0) return;
00258
00259 if (instant_close) {
00260
00261
00262 if (GetWidgetFromPos(this, pt.x, pt.y) == widget) {
00263 this->OnDropdownSelect(widget, index);
00264 }
00265 }
00266
00267
00268 if (this->nested_array != NULL) {
00269 NWidgetCore *nwi2 = this->GetWidget<NWidgetCore>(widget);
00270 if ((nwi2->type & WWT_MASK) == NWID_BUTTON_DROPDOWN) {
00271 nwi2->disp_flags &= ~ND_DROPDOWN_ACTIVE;
00272 } else {
00273 this->RaiseWidget(widget);
00274 }
00275 } else {
00276 this->RaiseWidget(widget);
00277 }
00278 this->SetWidgetDirty(widget);
00279 }
00280
00286 const Scrollbar *Window::GetScrollbar(uint widnum) const
00287 {
00288 return this->GetWidget<NWidgetScrollbar>(widnum);
00289 }
00290
00296 Scrollbar *Window::GetScrollbar(uint widnum)
00297 {
00298 return this->GetWidget<NWidgetScrollbar>(widnum);
00299 }
00300
00306 const QueryString *Window::GetQueryString(uint widnum) const
00307 {
00308 const SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
00309 return query != this->querystrings.End() ? query->second : NULL;
00310 }
00311
00317 QueryString *Window::GetQueryString(uint widnum)
00318 {
00319 SmallMap<int, QueryString*>::Pair *query = this->querystrings.Find(widnum);
00320 return query != this->querystrings.End() ? query->second : NULL;
00321 }
00322
00323
00328 void SetFocusedWindow(Window *w)
00329 {
00330 if (_focused_window == w) return;
00331
00332
00333 if (_focused_window != NULL) {
00334 if (_focused_window->nested_focus != NULL) _focused_window->nested_focus->SetDirty(_focused_window);
00335 }
00336
00337
00338 Window *old_focused = _focused_window;
00339 _focused_window = w;
00340
00341
00342 if (old_focused != NULL) old_focused->OnFocusLost();
00343 if (_focused_window != NULL) _focused_window->OnFocus();
00344 }
00345
00351 static bool EditBoxInGlobalFocus()
00352 {
00353 if (_focused_window == NULL) return false;
00354
00355
00356 if (_focused_window->window_class == WC_CONSOLE) return true;
00357
00358 return _focused_window->nested_focus != NULL && _focused_window->nested_focus->type == WWT_EDITBOX;
00359 }
00360
00364 void Window::UnfocusFocusedWidget()
00365 {
00366 if (this->nested_focus != NULL) {
00367
00368 this->nested_focus->SetDirty(this);
00369 this->nested_focus = NULL;
00370 }
00371 }
00372
00378 bool Window::SetFocusedWidget(int widget_index)
00379 {
00380
00381 if ((uint)widget_index >= this->nested_array_size) return false;
00382
00383 assert(this->nested_array[widget_index] != NULL);
00384 if (this->nested_focus != NULL) {
00385 if (this->GetWidget<NWidgetCore>(widget_index) == this->nested_focus) return false;
00386
00387
00388 this->nested_focus->SetDirty(this);
00389 }
00390 this->nested_focus = this->GetWidget<NWidgetCore>(widget_index);
00391 return true;
00392 }
00393
00401 void CDECL Window::SetWidgetsDisabledState(bool disab_stat, int widgets, ...)
00402 {
00403 va_list wdg_list;
00404
00405 va_start(wdg_list, widgets);
00406
00407 while (widgets != WIDGET_LIST_END) {
00408 SetWidgetDisabledState(widgets, disab_stat);
00409 widgets = va_arg(wdg_list, int);
00410 }
00411
00412 va_end(wdg_list);
00413 }
00414
00420 void CDECL Window::SetWidgetsLoweredState(bool lowered_stat, int widgets, ...)
00421 {
00422 va_list wdg_list;
00423
00424 va_start(wdg_list, widgets);
00425
00426 while (widgets != WIDGET_LIST_END) {
00427 SetWidgetLoweredState(widgets, lowered_stat);
00428 widgets = va_arg(wdg_list, int);
00429 }
00430
00431 va_end(wdg_list);
00432 }
00433
00438 void Window::RaiseButtons(bool autoraise)
00439 {
00440 for (uint i = 0; i < this->nested_array_size; i++) {
00441 if (this->nested_array[i] == NULL) continue;
00442 WidgetType type = this->nested_array[i]->type;
00443 if (((type & ~WWB_PUSHBUTTON) < WWT_LAST || type == NWID_PUSHBUTTON_DROPDOWN) &&
00444 (!autoraise || (type & WWB_PUSHBUTTON) || type == WWT_EDITBOX) && this->IsWidgetLowered(i)) {
00445 this->RaiseWidget(i);
00446 this->SetWidgetDirty(i);
00447 }
00448 }
00449
00450
00451 NWidgetCore *wid = this->nested_root != NULL ? (NWidgetCore*)this->nested_root->GetWidgetOfType(WWT_DEFSIZEBOX) : NULL;
00452 if (wid != NULL) {
00453 wid->SetLowered(false);
00454 wid->SetDirty(this);
00455 }
00456 }
00457
00462 void Window::SetWidgetDirty(byte widget_index) const
00463 {
00464
00465 if (this->nested_array == NULL) return;
00466
00467 this->nested_array[widget_index]->SetDirty(this);
00468 }
00469
00475 void Window::HandleButtonClick(byte widget)
00476 {
00477 this->LowerWidget(widget);
00478 this->SetTimeout();
00479 this->SetWidgetDirty(widget);
00480 }
00481
00482 static void StartWindowDrag(Window *w);
00483 static void StartWindowSizing(Window *w, bool to_left);
00484
00492 static void DispatchLeftClickEvent(Window *w, int x, int y, int click_count)
00493 {
00494 NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
00495 WidgetType widget_type = (nw != NULL) ? nw->type : WWT_EMPTY;
00496
00497 bool focused_widget_changed = false;
00498
00499 if (_focused_window != w &&
00500 (w->window_desc->flags & WDF_NO_FOCUS) == 0 &&
00501 widget_type != WWT_CLOSEBOX) {
00502 focused_widget_changed = true;
00503 SetFocusedWindow(w);
00504 w->OnFocus();
00505 }
00506
00507 if (nw == NULL) return;
00508
00509
00510 if (nw->IsDisabled()) return;
00511
00512 int widget_index = nw->index;
00513
00514
00515
00516
00517 if (widget_type != WWT_CAPTION && w->window_class != WC_OSK) {
00518
00519
00520
00521
00522
00523
00524
00525
00526
00527 focused_widget_changed |= w->SetFocusedWidget(widget_index);
00528 }
00529
00530
00531
00532 if (HideDropDownMenu(w) == widget_index && widget_index >= 0) return;
00533
00534 if ((widget_type & ~WWB_PUSHBUTTON) < WWT_LAST && (widget_type & WWB_PUSHBUTTON)) w->HandleButtonClick(widget_index);
00535
00536 Point pt = { x, y };
00537
00538 switch (widget_type) {
00539 case NWID_VSCROLLBAR:
00540 case NWID_HSCROLLBAR:
00541 ScrollbarClickHandler(w, nw, x, y);
00542 break;
00543
00544 case WWT_EDITBOX: {
00545 QueryString *query = w->GetQueryString(widget_index);
00546 if (query != NULL) query->ClickEditBox(w, pt, widget_index, click_count, focused_widget_changed);
00547 break;
00548 }
00549
00550 case WWT_CLOSEBOX:
00551 delete w;
00552 return;
00553
00554 case WWT_CAPTION:
00555 StartWindowDrag(w);
00556 return;
00557
00558 case WWT_RESIZEBOX:
00559
00560
00561 StartWindowSizing(w, (int)nw->pos_x < (w->width / 2));
00562 nw->SetDirty(w);
00563 return;
00564
00565 case WWT_DEFSIZEBOX: {
00566 if (_ctrl_pressed) {
00567 w->window_desc->pref_width = w->width;
00568 w->window_desc->pref_height = w->height;
00569 } else {
00570 int16 def_width = max<int16>(min(w->window_desc->GetDefaultWidth(), _screen.width), w->nested_root->smallest_x);
00571 int16 def_height = max<int16>(min(w->window_desc->GetDefaultHeight(), _screen.height - 50), w->nested_root->smallest_y);
00572
00573 int dx = (w->resize.step_width == 0) ? 0 : def_width - w->width;
00574 int dy = (w->resize.step_height == 0) ? 0 : def_height - w->height;
00575
00576
00577 if (w->resize.step_width > 1) dx -= dx % (int)w->resize.step_width;
00578 if (w->resize.step_height > 1) dy -= dy % (int)w->resize.step_height;
00579 ResizeWindow(w, dx, dy, false);
00580 }
00581
00582 nw->SetLowered(true);
00583 nw->SetDirty(w);
00584 w->SetTimeout();
00585 break;
00586 }
00587
00588 case WWT_DEBUGBOX:
00589 w->ShowNewGRFInspectWindow();
00590 break;
00591
00592 case WWT_SHADEBOX:
00593 nw->SetDirty(w);
00594 w->SetShaded(!w->IsShaded());
00595 return;
00596
00597 case WWT_STICKYBOX:
00598 w->flags ^= WF_STICKY;
00599 nw->SetDirty(w);
00600 if (_ctrl_pressed) w->window_desc->pref_sticky = (w->flags & WF_STICKY) != 0;
00601 return;
00602
00603 default:
00604 break;
00605 }
00606
00607
00608 if (widget_index < 0) return;
00609
00610
00611 if (w->IsWidgetHighlighted(widget_index)) {
00612 w->SetWidgetHighlight(widget_index, TC_INVALID);
00613 Game::NewEvent(new ScriptEventWindowWidgetClick((ScriptWindow::WindowClass)w->window_class, w->window_number, widget_index));
00614 }
00615
00616 w->OnClick(pt, widget_index, click_count);
00617 }
00618
00625 static void DispatchRightClickEvent(Window *w, int x, int y)
00626 {
00627 NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00628 if (wid == NULL) return;
00629
00630
00631 if (wid->index >= 0) {
00632 Point pt = { x, y };
00633 if (w->OnRightClick(pt, wid->index)) return;
00634 }
00635
00636 if (_settings_client.gui.hover_delay == 0 && wid->tool_tip != 0) GuiShowTooltips(w, wid->tool_tip, 0, NULL, TCC_RIGHT_CLICK);
00637 }
00638
00645 static void DispatchHoverEvent(Window *w, int x, int y)
00646 {
00647 NWidgetCore *wid = w->nested_root->GetWidgetFromPos(x, y);
00648
00649
00650 if (wid == NULL) return;
00651
00652
00653 if (wid->tool_tip != 0) {
00654 GuiShowTooltips(w, wid->tool_tip);
00655 return;
00656 }
00657
00658
00659 if (wid->index < 0) return;
00660
00661 Point pt = { x, y };
00662 w->OnHover(pt, wid->index);
00663 }
00664
00672 static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel)
00673 {
00674 if (nwid == NULL) return;
00675
00676
00677 if (nwid->type == WWT_CAPTION || nwid->type == WWT_SHADEBOX) {
00678 w->SetShaded(wheel < 0);
00679 return;
00680 }
00681
00682
00683 if (nwid->type == NWID_VSCROLLBAR) {
00684 NWidgetScrollbar *sb = static_cast<NWidgetScrollbar *>(nwid);
00685 if (sb->GetCount() > sb->GetCapacity()) {
00686 sb->UpdatePosition(wheel);
00687 w->SetDirty();
00688 }
00689 return;
00690 }
00691
00692
00693 Scrollbar *sb = (nwid->scrollbar_index >= 0 ? w->GetScrollbar(nwid->scrollbar_index) : NULL);
00694 if (sb != NULL && sb->GetCount() > sb->GetCapacity()) {
00695 sb->UpdatePosition(wheel);
00696 w->SetDirty();
00697 }
00698 }
00699
00705 static bool MayBeShown(const Window *w)
00706 {
00707
00708 if (!HasModalProgress()) return true;
00709
00710 switch (w->window_class) {
00711 case WC_MAIN_WINDOW:
00712 case WC_MODAL_PROGRESS:
00713 case WC_CONFIRM_POPUP_QUERY:
00714 return true;
00715
00716 default:
00717 return false;
00718 }
00719 }
00720
00733 static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom)
00734 {
00735 const Window *v;
00736 FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) {
00737 if (MayBeShown(v) &&
00738 right > v->left &&
00739 bottom > v->top &&
00740 left < v->left + v->width &&
00741 top < v->top + v->height) {
00742
00743 int x;
00744
00745 if (left < (x = v->left)) {
00746 DrawOverlappedWindow(w, left, top, x, bottom);
00747 DrawOverlappedWindow(w, x, top, right, bottom);
00748 return;
00749 }
00750
00751 if (right > (x = v->left + v->width)) {
00752 DrawOverlappedWindow(w, left, top, x, bottom);
00753 DrawOverlappedWindow(w, x, top, right, bottom);
00754 return;
00755 }
00756
00757 if (top < (x = v->top)) {
00758 DrawOverlappedWindow(w, left, top, right, x);
00759 DrawOverlappedWindow(w, left, x, right, bottom);
00760 return;
00761 }
00762
00763 if (bottom > (x = v->top + v->height)) {
00764 DrawOverlappedWindow(w, left, top, right, x);
00765 DrawOverlappedWindow(w, left, x, right, bottom);
00766 return;
00767 }
00768
00769 return;
00770 }
00771 }
00772
00773
00774 DrawPixelInfo *dp = _cur_dpi;
00775 dp->width = right - left;
00776 dp->height = bottom - top;
00777 dp->left = left - w->left;
00778 dp->top = top - w->top;
00779 dp->pitch = _screen.pitch;
00780 dp->dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top);
00781 dp->zoom = ZOOM_LVL_NORMAL;
00782 w->OnPaint();
00783 }
00784
00793 void DrawOverlappedWindowForAll(int left, int top, int right, int bottom)
00794 {
00795 Window *w;
00796 DrawPixelInfo bk;
00797 _cur_dpi = &bk;
00798
00799 FOR_ALL_WINDOWS_FROM_BACK(w) {
00800 if (MayBeShown(w) &&
00801 right > w->left &&
00802 bottom > w->top &&
00803 left < w->left + w->width &&
00804 top < w->top + w->height) {
00805
00806 DrawOverlappedWindow(w, left, top, right, bottom);
00807 }
00808 }
00809 }
00810
00815 void Window::SetDirty() const
00816 {
00817 SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height);
00818 }
00819
00826 void Window::ReInit(int rx, int ry)
00827 {
00828 this->SetDirty();
00829
00830
00831 int window_width = this->width;
00832 int window_height = this->height;
00833
00834 this->OnInit();
00835
00836 this->nested_root->SetupSmallestSize(this, false);
00837 this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
00838 this->width = this->nested_root->smallest_x;
00839 this->height = this->nested_root->smallest_y;
00840 this->resize.step_width = this->nested_root->resize_x;
00841 this->resize.step_height = this->nested_root->resize_y;
00842
00843
00844 window_width = max(window_width + rx, this->width);
00845 window_height = max(window_height + ry, this->height);
00846 int dx = (this->resize.step_width == 0) ? 0 : window_width - this->width;
00847 int dy = (this->resize.step_height == 0) ? 0 : window_height - this->height;
00848
00849
00850 if (this->resize.step_width > 1) dx -= dx % (int)this->resize.step_width;
00851 if (this->resize.step_height > 1) dy -= dy % (int)this->resize.step_height;
00852
00853 ResizeWindow(this, dx, dy);
00854
00855 }
00856
00862 void Window::SetShaded(bool make_shaded)
00863 {
00864 if (this->shade_select == NULL) return;
00865
00866 int desired = make_shaded ? SZSP_HORIZONTAL : 0;
00867 if (this->shade_select->shown_plane != desired) {
00868 if (make_shaded) {
00869 this->unshaded_size.width = this->width;
00870 this->unshaded_size.height = this->height;
00871 this->shade_select->SetDisplayedPlane(desired);
00872 this->ReInit(0, -this->height);
00873 } else {
00874 this->shade_select->SetDisplayedPlane(desired);
00875 int dx = ((int)this->unshaded_size.width > this->width) ? (int)this->unshaded_size.width - this->width : 0;
00876 int dy = ((int)this->unshaded_size.height > this->height) ? (int)this->unshaded_size.height - this->height : 0;
00877 this->ReInit(dx, dy);
00878 }
00879 }
00880 }
00881
00888 static Window *FindChildWindow(const Window *w, WindowClass wc)
00889 {
00890 Window *v;
00891 FOR_ALL_WINDOWS_FROM_BACK(v) {
00892 if ((wc == WC_INVALID || wc == v->window_class) && v->parent == w) return v;
00893 }
00894
00895 return NULL;
00896 }
00897
00902 void Window::DeleteChildWindows(WindowClass wc) const
00903 {
00904 Window *child = FindChildWindow(this, wc);
00905 while (child != NULL) {
00906 delete child;
00907 child = FindChildWindow(this, wc);
00908 }
00909 }
00910
00914 Window::~Window()
00915 {
00916 if (_thd.window_class == this->window_class &&
00917 _thd.window_number == this->window_number) {
00918 ResetObjectToPlace();
00919 }
00920
00921
00922 if (_mouseover_last_w == this) _mouseover_last_w = NULL;
00923
00924
00925 if (_last_scroll_window == this) _last_scroll_window = NULL;
00926
00927
00928 if (_focused_window == this) _focused_window = NULL;
00929
00930 this->DeleteChildWindows();
00931
00932 if (this->viewport != NULL) DeleteWindowViewport(this);
00933
00934 this->SetDirty();
00935
00936 free(this->nested_array);
00937 delete this->nested_root;
00938
00939 this->window_class = WC_INVALID;
00940 }
00941
00948 Window *FindWindowById(WindowClass cls, WindowNumber number)
00949 {
00950 Window *w;
00951 FOR_ALL_WINDOWS_FROM_BACK(w) {
00952 if (w->window_class == cls && w->window_number == number) return w;
00953 }
00954
00955 return NULL;
00956 }
00957
00964 Window *FindWindowByClass(WindowClass cls)
00965 {
00966 Window *w;
00967 FOR_ALL_WINDOWS_FROM_BACK(w) {
00968 if (w->window_class == cls) return w;
00969 }
00970
00971 return NULL;
00972 }
00973
00980 void DeleteWindowById(WindowClass cls, WindowNumber number, bool force)
00981 {
00982 Window *w = FindWindowById(cls, number);
00983 if (force || w == NULL ||
00984 (w->flags & WF_STICKY) == 0) {
00985 delete w;
00986 }
00987 }
00988
00993 void DeleteWindowByClass(WindowClass cls)
00994 {
00995 Window *w;
00996
00997 restart_search:
00998
00999
01000
01001 FOR_ALL_WINDOWS_FROM_BACK(w) {
01002 if (w->window_class == cls) {
01003 delete w;
01004 goto restart_search;
01005 }
01006 }
01007 }
01008
01015 void DeleteCompanyWindows(CompanyID id)
01016 {
01017 Window *w;
01018
01019 restart_search:
01020
01021
01022
01023 FOR_ALL_WINDOWS_FROM_BACK(w) {
01024 if (w->owner == id) {
01025 delete w;
01026 goto restart_search;
01027 }
01028 }
01029
01030
01031 DeleteWindowById(WC_BUY_COMPANY, id);
01032 }
01033
01041 void ChangeWindowOwner(Owner old_owner, Owner new_owner)
01042 {
01043 Window *w;
01044 FOR_ALL_WINDOWS_FROM_BACK(w) {
01045 if (w->owner != old_owner) continue;
01046
01047 switch (w->window_class) {
01048 case WC_COMPANY_COLOUR:
01049 case WC_FINANCES:
01050 case WC_STATION_LIST:
01051 case WC_TRAINS_LIST:
01052 case WC_ROADVEH_LIST:
01053 case WC_SHIPS_LIST:
01054 case WC_AIRCRAFT_LIST:
01055 case WC_BUY_COMPANY:
01056 case WC_COMPANY:
01057 case WC_COMPANY_INFRASTRUCTURE:
01058 continue;
01059
01060 default:
01061 w->owner = new_owner;
01062 break;
01063 }
01064 }
01065 }
01066
01067 static void BringWindowToFront(Window *w);
01068
01076 Window *BringWindowToFrontById(WindowClass cls, WindowNumber number)
01077 {
01078 Window *w = FindWindowById(cls, number);
01079
01080 if (w != NULL) {
01081 if (w->IsShaded()) w->SetShaded(false);
01082
01083 w->SetWhiteBorder();
01084 BringWindowToFront(w);
01085 w->SetDirty();
01086 }
01087
01088 return w;
01089 }
01090
01091 static inline bool IsVitalWindow(const Window *w)
01092 {
01093 switch (w->window_class) {
01094 case WC_MAIN_TOOLBAR:
01095 case WC_STATUS_BAR:
01096 case WC_NEWS_WINDOW:
01097 case WC_SEND_NETWORK_MSG:
01098 return true;
01099
01100 default:
01101 return false;
01102 }
01103 }
01104
01113 static uint GetWindowZPriority(const Window *w)
01114 {
01115 assert(w->window_class != WC_INVALID);
01116
01117 uint z_priority = 0;
01118
01119 switch (w->window_class) {
01120 case WC_ENDSCREEN:
01121 ++z_priority;
01122
01123 case WC_HIGHSCORE:
01124 ++z_priority;
01125
01126 case WC_TOOLTIPS:
01127 ++z_priority;
01128
01129 case WC_DROPDOWN_MENU:
01130 ++z_priority;
01131
01132 case WC_MAIN_TOOLBAR:
01133 case WC_STATUS_BAR:
01134 ++z_priority;
01135
01136 case WC_OSK:
01137 ++z_priority;
01138
01139 case WC_QUERY_STRING:
01140 case WC_SEND_NETWORK_MSG:
01141 ++z_priority;
01142
01143 case WC_ERRMSG:
01144 case WC_CONFIRM_POPUP_QUERY:
01145 case WC_MODAL_PROGRESS:
01146 case WC_NETWORK_STATUS_WINDOW:
01147 ++z_priority;
01148
01149 case WC_GENERATE_LANDSCAPE:
01150 case WC_SAVELOAD:
01151 case WC_GAME_OPTIONS:
01152 case WC_CUSTOM_CURRENCY:
01153 case WC_NETWORK_WINDOW:
01154 case WC_GRF_PARAMETERS:
01155 case WC_AI_LIST:
01156 case WC_AI_SETTINGS:
01157 case WC_TEXTFILE:
01158 ++z_priority;
01159
01160 case WC_CONSOLE:
01161 ++z_priority;
01162
01163 case WC_NEWS_WINDOW:
01164 ++z_priority;
01165
01166 default:
01167 ++z_priority;
01168
01169 case WC_MAIN_WINDOW:
01170 return z_priority;
01171 }
01172 }
01173
01178 static void AddWindowToZOrdering(Window *w)
01179 {
01180 assert(w->z_front == NULL && w->z_back == NULL);
01181
01182 if (_z_front_window == NULL) {
01183
01184 _z_front_window = _z_back_window = w;
01185 w->z_front = w->z_back = NULL;
01186 } else {
01187
01188 Window *v = _z_front_window;
01189 uint last_z_priority = UINT_MAX;
01190 while (v != NULL && (v->window_class == WC_INVALID || GetWindowZPriority(v) > GetWindowZPriority(w))) {
01191 if (v->window_class != WC_INVALID) {
01192
01193 assert(last_z_priority >= GetWindowZPriority(v));
01194 last_z_priority = GetWindowZPriority(v);
01195 }
01196
01197 v = v->z_back;
01198 }
01199
01200 if (v == NULL) {
01201
01202 w->z_front = _z_back_window;
01203 w->z_back = NULL;
01204 _z_back_window->z_back = w;
01205 _z_back_window = w;
01206 } else if (v == _z_front_window) {
01207
01208 w->z_front = NULL;
01209 w->z_back = _z_front_window;
01210 _z_front_window->z_front = w;
01211 _z_front_window = w;
01212 } else {
01213
01214 w->z_front = v->z_front;
01215 w->z_back = v;
01216 v->z_front->z_back = w;
01217 v->z_front = w;
01218 }
01219 }
01220 }
01221
01222
01227 static void RemoveWindowFromZOrdering(Window *w)
01228 {
01229 if (w->z_front == NULL) {
01230 assert(_z_front_window == w);
01231 _z_front_window = w->z_back;
01232 } else {
01233 w->z_front->z_back = w->z_back;
01234 }
01235
01236 if (w->z_back == NULL) {
01237 assert(_z_back_window == w);
01238 _z_back_window = w->z_front;
01239 } else {
01240 w->z_back->z_front = w->z_front;
01241 }
01242
01243 w->z_front = w->z_back = NULL;
01244 }
01245
01251 static void BringWindowToFront(Window *w)
01252 {
01253 RemoveWindowFromZOrdering(w);
01254 AddWindowToZOrdering(w);
01255
01256 w->SetDirty();
01257 }
01258
01267 void Window::InitializeData(WindowNumber window_number)
01268 {
01269
01270 this->window_class = this->window_desc->cls;
01271 this->SetWhiteBorder();
01272 if (this->window_desc->default_pos == WDP_CENTER) this->flags |= WF_CENTERED;
01273 this->owner = INVALID_OWNER;
01274 this->nested_focus = NULL;
01275 this->window_number = window_number;
01276
01277 this->OnInit();
01278
01279 if (this->nested_array == NULL) {
01280 this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01281 this->nested_root->SetupSmallestSize(this, true);
01282 } else {
01283 this->nested_root->SetupSmallestSize(this, false);
01284 }
01285
01286 this->nested_root->AssignSizePosition(ST_SMALLEST, 0, 0, this->nested_root->smallest_x, this->nested_root->smallest_y, _current_text_dir == TD_RTL);
01287
01288
01289
01290 this->resize.step_width = this->nested_root->resize_x;
01291 this->resize.step_height = this->nested_root->resize_y;
01292
01293
01294
01295
01296 if (!EditBoxInGlobalFocus() || this->nested_root->GetWidgetOfType(WWT_EDITBOX) != NULL) SetFocusedWindow(this);
01297
01298
01299 AddWindowToZOrdering(this);
01300 }
01301
01309 void Window::InitializePositionSize(int x, int y, int sm_width, int sm_height)
01310 {
01311 this->left = x;
01312 this->top = y;
01313 this->width = sm_width;
01314 this->height = sm_height;
01315 }
01316
01327 void Window::FindWindowPlacementAndResize(int def_width, int def_height)
01328 {
01329 def_width = max(def_width, this->width);
01330 def_height = max(def_height, this->height);
01331
01332
01333
01334
01335
01336 if (this->width != def_width || this->height != def_height) {
01337
01338 int free_height = _screen.height;
01339 const Window *wt = FindWindowById(WC_STATUS_BAR, 0);
01340 if (wt != NULL) free_height -= wt->height;
01341 wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01342 if (wt != NULL) free_height -= wt->height;
01343
01344 int enlarge_x = max(min(def_width - this->width, _screen.width - this->width), 0);
01345 int enlarge_y = max(min(def_height - this->height, free_height - this->height), 0);
01346
01347
01348
01349
01350 if (this->resize.step_width > 1) enlarge_x -= enlarge_x % (int)this->resize.step_width;
01351 if (this->resize.step_height > 1) enlarge_y -= enlarge_y % (int)this->resize.step_height;
01352
01353 ResizeWindow(this, enlarge_x, enlarge_y);
01354
01355 } else {
01356
01357 this->OnResize();
01358 }
01359
01360 int nx = this->left;
01361 int ny = this->top;
01362
01363 if (nx + this->width > _screen.width) nx -= (nx + this->width - _screen.width);
01364
01365 const Window *wt = FindWindowById(WC_MAIN_TOOLBAR, 0);
01366 ny = max(ny, (wt == NULL || this == wt || this->top == 0) ? 0 : wt->height);
01367 nx = max(nx, 0);
01368
01369 if (this->viewport != NULL) {
01370 this->viewport->left += nx - this->left;
01371 this->viewport->top += ny - this->top;
01372 }
01373 this->left = nx;
01374 this->top = ny;
01375
01376 this->SetDirty();
01377 }
01378
01390 static bool IsGoodAutoPlace1(int left, int top, int width, int height, Point &pos)
01391 {
01392 int right = width + left;
01393 int bottom = height + top;
01394
01395 const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01396 if (left < 0 || (main_toolbar != NULL && top < main_toolbar->height) || right > _screen.width || bottom > _screen.height) return false;
01397
01398
01399 const Window *w;
01400 FOR_ALL_WINDOWS_FROM_BACK(w) {
01401 if (w->window_class == WC_MAIN_WINDOW) continue;
01402
01403 if (right > w->left &&
01404 w->left + w->width > left &&
01405 bottom > w->top &&
01406 w->top + w->height > top) {
01407 return false;
01408 }
01409 }
01410
01411 pos.x = left;
01412 pos.y = top;
01413 return true;
01414 }
01415
01427 static bool IsGoodAutoPlace2(int left, int top, int width, int height, Point &pos)
01428 {
01429
01430
01431
01432 if (left < -(width >> 2) || left > _screen.width - (width >> 1)) return false;
01433
01434 if (top < 22 || top > _screen.height - (height >> 2)) return false;
01435
01436
01437 const Window *w;
01438 FOR_ALL_WINDOWS_FROM_BACK(w) {
01439 if (w->window_class == WC_MAIN_WINDOW) continue;
01440
01441 if (left + width > w->left &&
01442 w->left + w->width > left &&
01443 top + height > w->top &&
01444 w->top + w->height > top) {
01445 return false;
01446 }
01447 }
01448
01449 pos.x = left;
01450 pos.y = top;
01451 return true;
01452 }
01453
01460 static Point GetAutoPlacePosition(int width, int height)
01461 {
01462 Point pt;
01463
01464
01465 const Window *main_toolbar = FindWindowByClass(WC_MAIN_TOOLBAR);
01466 if (IsGoodAutoPlace1(0, main_toolbar != NULL ? main_toolbar->height + 2 : 2, width, height, pt)) return pt;
01467
01468
01469
01470
01471
01472 const Window *w;
01473 FOR_ALL_WINDOWS_FROM_BACK(w) {
01474 if (w->window_class == WC_MAIN_WINDOW) continue;
01475
01476 if (IsGoodAutoPlace1(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01477 if (IsGoodAutoPlace1(w->left - width - 2, w->top, width, height, pt)) return pt;
01478 if (IsGoodAutoPlace1(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01479 if (IsGoodAutoPlace1(w->left, w->top - height - 2, width, height, pt)) return pt;
01480 if (IsGoodAutoPlace1(w->left + w->width + 2, w->top + w->height - height, width, height, pt)) return pt;
01481 if (IsGoodAutoPlace1(w->left - width - 2, w->top + w->height - height, width, height, pt)) return pt;
01482 if (IsGoodAutoPlace1(w->left + w->width - width, w->top + w->height + 2, width, height, pt)) return pt;
01483 if (IsGoodAutoPlace1(w->left + w->width - width, w->top - height - 2, width, height, pt)) return pt;
01484 }
01485
01486
01487
01488
01489
01490 FOR_ALL_WINDOWS_FROM_BACK(w) {
01491 if (w->window_class == WC_MAIN_WINDOW) continue;
01492
01493 if (IsGoodAutoPlace2(w->left + w->width + 2, w->top, width, height, pt)) return pt;
01494 if (IsGoodAutoPlace2(w->left - width - 2, w->top, width, height, pt)) return pt;
01495 if (IsGoodAutoPlace2(w->left, w->top + w->height + 2, width, height, pt)) return pt;
01496 if (IsGoodAutoPlace2(w->left, w->top - height - 2, width, height, pt)) return pt;
01497 }
01498
01499
01500
01501
01502 int left = 0, top = 24;
01503
01504 restart:
01505 FOR_ALL_WINDOWS_FROM_BACK(w) {
01506 if (w->left == left && w->top == top) {
01507 left += 5;
01508 top += 5;
01509 goto restart;
01510 }
01511 }
01512
01513 pt.x = left;
01514 pt.y = top;
01515 return pt;
01516 }
01517
01524 Point GetToolbarAlignedWindowPosition(int window_width)
01525 {
01526 const Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01527 assert(w != NULL);
01528 Point pt = { _current_text_dir == TD_RTL ? w->left : (w->left + w->width) - window_width, w->top + w->height };
01529 return pt;
01530 }
01531
01549 static Point LocalGetWindowPlacement(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
01550 {
01551 Point pt;
01552 const Window *w;
01553
01554 int16 default_width = max(desc->GetDefaultWidth(), sm_width);
01555 int16 default_height = max(desc->GetDefaultHeight(), sm_height);
01556
01557 if (desc->parent_cls != 0 &&
01558 (w = FindWindowById(desc->parent_cls, window_number)) != NULL &&
01559 w->left < _screen.width - 20 && w->left > -60 && w->top < _screen.height - 20) {
01560
01561 pt.x = w->left + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? 0 : 10);
01562 if (pt.x > _screen.width + 10 - default_width) {
01563 pt.x = (_screen.width + 10 - default_width) - 20;
01564 }
01565 pt.y = w->top + ((desc->parent_cls == WC_BUILD_TOOLBAR || desc->parent_cls == WC_SCEN_LAND_GEN) ? w->height : 10);
01566 return pt;
01567 }
01568
01569 switch (desc->default_pos) {
01570 case WDP_ALIGN_TOOLBAR:
01571 return GetToolbarAlignedWindowPosition(default_width);
01572
01573 case WDP_AUTO:
01574 return GetAutoPlacePosition(default_width, default_height);
01575
01576 case WDP_CENTER:
01577 pt.x = (_screen.width - default_width) / 2;
01578 pt.y = (_screen.height - default_height) / 2;
01579 break;
01580
01581 case WDP_MANUAL:
01582 pt.x = 0;
01583 pt.y = 0;
01584 break;
01585
01586 default:
01587 NOT_REACHED();
01588 }
01589
01590 return pt;
01591 }
01592
01593 Point Window::OnInitialPosition(int16 sm_width, int16 sm_height, int window_number)
01594 {
01595 return LocalGetWindowPlacement(this->window_desc, sm_width, sm_height, window_number);
01596 }
01597
01605 void Window::CreateNestedTree(bool fill_nested)
01606 {
01607 int biggest_index = -1;
01608 this->nested_root = MakeWindowNWidgetTree(this->window_desc->nwid_parts, this->window_desc->nwid_length, &biggest_index, &this->shade_select);
01609 this->nested_array_size = (uint)(biggest_index + 1);
01610
01611 if (fill_nested) {
01612 this->nested_array = CallocT<NWidgetBase *>(this->nested_array_size);
01613 this->nested_root->FillNestedArray(this->nested_array, this->nested_array_size);
01614 }
01615 }
01616
01621 void Window::FinishInitNested(WindowNumber window_number)
01622 {
01623 this->InitializeData(window_number);
01624 this->ApplyDefaults();
01625 Point pt = this->OnInitialPosition(this->nested_root->smallest_x, this->nested_root->smallest_y, window_number);
01626 this->InitializePositionSize(pt.x, pt.y, this->nested_root->smallest_x, this->nested_root->smallest_y);
01627 this->FindWindowPlacementAndResize(this->window_desc->GetDefaultWidth(), this->window_desc->GetDefaultHeight());
01628 }
01629
01634 void Window::InitNested(WindowNumber window_number)
01635 {
01636 this->CreateNestedTree(false);
01637 this->FinishInitNested(window_number);
01638 }
01639
01644 Window::Window(WindowDesc *desc) : window_desc(desc), scrolling_scrollbar(-1)
01645 {
01646 }
01647
01655 Window *FindWindowFromPt(int x, int y)
01656 {
01657 Window *w;
01658 FOR_ALL_WINDOWS_FROM_FRONT(w) {
01659 if (MayBeShown(w) && IsInsideBS(x, w->left, w->width) && IsInsideBS(y, w->top, w->height)) {
01660 return w;
01661 }
01662 }
01663
01664 return NULL;
01665 }
01666
01670 void InitWindowSystem()
01671 {
01672 IConsoleClose();
01673
01674 _z_back_window = NULL;
01675 _z_front_window = NULL;
01676 _focused_window = NULL;
01677 _mouseover_last_w = NULL;
01678 _last_scroll_window = NULL;
01679 _scrolling_viewport = false;
01680 _mouse_hovering = false;
01681
01682 NWidgetLeaf::InvalidateDimensionCache();
01683 NWidgetScrollbar::InvalidateDimensionCache();
01684
01685 ShowFirstError();
01686 }
01687
01691 void UnInitWindowSystem()
01692 {
01693 UnshowCriticalError();
01694
01695 Window *w;
01696 FOR_ALL_WINDOWS_FROM_FRONT(w) delete w;
01697
01698 for (w = _z_front_window; w != NULL; ) {
01699 Window *to_del = w;
01700 w = w->z_back;
01701 free(to_del);
01702 }
01703
01704 _z_front_window = NULL;
01705 _z_back_window = NULL;
01706 }
01707
01711 void ResetWindowSystem()
01712 {
01713 UnInitWindowSystem();
01714 InitWindowSystem();
01715 _thd.Reset();
01716 }
01717
01718 static void DecreaseWindowCounters()
01719 {
01720 Window *w;
01721 FOR_ALL_WINDOWS_FROM_FRONT(w) {
01722 if (_scroller_click_timeout == 0) {
01723
01724 for (uint i = 0; i < w->nested_array_size; i++) {
01725 NWidgetBase *nwid = w->nested_array[i];
01726 if (nwid != NULL && (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR)) {
01727 NWidgetScrollbar *sb = static_cast<NWidgetScrollbar*>(nwid);
01728 if (sb->disp_flags & (ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN)) {
01729 sb->disp_flags &= ~(ND_SCROLLBAR_UP | ND_SCROLLBAR_DOWN);
01730 w->scrolling_scrollbar = -1;
01731 sb->SetDirty(w);
01732 }
01733 }
01734 }
01735 }
01736
01737
01738 for (SmallMap<int, QueryString*>::Pair *it = w->querystrings.Begin(); it != w->querystrings.End(); ++it) {
01739 it->second->HandleEditBox(w, it->first);
01740 }
01741
01742 w->OnMouseLoop();
01743 }
01744
01745 FOR_ALL_WINDOWS_FROM_FRONT(w) {
01746 if ((w->flags & WF_TIMEOUT) && --w->timeout_timer == 0) {
01747 CLRBITS(w->flags, WF_TIMEOUT);
01748
01749 w->OnTimeout();
01750 w->RaiseButtons(true);
01751 }
01752 }
01753 }
01754
01755 static void HandlePlacePresize()
01756 {
01757 if (_special_mouse_mode != WSM_PRESIZE) return;
01758
01759 Window *w = _thd.GetCallbackWnd();
01760 if (w == NULL) return;
01761
01762 Point pt = GetTileBelowCursor();
01763 if (pt.x == -1) {
01764 _thd.selend.x = -1;
01765 return;
01766 }
01767
01768 w->OnPlacePresize(pt, TileVirtXY(pt.x, pt.y));
01769 }
01770
01775 static EventState HandleMouseDragDrop()
01776 {
01777 if (_special_mouse_mode != WSM_DRAGDROP) return ES_NOT_HANDLED;
01778
01779 if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
01780
01781 Window *w = _thd.GetCallbackWnd();
01782 if (w != NULL) {
01783
01784 Point pt;
01785 pt.x = _cursor.pos.x - w->left;
01786 pt.y = _cursor.pos.y - w->top;
01787 if (_left_button_down) {
01788 w->OnMouseDrag(pt, GetWidgetFromPos(w, pt.x, pt.y));
01789 } else {
01790 w->OnDragDrop(pt, GetWidgetFromPos(w, pt.x, pt.y));
01791 }
01792 }
01793
01794 if (!_left_button_down) ResetObjectToPlace();
01795 return ES_HANDLED;
01796 }
01797
01799 static void HandleMouseOver()
01800 {
01801 Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
01802
01803
01804 if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
01805
01806 Point pt = { -1, -1 };
01807 _mouseover_last_w->OnMouseOver(pt, 0);
01808 }
01809
01810
01811 _mouseover_last_w = w;
01812
01813 if (w != NULL) {
01814
01815 Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
01816 const NWidgetCore *widget = w->nested_root->GetWidgetFromPos(pt.x, pt.y);
01817 if (widget != NULL) w->OnMouseOver(pt, widget->index);
01818 }
01819 }
01820
01822 static const int MIN_VISIBLE_TITLE_BAR = 13;
01823
01825 enum PreventHideDirection {
01826 PHD_UP,
01827 PHD_DOWN,
01828 };
01829
01840 static void PreventHiding(int *nx, int *ny, const Rect &rect, const Window *v, int px, PreventHideDirection dir)
01841 {
01842 if (v == NULL) return;
01843
01844 int v_bottom = v->top + v->height;
01845 int v_right = v->left + v->width;
01846 int safe_y = (dir == PHD_UP) ? (v->top - MIN_VISIBLE_TITLE_BAR - rect.top) : (v_bottom + MIN_VISIBLE_TITLE_BAR - rect.bottom);
01847
01848 if (*ny + rect.top <= v->top - MIN_VISIBLE_TITLE_BAR) return;
01849 if (*ny + rect.bottom >= v_bottom + MIN_VISIBLE_TITLE_BAR) return;
01850
01851
01852 if (*nx + rect.left + MIN_VISIBLE_TITLE_BAR < v->left) {
01853 if (v->left < MIN_VISIBLE_TITLE_BAR) *ny = safe_y;
01854 return;
01855 }
01856 if (*nx + rect.right - MIN_VISIBLE_TITLE_BAR > v_right) {
01857 if (v_right > _screen.width - MIN_VISIBLE_TITLE_BAR) *ny = safe_y;
01858 return;
01859 }
01860
01861
01862 if (px + rect.left < v->left && v->left >= MIN_VISIBLE_TITLE_BAR) {
01863 *nx = v->left - MIN_VISIBLE_TITLE_BAR - rect.left;
01864 } else if (px + rect.right > v_right && v_right <= _screen.width - MIN_VISIBLE_TITLE_BAR) {
01865 *nx = v_right + MIN_VISIBLE_TITLE_BAR - rect.right;
01866 } else {
01867 *ny = safe_y;
01868 }
01869 }
01870
01878 static void EnsureVisibleCaption(Window *w, int nx, int ny)
01879 {
01880
01881 Rect caption_rect;
01882 const NWidgetBase *caption = w->nested_root->GetWidgetOfType(WWT_CAPTION);
01883 if (caption != NULL) {
01884 caption_rect.left = caption->pos_x;
01885 caption_rect.right = caption->pos_x + caption->current_x;
01886 caption_rect.top = caption->pos_y;
01887 caption_rect.bottom = caption->pos_y + caption->current_y;
01888
01889
01890 nx = Clamp(nx, MIN_VISIBLE_TITLE_BAR - caption_rect.right, _screen.width - MIN_VISIBLE_TITLE_BAR - caption_rect.left);
01891 ny = Clamp(ny, 0, _screen.height - MIN_VISIBLE_TITLE_BAR);
01892
01893
01894 PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_MAIN_TOOLBAR, 0), w->left, PHD_DOWN);
01895 PreventHiding(&nx, &ny, caption_rect, FindWindowById(WC_STATUS_BAR, 0), w->left, PHD_UP);
01896 }
01897
01898 if (w->viewport != NULL) {
01899 w->viewport->left += nx - w->left;
01900 w->viewport->top += ny - w->top;
01901 }
01902
01903 w->left = nx;
01904 w->top = ny;
01905 }
01906
01917 void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen)
01918 {
01919 if (delta_x != 0 || delta_y != 0) {
01920 if (clamp_to_screen) {
01921
01922
01923 int new_right = w->left + w->width + delta_x;
01924 int new_bottom = w->top + w->height + delta_y;
01925 if (new_right >= (int)_cur_resolution.width) delta_x -= Ceil(new_right - _cur_resolution.width, max(1U, w->nested_root->resize_x));
01926 if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, max(1U, w->nested_root->resize_y));
01927 }
01928
01929 w->SetDirty();
01930
01931 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);
01932 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);
01933 assert(w->nested_root->resize_x == 0 || new_xinc % w->nested_root->resize_x == 0);
01934 assert(w->nested_root->resize_y == 0 || new_yinc % w->nested_root->resize_y == 0);
01935
01936 w->nested_root->AssignSizePosition(ST_RESIZE, 0, 0, w->nested_root->smallest_x + new_xinc, w->nested_root->smallest_y + new_yinc, _current_text_dir == TD_RTL);
01937 w->width = w->nested_root->current_x;
01938 w->height = w->nested_root->current_y;
01939 }
01940
01941 EnsureVisibleCaption(w, w->left, w->top);
01942
01943
01944 w->OnResize();
01945 w->SetDirty();
01946 }
01947
01953 int GetMainViewTop()
01954 {
01955 Window *w = FindWindowById(WC_MAIN_TOOLBAR, 0);
01956 return (w == NULL) ? 0 : w->top + w->height;
01957 }
01958
01964 int GetMainViewBottom()
01965 {
01966 Window *w = FindWindowById(WC_STATUS_BAR, 0);
01967 return (w == NULL) ? _screen.height : w->top;
01968 }
01969
01970 static bool _dragging_window;
01971
01976 static EventState HandleWindowDragging()
01977 {
01978
01979 if (!_dragging_window) return ES_NOT_HANDLED;
01980
01981
01982 if (_left_button_down && _cursor.delta.x == 0 && _cursor.delta.y == 0) return ES_HANDLED;
01983
01984
01985 Window *w;
01986 FOR_ALL_WINDOWS_FROM_BACK(w) {
01987 if (w->flags & WF_DRAGGING) {
01988
01989 if (!_left_button_down) {
01990 w->flags &= ~WF_DRAGGING;
01991 break;
01992 }
01993
01994 w->SetDirty();
01995
01996 int x = _cursor.pos.x + _drag_delta.x;
01997 int y = _cursor.pos.y + _drag_delta.y;
01998 int nx = x;
01999 int ny = y;
02000
02001 if (_settings_client.gui.window_snap_radius != 0) {
02002 const Window *v;
02003
02004 int hsnap = _settings_client.gui.window_snap_radius;
02005 int vsnap = _settings_client.gui.window_snap_radius;
02006 int delta;
02007
02008 FOR_ALL_WINDOWS_FROM_BACK(v) {
02009 if (v == w) continue;
02010
02011 if (y + w->height > v->top && y < v->top + v->height) {
02012
02013 delta = abs(v->left + v->width - x);
02014 if (delta <= hsnap) {
02015 nx = v->left + v->width;
02016 hsnap = delta;
02017 }
02018
02019
02020 delta = abs(v->left - x - w->width);
02021 if (delta <= hsnap) {
02022 nx = v->left - w->width;
02023 hsnap = delta;
02024 }
02025 }
02026
02027 if (w->top + w->height >= v->top && w->top <= v->top + v->height) {
02028
02029 delta = abs(v->left - x);
02030 if (delta <= hsnap) {
02031 nx = v->left;
02032 hsnap = delta;
02033 }
02034
02035
02036 delta = abs(v->left + v->width - x - w->width);
02037 if (delta <= hsnap) {
02038 nx = v->left + v->width - w->width;
02039 hsnap = delta;
02040 }
02041 }
02042
02043 if (x + w->width > v->left && x < v->left + v->width) {
02044
02045 delta = abs(v->top + v->height - y);
02046 if (delta <= vsnap) {
02047 ny = v->top + v->height;
02048 vsnap = delta;
02049 }
02050
02051
02052 delta = abs(v->top - y - w->height);
02053 if (delta <= vsnap) {
02054 ny = v->top - w->height;
02055 vsnap = delta;
02056 }
02057 }
02058
02059 if (w->left + w->width >= v->left && w->left <= v->left + v->width) {
02060
02061 delta = abs(v->top - y);
02062 if (delta <= vsnap) {
02063 ny = v->top;
02064 vsnap = delta;
02065 }
02066
02067
02068 delta = abs(v->top + v->height - y - w->height);
02069 if (delta <= vsnap) {
02070 ny = v->top + v->height - w->height;
02071 vsnap = delta;
02072 }
02073 }
02074 }
02075 }
02076
02077 EnsureVisibleCaption(w, nx, ny);
02078
02079 w->SetDirty();
02080 return ES_HANDLED;
02081 } else if (w->flags & WF_SIZING) {
02082
02083 if (!_left_button_down) {
02084 w->flags &= ~WF_SIZING;
02085 w->SetDirty();
02086 break;
02087 }
02088
02089
02090
02091
02092 int x, y = _cursor.pos.y - _drag_delta.y;
02093 if (w->flags & WF_SIZING_LEFT) {
02094 x = _drag_delta.x - _cursor.pos.x;
02095 } else {
02096 x = _cursor.pos.x - _drag_delta.x;
02097 }
02098
02099
02100 if (w->resize.step_width == 0) x = 0;
02101 if (w->resize.step_height == 0) y = 0;
02102
02103
02104 if (w->top + w->height + y > _screen.height) {
02105 y = _screen.height - w->height - w->top;
02106 }
02107
02108
02109
02110
02111 if (w->resize.step_width > 1) x -= x % (int)w->resize.step_width;
02112 if (w->resize.step_height > 1) y -= y % (int)w->resize.step_height;
02113
02114
02115 if ((int)w->width + x < (int)w->nested_root->smallest_x) {
02116 x = w->nested_root->smallest_x - w->width;
02117 }
02118 if ((int)w->height + y < (int)w->nested_root->smallest_y) {
02119 y = w->nested_root->smallest_y - w->height;
02120 }
02121
02122
02123 if (x == 0 && y == 0) return ES_HANDLED;
02124
02125
02126 _drag_delta.y += y;
02127 if ((w->flags & WF_SIZING_LEFT) && x != 0) {
02128 _drag_delta.x -= x;
02129 w->SetDirty();
02130 w->left -= x;
02131
02132 } else {
02133 _drag_delta.x += x;
02134 }
02135
02136
02137 ResizeWindow(w, x, y);
02138 return ES_HANDLED;
02139 }
02140 }
02141
02142 _dragging_window = false;
02143 return ES_HANDLED;
02144 }
02145
02150 static void StartWindowDrag(Window *w)
02151 {
02152 w->flags |= WF_DRAGGING;
02153 w->flags &= ~WF_CENTERED;
02154 _dragging_window = true;
02155
02156 _drag_delta.x = w->left - _cursor.pos.x;
02157 _drag_delta.y = w->top - _cursor.pos.y;
02158
02159 BringWindowToFront(w);
02160 DeleteWindowById(WC_DROPDOWN_MENU, 0);
02161 }
02162
02168 static void StartWindowSizing(Window *w, bool to_left)
02169 {
02170 w->flags |= to_left ? WF_SIZING_LEFT : WF_SIZING_RIGHT;
02171 w->flags &= ~WF_CENTERED;
02172 _dragging_window = true;
02173
02174 _drag_delta.x = _cursor.pos.x;
02175 _drag_delta.y = _cursor.pos.y;
02176
02177 BringWindowToFront(w);
02178 DeleteWindowById(WC_DROPDOWN_MENU, 0);
02179 }
02180
02185 static EventState HandleScrollbarScrolling()
02186 {
02187 Window *w;
02188 FOR_ALL_WINDOWS_FROM_BACK(w) {
02189 if (w->scrolling_scrollbar >= 0) {
02190
02191 if (!_left_button_down) {
02192 w->scrolling_scrollbar = -1;
02193 w->SetDirty();
02194 return ES_HANDLED;
02195 }
02196
02197 int i;
02198 NWidgetScrollbar *sb = w->GetWidget<NWidgetScrollbar>(w->scrolling_scrollbar);
02199 bool rtl = false;
02200
02201 if (sb->type == NWID_HSCROLLBAR) {
02202 i = _cursor.pos.x - _cursorpos_drag_start.x;
02203 rtl = _current_text_dir == TD_RTL;
02204 } else {
02205 i = _cursor.pos.y - _cursorpos_drag_start.y;
02206 }
02207
02208 if (sb->disp_flags & ND_SCROLLBAR_BTN) {
02209 if (_scroller_click_timeout == 1) {
02210 _scroller_click_timeout = 3;
02211 sb->UpdatePosition(rtl == HasBit(sb->disp_flags, NDB_SCROLLBAR_UP) ? 1 : -1);
02212 w->SetDirty();
02213 }
02214 return ES_HANDLED;
02215 }
02216
02217
02218 int pos = min(max(0, i + _scrollbar_start_pos) * sb->GetCount() / _scrollbar_size, max(0, sb->GetCount() - sb->GetCapacity()));
02219 if (rtl) pos = max(0, sb->GetCount() - sb->GetCapacity() - pos);
02220 if (pos != sb->GetPosition()) {
02221 sb->SetPosition(pos);
02222 w->SetDirty();
02223 }
02224 return ES_HANDLED;
02225 }
02226 }
02227
02228 return ES_NOT_HANDLED;
02229 }
02230
02235 static EventState HandleViewportScroll()
02236 {
02237 bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02238
02239 if (!_scrolling_viewport) return ES_NOT_HANDLED;
02240
02241
02242
02243
02244 if (_last_scroll_window == NULL) _last_scroll_window = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
02245
02246 if (_last_scroll_window == NULL || !(_right_button_down || scrollwheel_scrolling || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down))) {
02247 _cursor.fix_at = false;
02248 _scrolling_viewport = false;
02249 _last_scroll_window = NULL;
02250 return ES_NOT_HANDLED;
02251 }
02252
02253 if (_last_scroll_window == FindWindowById(WC_MAIN_WINDOW, 0) && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) {
02254
02255 const Vehicle *veh = Vehicle::Get(_last_scroll_window->viewport->follow_vehicle);
02256 ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true);
02257 return ES_NOT_HANDLED;
02258 }
02259
02260 Point delta;
02261 if (_settings_client.gui.reverse_scroll || (_settings_client.gui.left_mouse_btn_scrolling && _left_button_down)) {
02262 delta.x = -_cursor.delta.x;
02263 delta.y = -_cursor.delta.y;
02264 } else {
02265 delta.x = _cursor.delta.x;
02266 delta.y = _cursor.delta.y;
02267 }
02268
02269 if (scrollwheel_scrolling) {
02270
02271 delta.x = _cursor.h_wheel;
02272 delta.y = _cursor.v_wheel;
02273 _cursor.v_wheel = 0;
02274 _cursor.h_wheel = 0;
02275 }
02276
02277
02278 if (delta.x != 0 || delta.y != 0) _last_scroll_window->OnScroll(delta);
02279
02280 _cursor.delta.x = 0;
02281 _cursor.delta.y = 0;
02282 return ES_HANDLED;
02283 }
02284
02295 static bool MaybeBringWindowToFront(Window *w)
02296 {
02297 bool bring_to_front = false;
02298
02299 if (w->window_class == WC_MAIN_WINDOW ||
02300 IsVitalWindow(w) ||
02301 w->window_class == WC_TOOLTIPS ||
02302 w->window_class == WC_DROPDOWN_MENU) {
02303 return true;
02304 }
02305
02306
02307 int w_width = w->width;
02308 int w_height = w->height;
02309 if (w->IsShaded()) {
02310 w_width = w->unshaded_size.width;
02311 w_height = w->unshaded_size.height;
02312 }
02313
02314 Window *u;
02315 FOR_ALL_WINDOWS_FROM_BACK_FROM(u, w->z_front) {
02316
02317 if (u->parent == w && (u->window_desc->flags & WDF_MODAL)) {
02318 u->SetWhiteBorder();
02319 u->SetDirty();
02320 return false;
02321 }
02322
02323 if (u->window_class == WC_MAIN_WINDOW ||
02324 IsVitalWindow(u) ||
02325 u->window_class == WC_TOOLTIPS ||
02326 u->window_class == WC_DROPDOWN_MENU) {
02327 continue;
02328 }
02329
02330
02331 if (w->left + w_width <= u->left ||
02332 u->left + u->width <= w->left ||
02333 w->top + w_height <= u->top ||
02334 u->top + u->height <= w->top) {
02335 continue;
02336 }
02337
02338 bring_to_front = true;
02339 }
02340
02341 if (bring_to_front) BringWindowToFront(w);
02342 return true;
02343 }
02344
02353 EventState Window::HandleEditBoxKey(int wid, uint16 key, uint16 keycode)
02354 {
02355 QueryString *query = this->GetQueryString(wid);
02356 if (query == NULL) return ES_NOT_HANDLED;
02357
02358 int action = QueryString::ACTION_NOTHING;
02359
02360 switch (query->text.HandleKeyPress(key, keycode)) {
02361 case HKPR_EDITING:
02362 this->SetWidgetDirty(wid);
02363 this->OnEditboxChanged(wid);
02364 break;
02365
02366 case HKPR_CURSOR:
02367 this->SetWidgetDirty(wid);
02368
02369 if (this->window_class == WC_OSK) this->InvalidateData();
02370 break;
02371
02372 case HKPR_CONFIRM:
02373 if (this->window_class == WC_OSK) {
02374 this->OnClick(Point(), WID_OSK_OK, 1);
02375 } else if (query->ok_button >= 0) {
02376 this->OnClick(Point(), query->ok_button, 1);
02377 } else {
02378 action = query->ok_button;
02379 }
02380 break;
02381
02382 case HKPR_CANCEL:
02383 if (this->window_class == WC_OSK) {
02384 this->OnClick(Point(), WID_OSK_CANCEL, 1);
02385 } else if (query->cancel_button >= 0) {
02386 this->OnClick(Point(), query->cancel_button, 1);
02387 } else {
02388 action = query->cancel_button;
02389 }
02390 break;
02391
02392 case HKPR_NOT_HANDLED:
02393 return ES_NOT_HANDLED;
02394
02395 default: break;
02396 }
02397
02398 switch (action) {
02399 case QueryString::ACTION_DESELECT:
02400 this->UnfocusFocusedWidget();
02401 break;
02402
02403 case QueryString::ACTION_CLEAR:
02404 query->text.DeleteAll();
02405 this->SetWidgetDirty(wid);
02406 this->OnEditboxChanged(wid);
02407 break;
02408
02409 default:
02410 break;
02411 }
02412
02413 return ES_HANDLED;
02414 }
02415
02420 void HandleKeypress(uint32 raw_key)
02421 {
02422
02423
02424 assert(HasModalProgress() || IsLocalCompany());
02425
02426
02427 uint16 key = GB(raw_key, 0, 16);
02428 uint16 keycode = GB(raw_key, 16, 16);
02429
02430
02431
02432
02433
02434
02435
02436
02437 if (key >= 0xE000 && key <= 0xF8FF) key = 0;
02438
02439
02440
02441
02442 if (key == 0 && keycode == 0) return;
02443
02444
02445 if (EditBoxInGlobalFocus()) {
02446
02447 if (_focused_window->window_class == WC_CONSOLE) {
02448 if (_focused_window->OnKeyPress(key, keycode) == ES_HANDLED) return;
02449 } else {
02450 if (_focused_window->HandleEditBoxKey(_focused_window->nested_focus->index, key, keycode) == ES_HANDLED) return;
02451 }
02452 }
02453
02454
02455 Window *w;
02456 FOR_ALL_WINDOWS_FROM_FRONT(w) {
02457 if (w->window_class == WC_MAIN_TOOLBAR) continue;
02458 if (w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02459 }
02460
02461 w = FindWindowById(WC_MAIN_TOOLBAR, 0);
02462
02463 if (w != NULL && w->OnKeyPress(key, keycode) == ES_HANDLED) return;
02464
02465 HandleGlobalHotkeys(key, keycode);
02466 }
02467
02471 void HandleCtrlChanged()
02472 {
02473
02474 Window *w;
02475 FOR_ALL_WINDOWS_FROM_FRONT(w) {
02476 if (w->OnCTRLStateChange() == ES_HANDLED) return;
02477 }
02478 }
02479
02486 static int _input_events_this_tick = 0;
02487
02492 static void HandleAutoscroll()
02493 {
02494 if (_game_mode == GM_MENU || HasModalProgress()) return;
02495 if (_settings_client.gui.auto_scrolling == VA_DISABLED) return;
02496 if (_settings_client.gui.auto_scrolling == VA_MAIN_VIEWPORT_FULLSCREEN && !_fullscreen) return;
02497
02498 int x = _cursor.pos.x;
02499 int y = _cursor.pos.y;
02500 Window *w = FindWindowFromPt(x, y);
02501 if (w == NULL || w->flags & WF_DISABLE_VP_SCROLL) return;
02502 if (_settings_client.gui.auto_scrolling != VA_EVERY_VIEWPORT && w->window_class != WC_MAIN_WINDOW) return;
02503
02504 ViewPort *vp = IsPtInWindowViewport(w, x, y);
02505 if (vp == NULL) return;
02506
02507 x -= vp->left;
02508 y -= vp->top;
02509
02510
02511 #define scrollspeed 3
02512 if (x - 15 < 0) {
02513 w->viewport->dest_scrollpos_x += ScaleByZoom((x - 15) * scrollspeed, vp->zoom);
02514 } else if (15 - (vp->width - x) > 0) {
02515 w->viewport->dest_scrollpos_x += ScaleByZoom((15 - (vp->width - x)) * scrollspeed, vp->zoom);
02516 }
02517 if (y - 15 < 0) {
02518 w->viewport->dest_scrollpos_y += ScaleByZoom((y - 15) * scrollspeed, vp->zoom);
02519 } else if (15 - (vp->height - y) > 0) {
02520 w->viewport->dest_scrollpos_y += ScaleByZoom((15 - (vp->height - y)) * scrollspeed, vp->zoom);
02521 }
02522 #undef scrollspeed
02523 }
02524
02525 enum MouseClick {
02526 MC_NONE = 0,
02527 MC_LEFT,
02528 MC_RIGHT,
02529 MC_DOUBLE_LEFT,
02530 MC_HOVER,
02531
02532 MAX_OFFSET_DOUBLE_CLICK = 5,
02533 TIME_BETWEEN_DOUBLE_CLICK = 500,
02534 MAX_OFFSET_HOVER = 5,
02535 };
02536 extern EventState VpHandlePlaceSizingDrag();
02537
02538 static void ScrollMainViewport(int x, int y)
02539 {
02540 if (_game_mode != GM_MENU) {
02541 Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
02542 assert(w);
02543
02544 w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom);
02545 w->viewport->dest_scrollpos_y += ScaleByZoom(y, w->viewport->zoom);
02546 }
02547 }
02548
02558 static const int8 scrollamt[16][2] = {
02559 { 0, 0},
02560 {-2, 0},
02561 { 0, -2},
02562 {-2, -1},
02563 { 2, 0},
02564 { 0, 0},
02565 { 2, -1},
02566 { 0, -2},
02567 { 0, 2},
02568 {-2, 1},
02569 { 0, 0},
02570 {-2, 0},
02571 { 2, 1},
02572 { 0, 2},
02573 { 2, 0},
02574 { 0, 0},
02575 };
02576
02577 static void HandleKeyScrolling()
02578 {
02579
02580
02581
02582
02583 if (_dirkeys && !EditBoxInGlobalFocus()) {
02584 int factor = _shift_pressed ? 50 : 10;
02585 ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
02586 }
02587 }
02588
02589 static void MouseLoop(MouseClick click, int mousewheel)
02590 {
02591
02592
02593 assert(HasModalProgress() || IsLocalCompany());
02594
02595 HandlePlacePresize();
02596 UpdateTileSelection();
02597
02598 if (VpHandlePlaceSizingDrag() == ES_HANDLED) return;
02599 if (HandleMouseDragDrop() == ES_HANDLED) return;
02600 if (HandleWindowDragging() == ES_HANDLED) return;
02601 if (HandleScrollbarScrolling() == ES_HANDLED) return;
02602 if (HandleViewportScroll() == ES_HANDLED) return;
02603
02604 HandleMouseOver();
02605
02606 bool scrollwheel_scrolling = _settings_client.gui.scrollwheel_scrolling == 1 && (_cursor.v_wheel != 0 || _cursor.h_wheel != 0);
02607 if (click == MC_NONE && mousewheel == 0 && !scrollwheel_scrolling) return;
02608
02609 int x = _cursor.pos.x;
02610 int y = _cursor.pos.y;
02611 Window *w = FindWindowFromPt(x, y);
02612 if (w == NULL) return;
02613
02614 if (click != MC_HOVER && !MaybeBringWindowToFront(w)) return;
02615 ViewPort *vp = IsPtInWindowViewport(w, x, y);
02616
02617
02618 if (vp != NULL && (_game_mode == GM_MENU || HasModalProgress())) return;
02619
02620 if (mousewheel != 0) {
02621
02622 w->OnMouseWheel(mousewheel);
02623
02624
02625 if (vp == NULL) DispatchMouseWheelEvent(w, w->nested_root->GetWidgetFromPos(x - w->left, y - w->top), mousewheel);
02626 }
02627
02628 if (vp != NULL) {
02629 if (scrollwheel_scrolling) click = MC_RIGHT;
02630 switch (click) {
02631 case MC_DOUBLE_LEFT:
02632 case MC_LEFT:
02633 DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
02634 if (!HandleViewportClicked(vp, x, y) &&
02635 !(w->flags & WF_DISABLE_VP_SCROLL) &&
02636 _settings_client.gui.left_mouse_btn_scrolling) {
02637 _scrolling_viewport = true;
02638 _cursor.fix_at = false;
02639 }
02640 break;
02641
02642 case MC_RIGHT:
02643 if (!(w->flags & WF_DISABLE_VP_SCROLL)) {
02644 _scrolling_viewport = true;
02645 _cursor.fix_at = true;
02646
02647
02648 _cursor.h_wheel = 0;
02649 _cursor.v_wheel = 0;
02650 }
02651 break;
02652
02653 default:
02654 break;
02655 }
02656 } else {
02657 switch (click) {
02658 case MC_LEFT:
02659 case MC_DOUBLE_LEFT:
02660 DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
02661 break;
02662
02663 default:
02664 if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
02665
02666
02667
02668
02669 case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
02670
02671 case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
02672 }
02673 }
02674 }
02675
02679 void HandleMouseEvents()
02680 {
02681
02682
02683 assert(HasModalProgress() || IsLocalCompany());
02684
02685 static int double_click_time = 0;
02686 static Point double_click_pos = {0, 0};
02687
02688
02689 MouseClick click = MC_NONE;
02690 if (_left_button_down && !_left_button_clicked) {
02691 click = MC_LEFT;
02692 if (double_click_time != 0 && _realtime_tick - double_click_time < TIME_BETWEEN_DOUBLE_CLICK &&
02693 double_click_pos.x != 0 && abs(_cursor.pos.x - double_click_pos.x) < MAX_OFFSET_DOUBLE_CLICK &&
02694 double_click_pos.y != 0 && abs(_cursor.pos.y - double_click_pos.y) < MAX_OFFSET_DOUBLE_CLICK) {
02695 click = MC_DOUBLE_LEFT;
02696 }
02697 double_click_time = _realtime_tick;
02698 double_click_pos = _cursor.pos;
02699 _left_button_clicked = true;
02700 _input_events_this_tick++;
02701 } else if (_right_button_clicked) {
02702 _right_button_clicked = false;
02703 click = MC_RIGHT;
02704 _input_events_this_tick++;
02705 }
02706
02707 int mousewheel = 0;
02708 if (_cursor.wheel) {
02709 mousewheel = _cursor.wheel;
02710 _cursor.wheel = 0;
02711 _input_events_this_tick++;
02712 }
02713
02714 static uint32 hover_time = 0;
02715 static Point hover_pos = {0, 0};
02716
02717 if (_settings_client.gui.hover_delay > 0) {
02718 if (!_cursor.in_window || click != MC_NONE || mousewheel != 0 || _left_button_down || _right_button_down ||
02719 hover_pos.x == 0 || abs(_cursor.pos.x - hover_pos.x) >= MAX_OFFSET_HOVER ||
02720 hover_pos.y == 0 || abs(_cursor.pos.y - hover_pos.y) >= MAX_OFFSET_HOVER) {
02721 hover_pos = _cursor.pos;
02722 hover_time = _realtime_tick;
02723 _mouse_hovering = false;
02724 } else {
02725 if (hover_time != 0 && _realtime_tick > hover_time + _settings_client.gui.hover_delay * 1000) {
02726 click = MC_HOVER;
02727 _input_events_this_tick++;
02728 _mouse_hovering = true;
02729 }
02730 }
02731 }
02732
02733
02734 if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && _newgrf_debug_sprite_picker.click_time != _realtime_tick) {
02735
02736 _newgrf_debug_sprite_picker.mode = SPM_NONE;
02737 InvalidateWindowData(WC_SPRITE_ALIGNER, 0, 1);
02738 }
02739
02740 if (click == MC_LEFT && _newgrf_debug_sprite_picker.mode == SPM_WAIT_CLICK) {
02741
02742 Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
02743 _newgrf_debug_sprite_picker.clicked_pixel = blitter->MoveTo(_screen.dst_ptr, _cursor.pos.x, _cursor.pos.y);
02744 _newgrf_debug_sprite_picker.click_time = _realtime_tick;
02745 _newgrf_debug_sprite_picker.sprites.Clear();
02746 _newgrf_debug_sprite_picker.mode = SPM_REDRAW;
02747 MarkWholeScreenDirty();
02748 } else {
02749 MouseLoop(click, mousewheel);
02750 }
02751
02752
02753
02754 _cursor.delta.x = 0;
02755 _cursor.delta.y = 0;
02756 }
02757
02761 static void CheckSoftLimit()
02762 {
02763 if (_settings_client.gui.window_soft_limit == 0) return;
02764
02765 for (;;) {
02766 uint deletable_count = 0;
02767 Window *w, *last_deletable = NULL;
02768 FOR_ALL_WINDOWS_FROM_FRONT(w) {
02769 if (w->window_class == WC_MAIN_WINDOW || IsVitalWindow(w) || (w->flags & WF_STICKY)) continue;
02770
02771 last_deletable = w;
02772 deletable_count++;
02773 }
02774
02775
02776 if (deletable_count <= _settings_client.gui.window_soft_limit) break;
02777
02778 assert(last_deletable != NULL);
02779 delete last_deletable;
02780 }
02781 }
02782
02786 void InputLoop()
02787 {
02788
02789
02790 assert(HasModalProgress() || IsLocalCompany());
02791
02792 CheckSoftLimit();
02793 HandleKeyScrolling();
02794
02795
02796 for (Window *v = _z_front_window; v != NULL; ) {
02797 Window *w = v;
02798 v = v->z_back;
02799
02800 if (w->window_class != WC_INVALID) continue;
02801
02802 RemoveWindowFromZOrdering(w);
02803 free(w);
02804 }
02805
02806 if (_scroller_click_timeout != 0) _scroller_click_timeout--;
02807 DecreaseWindowCounters();
02808
02809 if (_input_events_this_tick != 0) {
02810
02811 _input_events_this_tick = 0;
02812
02813 return;
02814 }
02815
02816
02817 HandleMouseEvents();
02818 HandleAutoscroll();
02819 }
02820
02824 void UpdateWindows()
02825 {
02826 Window *w;
02827
02828 static int highlight_timer = 1;
02829 if (--highlight_timer == 0) {
02830 highlight_timer = 15;
02831 _window_highlight_colour = !_window_highlight_colour;
02832 }
02833
02834 FOR_ALL_WINDOWS_FROM_FRONT(w) {
02835 w->ProcessScheduledInvalidations();
02836 w->ProcessHighlightedInvalidations();
02837 }
02838
02839 static int we4_timer = 0;
02840 int t = we4_timer + 1;
02841
02842 if (t >= 100) {
02843 FOR_ALL_WINDOWS_FROM_FRONT(w) {
02844 w->OnHundredthTick();
02845 }
02846 t = 0;
02847 }
02848 we4_timer = t;
02849
02850 FOR_ALL_WINDOWS_FROM_FRONT(w) {
02851 if ((w->flags & WF_WHITE_BORDER) && --w->white_border_timer == 0) {
02852 CLRBITS(w->flags, WF_WHITE_BORDER);
02853 w->SetDirty();
02854 }
02855 }
02856
02857 DrawDirtyBlocks();
02858
02859 FOR_ALL_WINDOWS_FROM_BACK(w) {
02860
02861 if (w->viewport != NULL && !w->IsShaded()) UpdateViewportPosition(w);
02862 }
02863 NetworkDrawChatMessage();
02864
02865 DrawMouseCursor();
02866 }
02867
02873 void SetWindowDirty(WindowClass cls, WindowNumber number)
02874 {
02875 const Window *w;
02876 FOR_ALL_WINDOWS_FROM_BACK(w) {
02877 if (w->window_class == cls && w->window_number == number) w->SetDirty();
02878 }
02879 }
02880
02887 void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index)
02888 {
02889 const Window *w;
02890 FOR_ALL_WINDOWS_FROM_BACK(w) {
02891 if (w->window_class == cls && w->window_number == number) {
02892 w->SetWidgetDirty(widget_index);
02893 }
02894 }
02895 }
02896
02901 void SetWindowClassesDirty(WindowClass cls)
02902 {
02903 Window *w;
02904 FOR_ALL_WINDOWS_FROM_BACK(w) {
02905 if (w->window_class == cls) w->SetDirty();
02906 }
02907 }
02908
02914 void Window::InvalidateData(int data, bool gui_scope)
02915 {
02916 this->SetDirty();
02917 if (!gui_scope) {
02918
02919 *this->scheduled_invalidation_data.Append() = data;
02920 }
02921 this->OnInvalidateData(data, gui_scope);
02922 }
02923
02927 void Window::ProcessScheduledInvalidations()
02928 {
02929 for (int *data = this->scheduled_invalidation_data.Begin(); this->window_class != WC_INVALID && data != this->scheduled_invalidation_data.End(); data++) {
02930 this->OnInvalidateData(*data, true);
02931 }
02932 this->scheduled_invalidation_data.Clear();
02933 }
02934
02938 void Window::ProcessHighlightedInvalidations()
02939 {
02940 if ((this->flags & WF_HIGHLIGHTED) == 0) return;
02941
02942 for (uint i = 0; i < this->nested_array_size; i++) {
02943 if (this->IsWidgetHighlighted(i)) this->SetWidgetDirty(i);
02944 }
02945 }
02946
02973 void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
02974 {
02975 Window *w;
02976 FOR_ALL_WINDOWS_FROM_BACK(w) {
02977 if (w->window_class == cls && w->window_number == number) {
02978 w->InvalidateData(data, gui_scope);
02979 }
02980 }
02981 }
02982
02991 void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
02992 {
02993 Window *w;
02994
02995 FOR_ALL_WINDOWS_FROM_BACK(w) {
02996 if (w->window_class == cls) {
02997 w->InvalidateData(data, gui_scope);
02998 }
02999 }
03000 }
03001
03005 void CallWindowTickEvent()
03006 {
03007 Window *w;
03008 FOR_ALL_WINDOWS_FROM_FRONT(w) {
03009 w->OnTick();
03010 }
03011 }
03012
03019 void DeleteNonVitalWindows()
03020 {
03021 Window *w;
03022
03023 restart_search:
03024
03025
03026
03027 FOR_ALL_WINDOWS_FROM_BACK(w) {
03028 if (w->window_class != WC_MAIN_WINDOW &&
03029 w->window_class != WC_SELECT_GAME &&
03030 w->window_class != WC_MAIN_TOOLBAR &&
03031 w->window_class != WC_STATUS_BAR &&
03032 w->window_class != WC_TOOLTIPS &&
03033 (w->flags & WF_STICKY) == 0) {
03034
03035 delete w;
03036 goto restart_search;
03037 }
03038 }
03039 }
03040
03048 void DeleteAllNonVitalWindows()
03049 {
03050 Window *w;
03051
03052
03053 DeleteNonVitalWindows();
03054
03055 restart_search:
03056
03057
03058
03059 FOR_ALL_WINDOWS_FROM_BACK(w) {
03060 if (w->flags & WF_STICKY) {
03061 delete w;
03062 goto restart_search;
03063 }
03064 }
03065 }
03066
03071 void DeleteConstructionWindows()
03072 {
03073 Window *w;
03074
03075 restart_search:
03076
03077
03078
03079 FOR_ALL_WINDOWS_FROM_BACK(w) {
03080 if (w->window_desc->flags & WDF_CONSTRUCTION) {
03081 delete w;
03082 goto restart_search;
03083 }
03084 }
03085
03086 FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty();
03087 }
03088
03090 void HideVitalWindows()
03091 {
03092 DeleteWindowById(WC_MAIN_TOOLBAR, 0);
03093 DeleteWindowById(WC_STATUS_BAR, 0);
03094 }
03095
03097 void ReInitAllWindows()
03098 {
03099 NWidgetLeaf::InvalidateDimensionCache();
03100 NWidgetScrollbar::InvalidateDimensionCache();
03101
03102 Window *w;
03103 FOR_ALL_WINDOWS_FROM_BACK(w) {
03104 w->ReInit();
03105 }
03106 #ifdef ENABLE_NETWORK
03107 void NetworkReInitChatBoxSize();
03108 NetworkReInitChatBoxSize();
03109 #endif
03110
03111
03112 RelocateAllWindows(_cur_resolution.width, _cur_resolution.height);
03113 MarkWholeScreenDirty();
03114 }
03115
03123 static int PositionWindow(Window *w, WindowClass clss, int setting)
03124 {
03125 if (w == NULL || w->window_class != clss) {
03126 w = FindWindowById(clss, 0);
03127 }
03128 if (w == NULL) return 0;
03129
03130 int old_left = w->left;
03131 switch (setting) {
03132 case 1: w->left = (_screen.width - w->width) / 2; break;
03133 case 2: w->left = _screen.width - w->width; break;
03134 default: w->left = 0; break;
03135 }
03136 if (w->viewport != NULL) w->viewport->left += w->left - old_left;
03137 SetDirtyBlocks(0, w->top, _screen.width, w->top + w->height);
03138 return w->left;
03139 }
03140
03146 int PositionMainToolbar(Window *w)
03147 {
03148 DEBUG(misc, 5, "Repositioning Main Toolbar...");
03149 return PositionWindow(w, WC_MAIN_TOOLBAR, _settings_client.gui.toolbar_pos);
03150 }
03151
03157 int PositionStatusbar(Window *w)
03158 {
03159 DEBUG(misc, 5, "Repositioning statusbar...");
03160 return PositionWindow(w, WC_STATUS_BAR, _settings_client.gui.statusbar_pos);
03161 }
03162
03168 int PositionNewsMessage(Window *w)
03169 {
03170 DEBUG(misc, 5, "Repositioning news message...");
03171 return PositionWindow(w, WC_NEWS_WINDOW, _settings_client.gui.statusbar_pos);
03172 }
03173
03179 int PositionNetworkChatWindow(Window *w)
03180 {
03181 DEBUG(misc, 5, "Repositioning network chat window...");
03182 return PositionWindow(w, WC_SEND_NETWORK_MSG, _settings_client.gui.statusbar_pos);
03183 }
03184
03185
03191 void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index)
03192 {
03193 Window *w;
03194 FOR_ALL_WINDOWS_FROM_BACK(w) {
03195 if (w->viewport != NULL && w->viewport->follow_vehicle == from_index) {
03196 w->viewport->follow_vehicle = to_index;
03197 w->SetDirty();
03198 }
03199 }
03200 }
03201
03202
03208 void RelocateAllWindows(int neww, int newh)
03209 {
03210 Window *w;
03211
03212 FOR_ALL_WINDOWS_FROM_BACK(w) {
03213 int left, top;
03214
03215 if (w->window_class == WC_MAIN_WINDOW) {
03216 ViewPort *vp = w->viewport;
03217 vp->width = w->width = neww;
03218 vp->height = w->height = newh;
03219 vp->virtual_width = ScaleByZoom(neww, vp->zoom);
03220 vp->virtual_height = ScaleByZoom(newh, vp->zoom);
03221 continue;
03222 }
03223
03224
03225
03226 switch (w->window_class) {
03227 case WC_BOOTSTRAP:
03228 ResizeWindow(w, neww, newh);
03229 continue;
03230
03231 case WC_MAIN_TOOLBAR:
03232 ResizeWindow(w, min(neww, w->window_desc->default_width) - w->width, 0, false);
03233
03234 top = w->top;
03235 left = PositionMainToolbar(w);
03236 break;
03237
03238 case WC_NEWS_WINDOW:
03239 top = newh - w->height;
03240 left = PositionNewsMessage(w);
03241 break;
03242
03243 case WC_STATUS_BAR:
03244 ResizeWindow(w, min(neww, w->window_desc->default_width) - w->width, 0, false);
03245
03246 top = newh - w->height;
03247 left = PositionStatusbar(w);
03248 break;
03249
03250 case WC_SEND_NETWORK_MSG:
03251 ResizeWindow(w, Clamp(neww, 320, 640) - w->width, 0, false);
03252 top = newh - w->height - FindWindowById(WC_STATUS_BAR, 0)->height;
03253 left = PositionNetworkChatWindow(w);
03254 break;
03255
03256 case WC_CONSOLE:
03257 IConsoleResize(w);
03258 continue;
03259
03260 default: {
03261 if (w->flags & WF_CENTERED) {
03262 top = (newh - w->height) >> 1;
03263 left = (neww - w->width) >> 1;
03264 break;
03265 }
03266
03267 left = w->left;
03268 if (left + (w->width >> 1) >= neww) left = neww - w->width;
03269 if (left < 0) left = 0;
03270
03271 top = w->top;
03272 if (top + (w->height >> 1) >= newh) top = newh - w->height;
03273 break;
03274 }
03275 }
03276
03277 EnsureVisibleCaption(w, left, top);
03278 }
03279 }
03280
03286 PickerWindowBase::~PickerWindowBase()
03287 {
03288 this->window_class = WC_INVALID;
03289 ResetObjectToPlace();
03290 }