(svn r23005) -Fix (r23004): Of course there's still the 16-sprite version for shore...
[openttd/fttd.git] / src / signs_gui.cpp
blobe0ab72e019b2f4f25e0724815a4e0f46b8a1c9af
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file signs_gui.cpp The GUI for signs. */
12 #include "stdafx.h"
13 #include "company_gui.h"
14 #include "company_func.h"
15 #include "signs_base.h"
16 #include "signs_func.h"
17 #include "debug.h"
18 #include "command_func.h"
19 #include "strings_func.h"
20 #include "window_func.h"
21 #include "map_func.h"
22 #include "gfx_func.h"
23 #include "viewport_func.h"
24 #include "querystring_gui.h"
25 #include "sortlist_type.h"
26 #include "string_func.h"
27 #include "core/geometry_func.hpp"
28 #include "hotkeys.h"
29 #include "transparency.h"
31 #include "table/strings.h"
32 #include "table/sprites.h"
34 /**
35 * Contains the necessary information to decide if a sign should
36 * be filtered out or not. This struct is sent as parameter to the
37 * sort functions of the GUISignList.
39 struct FilterInfo {
40 const char *string; ///< String to match sign names against
41 bool case_sensitive; ///< Should case sensitive matching be used?
44 struct SignList {
45 /**
46 * A GUIList contains signs and uses a custom data structure called #FilterInfo for
47 * passing data to the sort functions.
49 typedef GUIList<const Sign *, FilterInfo> GUISignList;
51 static const Sign *last_sign;
52 GUISignList signs;
54 char filter_string[MAX_LENGTH_SIGN_NAME_CHARS * MAX_CHAR_LENGTH]; ///< The match string to be used when the GUIList is (re)-sorted.
55 static bool match_case; ///< Should case sensitive matching be used?
57 /**
58 * Creates a SignList with filtering disabled by default.
60 SignList()
62 filter_string[0] = '\0';
65 void BuildSignsList()
67 if (!this->signs.NeedRebuild()) return;
69 DEBUG(misc, 3, "Building sign list");
71 this->signs.Clear();
73 const Sign *si;
74 FOR_ALL_SIGNS(si) *this->signs.Append() = si;
76 this->FilterSignList();
77 this->signs.Compact();
78 this->signs.RebuildDone();
81 /** Sort signs by their name */
82 static int CDECL SignNameSorter(const Sign * const *a, const Sign * const *b)
84 static char buf_cache[64];
85 char buf[64];
87 SetDParam(0, (*a)->index);
88 GetString(buf, STR_SIGN_NAME, lastof(buf));
90 if (*b != last_sign) {
91 last_sign = *b;
92 SetDParam(0, (*b)->index);
93 GetString(buf_cache, STR_SIGN_NAME, lastof(buf_cache));
96 int r = strnatcmp(buf, buf_cache); // Sort by name (natural sorting).
98 return r != 0 ? r : ((*a)->index - (*b)->index);
101 void SortSignsList()
103 if (!this->signs.Sort(&SignNameSorter)) return;
105 /* Reset the name sorter sort cache */
106 this->last_sign = NULL;
109 /** Filter sign list by sign name (case sensitive setting in FilterInfo) */
110 static bool CDECL SignNameFilter(const Sign * const *a, FilterInfo filter_info)
112 /* Get sign string */
113 char buf1[MAX_LENGTH_SIGN_NAME_CHARS * MAX_CHAR_LENGTH];
114 SetDParam(0, (*a)->index);
115 GetString(buf1, STR_SIGN_NAME, lastof(buf1));
117 return (filter_info.case_sensitive ? strstr(buf1, filter_info.string) : strcasestr(buf1, filter_info.string)) != NULL;
120 /** Filter sign list by owner */
121 static bool CDECL OwnerVisibilityFilter(const Sign * const *a, FilterInfo filter_info)
123 assert(!HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS));
124 /* Hide sign if non-own signs are hidden in the viewport */
125 return (*a)->owner == _local_company;
128 /** Filter out signs from the sign list that does not match the name filter */
129 void FilterSignList()
131 FilterInfo filter_info = {this->filter_string, this->match_case};
132 this->signs.Filter(&SignNameFilter, filter_info);
133 if (!HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS)) {
134 this->signs.Filter(&OwnerVisibilityFilter, filter_info);
139 const Sign *SignList::last_sign = NULL;
140 bool SignList::match_case = false;
142 /** Enum referring to the widgets in the sign list window */
143 enum SignListWidgets {
144 SLW_CAPTION,
145 SLW_LIST,
146 SLW_SCROLLBAR,
147 SLW_FILTER_TEXT, ///< Text box for typing a filter string
148 SLW_FILTER_MATCH_CASE_BTN, ///< Button to toggle if case sensitive filtering should be used
149 SLW_FILTER_CLEAR_BTN, ///< Button to clear the filter
152 /** Enum referring to the Hotkeys in the sign list window */
153 enum SignListHotkeys {
154 SLHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string
157 struct SignListWindow : QueryStringBaseWindow, SignList {
158 int text_offset; ///< Offset of the sign text relative to the left edge of the SLW_LIST widget.
159 Scrollbar *vscroll;
161 SignListWindow(const WindowDesc *desc, WindowNumber window_number) : QueryStringBaseWindow(MAX_LENGTH_SIGN_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_SIGN_NAME_CHARS)
163 this->CreateNestedTree(desc);
164 this->vscroll = this->GetScrollbar(SLW_SCROLLBAR);
165 this->FinishInitNested(desc, window_number);
166 this->SetWidgetLoweredState(SLW_FILTER_MATCH_CASE_BTN, SignList::match_case);
168 /* Initialize the text edit widget */
169 this->afilter = CS_ALPHANUMERAL;
170 InitializeTextBuffer(&this->text, this->edit_str_buf, MAX_LENGTH_SIGN_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_SIGN_NAME_CHARS);
171 ClearFilterTextWidget();
173 /* Initialize the filtering variables */
174 this->SetFilterString("");
176 /* Create initial list. */
177 this->signs.ForceRebuild();
178 this->signs.ForceResort();
179 this->BuildSortSignList();
183 * Empties the string buffer that is edited by the filter text edit widget.
184 * It also triggers the redraw of the widget so it become visible that the string has been made empty.
186 void ClearFilterTextWidget()
188 this->edit_str_buf[0] = '\0';
189 UpdateTextBufferSize(&this->text);
191 this->SetWidgetDirty(SLW_FILTER_TEXT);
195 * This function sets the filter string of the sign list. The contents of
196 * the edit widget is not updated by this function. Depending on if the
197 * new string is zero-length or not the clear button is made
198 * disabled/enabled. The sign list is updated according to the new filter.
200 void SetFilterString(const char *new_filter_string)
202 /* check if there is a new filter string */
203 if (!StrEmpty(new_filter_string)) {
204 /* Copy new filter string */
205 strecpy(this->filter_string, new_filter_string, lastof(this->filter_string));
207 this->signs.SetFilterState(true);
209 this->EnableWidget(SLW_FILTER_CLEAR_BTN);
210 } else {
211 /* There is no new string -> clear this->filter_string */
212 this->filter_string[0] = '\0';
214 this->signs.SetFilterState(!HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS)); // keep sign list filtering active if competitor signs should be hidden
215 this->DisableWidget(SLW_FILTER_CLEAR_BTN);
218 /* Repaint the clear button since its disabled state may have changed */
219 this->SetWidgetDirty(SLW_FILTER_CLEAR_BTN);
221 /* Rebuild the list of signs */
222 this->InvalidateData();
225 virtual void OnPaint()
227 if (this->signs.NeedRebuild()) this->BuildSortSignList();
228 this->DrawWidgets();
229 if (!this->IsShaded()) this->DrawEditBox(SLW_FILTER_TEXT);
232 virtual void DrawWidget(const Rect &r, int widget) const
234 switch (widget) {
235 case SLW_LIST: {
236 uint y = r.top + WD_FRAMERECT_TOP; // Offset from top of widget.
237 /* No signs? */
238 if (this->vscroll->GetCount() == 0) {
239 DrawString(r.left + WD_FRAMETEXT_LEFT, r.right, y, STR_STATION_LIST_NONE);
240 return;
243 bool rtl = _current_text_dir == TD_RTL;
244 int sprite_offset_y = (FONT_HEIGHT_NORMAL - 10) / 2 + 1;
245 uint icon_left = 4 + (rtl ? r.right - this->text_offset : r.left);
246 uint text_left = r.left + (rtl ? WD_FRAMERECT_LEFT : this->text_offset);
247 uint text_right = r.right - (rtl ? this->text_offset : WD_FRAMERECT_RIGHT);
249 /* At least one sign available. */
250 for (uint16 i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < this->vscroll->GetCount(); i++) {
251 const Sign *si = this->signs[i];
253 if (si->owner != OWNER_NONE) DrawCompanyIcon(si->owner, icon_left, y + sprite_offset_y);
255 SetDParam(0, si->index);
256 DrawString(text_left, text_right, y, STR_SIGN_NAME, TC_YELLOW);
257 y += this->resize.step_height;
259 break;
264 virtual void SetStringParameters(int widget) const
266 if (widget == SLW_CAPTION) SetDParam(0, this->vscroll->GetCount());
269 virtual void OnClick(Point pt, int widget, int click_count)
271 switch (widget) {
272 case SLW_LIST: {
273 uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, SLW_LIST, WD_FRAMERECT_TOP);
274 if (id_v == INT_MAX) return;
276 const Sign *si = this->signs[id_v];
277 ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
278 break;
280 case SLW_FILTER_CLEAR_BTN:
281 this->ClearFilterTextWidget(); // Empty the text in the EditBox widget
282 this->SetFilterString(""); // Use empty text as filter text (= view all signs)
283 break;
285 case SLW_FILTER_MATCH_CASE_BTN:
286 SignList::match_case = !SignList::match_case; // Toggle match case
287 this->SetWidgetLoweredState(SLW_FILTER_MATCH_CASE_BTN, SignList::match_case); // Toggle button pushed state
288 this->InvalidateData(); // Rebuild the list of signs
289 break;
293 virtual void OnResize()
295 this->vscroll->SetCapacityFromWidget(this, SLW_LIST, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
298 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
300 switch (widget) {
301 case SLW_LIST: {
302 Dimension spr_dim = GetSpriteSize(SPR_COMPANY_ICON);
303 this->text_offset = WD_FRAMETEXT_LEFT + spr_dim.width + 2; // 2 pixels space between icon and the sign text.
304 resize->height = max<uint>(FONT_HEIGHT_NORMAL, spr_dim.height);
305 Dimension d = {this->text_offset + WD_FRAMETEXT_RIGHT, WD_FRAMERECT_TOP + 5 * resize->height + WD_FRAMERECT_BOTTOM};
306 *size = maxdim(*size, d);
307 break;
310 case SLW_CAPTION:
311 SetDParam(0, max<size_t>(1000, Sign::GetPoolSize()));
312 *size = GetStringBoundingBox(STR_SIGN_LIST_CAPTION);
313 size->height += padding.height;
314 size->width += padding.width;
315 break;
319 virtual EventState OnKeyPress(uint16 key, uint16 keycode)
321 EventState state = ES_NOT_HANDLED;
322 switch (this->HandleEditBoxKey(SLW_FILTER_TEXT, key, keycode, state)) {
323 case HEBR_EDITING:
324 this->SetFilterString(this->text.buf);
325 break;
327 case HEBR_CONFIRM: // Enter pressed -> goto first sign in list
328 if (this->signs.Length() >= 1) {
329 const Sign *si = this->signs[0];
330 ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
332 return state;
334 case HEBR_CANCEL: // ESC pressed, clear filter.
335 this->OnClick(Point(), SLW_FILTER_CLEAR_BTN, 1); // Simulate click on clear button.
336 this->UnfocusFocusedWidget(); // Unfocus the text box.
337 return state;
339 case HEBR_NOT_FOCUSED: // The filter text box is not globaly focused.
340 if (CheckHotkeyMatch(signlist_hotkeys, keycode, this) == SLHK_FOCUS_FILTER_BOX) {
341 this->SetFocusedWidget(SLW_FILTER_TEXT);
342 SetFocusedWindow(this); // The user has asked to give focus to the text box, so make sure this window is focused.
343 state = ES_HANDLED;
345 break;
347 default:
348 NOT_REACHED();
351 if (state == ES_HANDLED) OnOSKInput(SLW_FILTER_TEXT);
353 return state;
356 virtual void OnOSKInput(int widget)
358 if (widget == SLW_FILTER_TEXT) this->SetFilterString(this->text.buf);
361 virtual void OnMouseLoop()
363 this->HandleEditBox(SLW_FILTER_TEXT);
366 void BuildSortSignList()
368 if (this->signs.NeedRebuild()) {
369 this->BuildSignsList();
370 this->vscroll->SetCount(this->signs.Length());
371 this->SetWidgetDirty(SLW_CAPTION);
373 this->SortSignsList();
376 virtual void OnHundredthTick()
378 this->BuildSortSignList();
379 this->SetDirty();
383 * Some data on this window has become invalid.
384 * @param data Information about the changed data.
385 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
387 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
389 if (data == -1) {
390 /* The DO_SHOW_COMPETITOR_SIGNS display option has changed */
391 this->signs.SetFilterState(!StrEmpty(this->filter_string) || !HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS));
394 /* When there is a filter string, we always need to rebuild the list even if
395 * the amount of signs in total is unchanged, as the subset of signs that is
396 * accepted by the filter might has changed. */
397 if (data == 0 || data == -1 || !StrEmpty(this->filter_string)) { // New or deleted sign, changed visibility setting or there is a filter string
398 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
399 this->signs.ForceRebuild();
400 } else { // Change of sign contents while there is no filter string
401 this->signs.ForceResort();
405 static Hotkey<SignListWindow> signlist_hotkeys[];
408 Hotkey<SignListWindow> SignListWindow::signlist_hotkeys[] = {
409 Hotkey<SignListWindow>('F', "focus_filter_box", SLHK_FOCUS_FILTER_BOX),
410 HOTKEY_LIST_END(SignListWindow)
412 Hotkey<SignListWindow> *_signlist_hotkeys = SignListWindow::signlist_hotkeys;
414 static const NWidgetPart _nested_sign_list_widgets[] = {
415 NWidget(NWID_HORIZONTAL),
416 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
417 NWidget(WWT_CAPTION, COLOUR_GREY, SLW_CAPTION), SetDataTip(STR_SIGN_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
418 NWidget(WWT_SHADEBOX, COLOUR_GREY),
419 NWidget(WWT_STICKYBOX, COLOUR_GREY),
420 EndContainer(),
421 NWidget(NWID_HORIZONTAL),
422 NWidget(NWID_VERTICAL),
423 NWidget(WWT_PANEL, COLOUR_GREY, SLW_LIST), SetMinimalSize(WD_FRAMETEXT_LEFT + 16 + 255 + WD_FRAMETEXT_RIGHT, 50),
424 SetResize(1, 10), SetFill(1, 0), SetScrollbar(SLW_SCROLLBAR), EndContainer(),
425 NWidget(NWID_HORIZONTAL),
426 NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1),
427 NWidget(WWT_EDITBOX, COLOUR_GREY, SLW_FILTER_TEXT), SetMinimalSize(80, 12), SetResize(1, 0), SetFill(1, 0), SetPadding(2, 2, 2, 2),
428 SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
429 EndContainer(),
430 NWidget(WWT_TEXTBTN, COLOUR_GREY, SLW_FILTER_MATCH_CASE_BTN), SetDataTip(STR_SIGN_LIST_MATCH_CASE, STR_SIGN_LIST_MATCH_CASE_TOOLTIP),
431 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLW_FILTER_CLEAR_BTN), SetDataTip(STR_SIGN_LIST_CLEAR, STR_SIGN_LIST_CLEAR_TOOLTIP),
432 EndContainer(),
433 EndContainer(),
434 NWidget(NWID_VERTICAL),
435 NWidget(NWID_VERTICAL), SetFill(0, 1),
436 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, SLW_SCROLLBAR),
437 EndContainer(),
438 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
439 EndContainer(),
440 EndContainer(),
443 static const WindowDesc _sign_list_desc(
444 WDP_AUTO, 358, 138,
445 WC_SIGN_LIST, WC_NONE,
446 WDF_UNCLICK_BUTTONS,
447 _nested_sign_list_widgets, lengthof(_nested_sign_list_widgets)
451 * Open the sign list window
453 * @return newly opened sign list window, or NULL if the window could not be opened.
455 Window *ShowSignList()
457 return AllocateWindowDescFront<SignListWindow>(&_sign_list_desc, 0);
460 EventState SignListGlobalHotkeys(uint16 key, uint16 keycode)
462 int num = CheckHotkeyMatch<SignListWindow>(_signlist_hotkeys, keycode, NULL, true);
463 if (num == -1) return ES_NOT_HANDLED;
464 Window *w = ShowSignList();
465 if (w == NULL) return ES_NOT_HANDLED;
466 return w->OnKeyPress(key, keycode);
470 * Actually rename the sign.
471 * @param index the sign to rename.
472 * @param text the new name.
473 * @return true if the window will already be removed after returning.
475 static bool RenameSign(SignID index, const char *text)
477 bool remove = StrEmpty(text);
478 DoCommandP(0, index, 0, CMD_RENAME_SIGN | (StrEmpty(text) ? CMD_MSG(STR_ERROR_CAN_T_DELETE_SIGN) : CMD_MSG(STR_ERROR_CAN_T_CHANGE_SIGN_NAME)), NULL, text);
479 return remove;
482 /** Widget numbers of the query sign edit window. */
483 enum QueryEditSignWidgets {
484 QUERY_EDIT_SIGN_WIDGET_CAPTION,
485 QUERY_EDIT_SIGN_WIDGET_TEXT,
486 QUERY_EDIT_SIGN_WIDGET_OK,
487 QUERY_EDIT_SIGN_WIDGET_CANCEL,
488 QUERY_EDIT_SIGN_WIDGET_DELETE,
489 QUERY_EDIT_SIGN_WIDGET_PREVIOUS,
490 QUERY_EDIT_SIGN_WIDGET_NEXT,
493 struct SignWindow : QueryStringBaseWindow, SignList {
494 SignID cur_sign;
496 SignWindow(const WindowDesc *desc, const Sign *si) : QueryStringBaseWindow(MAX_LENGTH_SIGN_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_SIGN_NAME_CHARS)
498 this->caption = STR_EDIT_SIGN_CAPTION;
499 this->afilter = CS_ALPHANUMERAL;
501 this->InitNested(desc);
503 this->LowerWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
504 UpdateSignEditWindow(si);
505 this->SetFocusedWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
508 void UpdateSignEditWindow(const Sign *si)
510 char *last_of = &this->edit_str_buf[this->edit_str_size - 1]; // points to terminating '\0'
512 /* Display an empty string when the sign hasnt been edited yet */
513 if (si->name != NULL) {
514 SetDParam(0, si->index);
515 GetString(this->edit_str_buf, STR_SIGN_NAME, last_of);
516 } else {
517 GetString(this->edit_str_buf, STR_EMPTY, last_of);
519 *last_of = '\0';
521 this->cur_sign = si->index;
522 InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, this->max_chars);
524 this->SetWidgetDirty(QUERY_EDIT_SIGN_WIDGET_TEXT);
525 this->SetFocusedWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
529 * Returns a pointer to the (alphabetically) previous or next sign of the current sign.
530 * @param next false if the previous sign is wanted, true if the next sign is wanted
531 * @return pointer to the previous/next sign
533 const Sign *PrevNextSign(bool next)
535 /* Rebuild the sign list */
536 this->signs.ForceRebuild();
537 this->signs.NeedResort();
538 this->BuildSignsList();
539 this->SortSignsList();
541 /* Search through the list for the current sign, excluding
542 * - the first sign if we want the previous sign or
543 * - the last sign if we want the next sign */
544 uint end = this->signs.Length() - (next ? 1 : 0);
545 for (uint i = next ? 0 : 1; i < end; i++) {
546 if (this->cur_sign == this->signs[i]->index) {
547 /* We've found the current sign, so return the sign before/after it */
548 return this->signs[i + (next ? 1 : -1)];
551 /* If we haven't found the current sign by now, return the last/first sign */
552 return this->signs[next ? 0 : this->signs.Length() - 1];
555 virtual void SetStringParameters(int widget) const
557 switch (widget) {
558 case QUERY_EDIT_SIGN_WIDGET_CAPTION:
559 SetDParam(0, this->caption);
560 break;
564 virtual void OnPaint()
566 this->DrawWidgets();
567 if (!this->IsShaded()) this->DrawEditBox(QUERY_EDIT_SIGN_WIDGET_TEXT);
570 virtual void OnClick(Point pt, int widget, int click_count)
572 switch (widget) {
573 case QUERY_EDIT_SIGN_WIDGET_PREVIOUS:
574 case QUERY_EDIT_SIGN_WIDGET_NEXT: {
575 const Sign *si = this->PrevNextSign(widget == QUERY_EDIT_SIGN_WIDGET_NEXT);
577 /* Rebuild the sign list */
578 this->signs.ForceRebuild();
579 this->signs.NeedResort();
580 this->BuildSignsList();
581 this->SortSignsList();
583 /* Scroll to sign and reopen window */
584 ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
585 UpdateSignEditWindow(si);
586 break;
589 case QUERY_EDIT_SIGN_WIDGET_DELETE:
590 /* Only need to set the buffer to null, the rest is handled as the OK button */
591 RenameSign(this->cur_sign, "");
592 /* don't delete this, we are deleted in Sign::~Sign() -> DeleteRenameSignWindow() */
593 break;
595 case QUERY_EDIT_SIGN_WIDGET_OK:
596 if (RenameSign(this->cur_sign, this->text.buf)) break;
597 /* FALL THROUGH */
599 case QUERY_EDIT_SIGN_WIDGET_CANCEL:
600 delete this;
601 break;
605 virtual EventState OnKeyPress(uint16 key, uint16 keycode)
607 EventState state = ES_NOT_HANDLED;
608 switch (this->HandleEditBoxKey(QUERY_EDIT_SIGN_WIDGET_TEXT, key, keycode, state)) {
609 default: break;
611 case HEBR_CONFIRM:
612 if (RenameSign(this->cur_sign, this->text.buf)) break;
613 /* FALL THROUGH */
615 case HEBR_CANCEL: // close window, abandon changes
616 delete this;
617 break;
619 return state;
622 virtual void OnMouseLoop()
624 this->HandleEditBox(QUERY_EDIT_SIGN_WIDGET_TEXT);
627 virtual void OnOpenOSKWindow(int wid)
629 ShowOnScreenKeyboard(this, wid, QUERY_EDIT_SIGN_WIDGET_CANCEL, QUERY_EDIT_SIGN_WIDGET_OK);
633 static const NWidgetPart _nested_query_sign_edit_widgets[] = {
634 NWidget(NWID_HORIZONTAL),
635 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
636 NWidget(WWT_CAPTION, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
637 EndContainer(),
638 NWidget(WWT_PANEL, COLOUR_GREY),
639 NWidget(WWT_EDITBOX, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_TEXT), SetMinimalSize(256, 12), SetDataTip(STR_EDIT_SIGN_SIGN_OSKTITLE, STR_NULL), SetPadding(2, 2, 2, 2),
640 EndContainer(),
641 NWidget(NWID_HORIZONTAL),
642 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_OK), SetMinimalSize(61, 12), SetDataTip(STR_BUTTON_OK, STR_NULL),
643 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_CANCEL), SetMinimalSize(60, 12), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
644 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_DELETE), SetMinimalSize(60, 12), SetDataTip(STR_TOWN_VIEW_DELETE_BUTTON, STR_NULL),
645 NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), EndContainer(),
646 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_PREVIOUS), SetMinimalSize(11, 12), SetDataTip(AWV_DECREASE, STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP),
647 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_NEXT), SetMinimalSize(11, 12), SetDataTip(AWV_INCREASE, STR_EDIT_SIGN_NEXT_SIGN_TOOLTIP),
648 EndContainer(),
651 static const WindowDesc _query_sign_edit_desc(
652 WDP_AUTO, 0, 0,
653 WC_QUERY_STRING, WC_NONE,
654 WDF_CONSTRUCTION | WDF_UNCLICK_BUTTONS,
655 _nested_query_sign_edit_widgets, lengthof(_nested_query_sign_edit_widgets)
659 * Handle clicking on a sign.
660 * @param si The sign that was clicked on.
662 void HandleClickOnSign(const Sign *si)
664 if (_ctrl_pressed && si->owner == _local_company) {
665 RenameSign(si->index, NULL);
666 return;
668 ShowRenameSignWindow(si);
672 * Show the window to change the text of a sign.
673 * @param si The sign to show the window for.
675 void ShowRenameSignWindow(const Sign *si)
677 /* Delete all other edit windows */
678 DeleteWindowById(WC_QUERY_STRING, 0);
680 new SignWindow(&_query_sign_edit_desc, si);
684 * Close the sign window associated with the given sign.
685 * @param sign The sign to close the window for.
687 void DeleteRenameSignWindow(SignID sign)
689 SignWindow *w = dynamic_cast<SignWindow *>(FindWindowById(WC_QUERY_STRING, 0));
691 if (w != NULL && w->cur_sign == sign) delete w;