# dropdown.cpp

Go to the documentation of this file.

    00001 /* $Id: dropdown.cpp 26128 2013-11-26 15:12:54Z rubidium $ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
    00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
    00008  */
    00009
    00012 #include "../stdafx.h"
    00013 #include "../window_gui.h"
    00014 #include "../string_func.h"
    00015 #include "../strings_func.h"
    00016 #include "../window_func.h"
    00017 #include "dropdown_type.h"
    00018
    00019 #include "dropdown_widget.h"
    00020
    00021
    00022 void DropDownListItem::Draw(int left, int right, int top, int bottom, bool sel, int bg_colour) const
    00023 {
    00024   int c1 = _colour_gradient[bg_colour][3];
    00025   int c2 = _colour_gradient[bg_colour][7];
    00026
    00027   int mid = top + this->Height(0) / 2;
    00028   GfxFillRect(left + 1, mid - 2, right - 1, mid - 2, c1);
    00029   GfxFillRect(left + 1, mid - 1, right - 1, mid - 1, c2);
    00030 }
    00031
    00032 uint DropDownListStringItem::Width() const
    00033 {
    00034   char buffer[512];
    00035   GetString(buffer, this->String(), lastof(buffer));
    00036   return GetStringBoundingBox(buffer).width;
    00037 }
    00038
    00039 void DropDownListStringItem::Draw(int left, int right, int top, int bottom, bool sel, int bg_colour) const
    00040 {
    00041   DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, this->String(), sel ? TC_WHITE : TC_BLACK);
    00042 }
    00043
    00051 /* static */ int DropDownListStringItem::NatSortFunc(const DropDownListItem * const *first, const DropDownListItem * const * second)
    00052 {
    00053   char buffer1[512], buffer2[512];
    00054   GetString(buffer1, static_cast<const DropDownListStringItem*>(*first)->String(), lastof(buffer1));
    00055   GetString(buffer2, static_cast<const DropDownListStringItem*>(*second)->String(), lastof(buffer2));
    00056   return strnatcmp(buffer1, buffer2);
    00057 }
    00058
    00059 StringID DropDownListParamStringItem::String() const
    00060 {
    00061   for (uint i = 0; i < lengthof(this->decode_params); i++) SetDParam(i, this->decode_params[i]);
    00062   return this->string;
    00063 }
    00064
    00065 StringID DropDownListCharStringItem::String() const
    00066 {
    00067   SetDParamStr(0, this->raw_string);
    00068   return this->string;
    00069 }
    00070
    00071 static const NWidgetPart _nested_dropdown_menu_widgets[] = {
    00072   NWidget(NWID_HORIZONTAL),
    00073     NWidget(WWT_PANEL, COLOUR_END, WID_DM_ITEMS), SetMinimalSize(1, 1), SetScrollbar(WID_DM_SCROLL), EndContainer(),
    00074     NWidget(NWID_SELECTION, INVALID_COLOUR, WID_DM_SHOW_SCROLL),
    00075       NWidget(NWID_VSCROLLBAR, COLOUR_END, WID_DM_SCROLL),
    00076     EndContainer(),
    00077   EndContainer(),
    00078 };
    00079
    00080 static WindowDesc _dropdown_desc(
    00081   WDP_MANUAL, NULL, 0, 0,
    00082   WC_DROPDOWN_MENU, WC_NONE,
    00083   0,
    00084   _nested_dropdown_menu_widgets, lengthof(_nested_dropdown_menu_widgets)
    00085 );
    00086
    00088 struct DropdownWindow : Window {
    00089   WindowClass parent_wnd_class;
    00090   WindowNumber parent_wnd_num;
    00091   int parent_button;
    00092   const DropDownList *list;
    00093   int selected_index;
    00094   byte click_delay;
    00095   bool drag_mode;
    00096   bool instant_close;
    00097   int scrolling;
    00098   Point position;
    00099   Scrollbar *vscroll;
    00100
    00114   DropdownWindow(Window *parent, const DropDownList *list, int selected, int button, bool instant_close, const Point &position, const Dimension &size, Colours wi_colour, bool scroll)
    00115       : Window(&_dropdown_desc)
    00116   {
    00117     this->position = position;
    00118
    00119     this->CreateNestedTree();
    00120
    00121     this->vscroll = this->GetScrollbar(WID_DM_SCROLL);
    00122
    00123     uint items_width = size.width - (scroll ? NWidgetScrollbar::GetVerticalDimension().width : 0);
    00124     NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_DM_ITEMS);
    00125     nwi->SetMinimalSize(items_width, size.height + 4);
    00126     nwi->colour = wi_colour;
    00127
    00128     nwi = this->GetWidget<NWidgetCore>(WID_DM_SCROLL);
    00129     nwi->colour = wi_colour;
    00130
    00131     this->GetWidget<NWidgetStacked>(WID_DM_SHOW_SCROLL)->SetDisplayedPlane(scroll ? 0 : SZSP_NONE);
    00132
    00133     this->FinishInitNested(0);
    00134     CLRBITS(this->flags, WF_WHITE_BORDER);
    00135
    00136     /* Total length of list */
    00137     int list_height = 0;
    00138     for (const DropDownListItem * const *it = list->Begin(); it != list->End(); ++it) {
    00139       const DropDownListItem *item = *it;
    00140       list_height += item->Height(items_width);
    00141     }
    00142
    00143     /* Capacity is the average number of items visible */
    00144     this->vscroll->SetCapacity(size.height * (uint16)list->Length() / list_height);
    00145     this->vscroll->SetCount((uint16)list->Length());
    00146
    00147     this->parent_wnd_class = parent->window_class;
    00148     this->parent_wnd_num   = parent->window_number;
    00149     this->parent_button    = button;
    00150     this->list             = list;
    00151     this->selected_index   = selected;
    00152     this->click_delay      = 0;
    00153     this->drag_mode        = true;
    00154     this->instant_close    = instant_close;
    00155   }
    00156
    00157   ~DropdownWindow()
    00158   {
    00159     /* Make the dropdown "invisible", so it doesn't affect new window placement.
    00160      * Also mark it dirty in case the callback deals with the screen. (e.g. screenshots). */
    00161     this->window_class = WC_INVALID;
    00162     this->SetDirty();
    00163
    00164     Window *w2 = FindWindowById(this->parent_wnd_class, this->parent_wnd_num);
    00165     if (w2 != NULL) {
    00166       Point pt = _cursor.pos;
    00167       pt.x -= w2->left;
    00168       pt.y -= w2->top;
    00169       w2->OnDropdownClose(pt, this->parent_button, this->selected_index, this->instant_close);
    00170     }
    00171     delete this->list;
    00172   }
    00173
    00174   virtual Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number)
    00175   {
    00176     return this->position;
    00177   }
    00178
    00184   bool GetDropDownItem(int &value)
    00185   {
    00186     if (GetWidgetFromPos(this, _cursor.pos.x - this->left, _cursor.pos.y - this->top) < 0) return false;
    00187
    00188     NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_DM_ITEMS);
    00189     int y     = _cursor.pos.y - this->top - nwi->pos_y - 2;
    00190     int width = nwi->current_x - 4;
    00191     int pos   = this->vscroll->GetPosition();
    00192
    00193     const DropDownList *list = this->list;
    00194
    00195     for (const DropDownListItem * const *it = list->Begin(); it != list->End(); ++it) {
    00196       /* Skip items that are scrolled up */
    00197       if (--pos >= 0) continue;
    00198
    00199       const DropDownListItem *item = *it;
    00200       int item_height = item->Height(width);
    00201
    00202       if (y < item_height) {
    00203         if (item->masked || !item->Selectable()) return false;
    00204         value = item->result;
    00205         return true;
    00206       }
    00207
    00208       y -= item_height;
    00209     }
    00210
    00211     return false;
    00212   }
    00213
    00214   virtual void DrawWidget(const Rect &r, int widget) const
    00215   {
    00216     if (widget != WID_DM_ITEMS) return;
    00217
    00218     Colours colour = this->GetWidget<NWidgetCore>(widget)->colour;
    00219
    00220     int y = r.top + 2;
    00221     int pos = this->vscroll->GetPosition();
    00222     for (const DropDownListItem * const *it = this->list->Begin(); it != this->list->End(); ++it) {
    00223       const DropDownListItem *item = *it;
    00224       int item_height = item->Height(r.right - r.left + 1);
    00225
    00226       /* Skip items that are scrolled up */
    00227       if (--pos >= 0) continue;
    00228
    00229       if (y + item_height < r.bottom) {
    00230         bool selected = (this->selected_index == item->result);
    00231         if (selected) GfxFillRect(r.left + 2, y, r.right - 1, y + item_height - 1, PC_BLACK);
    00232
    00233         item->Draw(r.left, r.right, y, y + item_height, selected, colour);
    00234
    00235         if (item->masked) {
    00236           GfxFillRect(r.left + 1, y, r.right - 1, y + item_height - 1, _colour_gradient[colour][5], FILLRECT_CHECKER);
    00237         }
    00238       }
    00239       y += item_height;
    00240     }
    00241   }
    00242
    00243   virtual void OnClick(Point pt, int widget, int click_count)
    00244   {
    00245     if (widget != WID_DM_ITEMS) return;
    00246     int item;
    00247     if (this->GetDropDownItem(item)) {
    00248       this->click_delay = 4;
    00249       this->selected_index = item;
    00250       this->SetDirty();
    00251     }
    00252   }
    00253
    00254   virtual void OnTick()
    00255   {
    00256     if (this->scrolling != 0) {
    00257       int pos = this->vscroll->GetPosition();
    00258
    00259       this->vscroll->UpdatePosition(this->scrolling);
    00260       this->scrolling = 0;
    00261
    00262       if (pos != this->vscroll->GetPosition()) {
    00263         this->SetDirty();
    00264       }
    00265     }
    00266   }
    00267
    00268   virtual void OnMouseLoop()
    00269   {
    00270     Window *w2 = FindWindowById(this->parent_wnd_class, this->parent_wnd_num);
    00271     if (w2 == NULL) {
    00272       delete this;
    00273       return;
    00274     }
    00275
    00276     if (this->click_delay != 0 && --this->click_delay == 0) {
    00277       /* Make the dropdown "invisible", so it doesn't affect new window placement.
    00278        * Also mark it dirty in case the callback deals with the screen. (e.g. screenshots). */
    00279       this->window_class = WC_INVALID;
    00280       this->SetDirty();
    00281
    00282       w2->OnDropdownSelect(this->parent_button, this->selected_index);
    00283       delete this;
    00284       return;
    00285     }
    00286
    00287     if (this->drag_mode) {
    00288       int item;
    00289
    00290       if (!_left_button_clicked) {
    00291         this->drag_mode = false;
    00292         if (!this->GetDropDownItem(item)) {
    00293           if (this->instant_close) delete this;
    00294           return;
    00295         }
    00296         this->click_delay = 2;
    00297       } else {
    00298         if (_cursor.pos.y <= this->top + 2) {
    00299           /* Cursor is above the list, set scroll up */
    00300           this->scrolling = -1;
    00301           return;
    00302         } else if (_cursor.pos.y >= this->top + this->height - 2) {
    00303           /* Cursor is below list, set scroll down */
    00304           this->scrolling = 1;
    00305           return;
    00306         }
    00307
    00308         if (!this->GetDropDownItem(item)) return;
    00309       }
    00310
    00311       if (this->selected_index != item) {
    00312         this->selected_index = item;
    00313         this->SetDirty();
    00314       }
    00315     }
    00316   }
    00317 };
    00318
    00333 void ShowDropDownListAt(Window *w, const DropDownList *list, int selected, int button, Rect wi_rect, Colours wi_colour, bool auto_width, bool instant_close)
    00334 {
    00335   DeleteWindowById(WC_DROPDOWN_MENU, 0);
    00336
    00337   /* The preferred position is just below the dropdown calling widget */
    00338   int top = w->top + wi_rect.bottom + 1;
    00339
    00340   /* The preferred width equals the calling widget */
    00341   uint width = wi_rect.right - wi_rect.left + 1;
    00342
    00343   uint max_item_width = 0;
    00344
    00345   if (auto_width) {
    00346     /* Find the longest item in the list */
    00347     for (const DropDownListItem * const *it = list->Begin(); it != list->End(); ++it) {
    00348       const DropDownListItem *item = *it;
    00349       max_item_width = max(max_item_width, item->Width() + 5);
    00350     }
    00351   }
    00352
    00353   /* Total length of list */
    00354   int list_height = 0;
    00355
    00356   for (const DropDownListItem * const *it = list->Begin(); it != list->End(); ++it) {
    00357     const DropDownListItem *item = *it;
    00358     list_height += item->Height(width);
    00359   }
    00360
    00361   /* Height of window visible */
    00362   int height = list_height;
    00363
    00364   /* Check if the status bar is visible, as we don't want to draw over it */
    00365   int screen_bottom = GetMainViewBottom();
    00366   bool scroll = false;
    00367
    00368   /* Check if the dropdown will fully fit below the widget */
    00369   if (top + height + 4 >= screen_bottom) {
    00370     /* If not, check if it will fit above the widget */
    00371     if (w->top + wi_rect.top - height > GetMainViewTop()) {
    00372       top = w->top + wi_rect.top - height - 4;
    00373     } else {
    00374       /* ... and lastly if it won't, enable the scroll bar and fit the
    00375        * list in below the widget */
    00376       int avg_height = list_height / (int)list->Length();
    00377       int rows = (screen_bottom - 4 - top) / avg_height;
    00378       height = rows * avg_height;
    00379       scroll = true;
    00380       /* Add space for the scroll bar if we automatically determined
    00381        * the width of the list. */
    00382       max_item_width += NWidgetScrollbar::GetVerticalDimension().width;
    00383     }
    00384   }
    00385
    00386   if (auto_width) width = max(width, max_item_width);
    00387
    00388   Point dw_pos = { w->left + (_current_text_dir == TD_RTL ? wi_rect.right + 1 - width : wi_rect.left), top};
    00389   Dimension dw_size = {width, height};
    00390   new DropdownWindow(w, list, selected, button, instant_close, dw_pos, dw_size, wi_colour, scroll);
    00391 }
    00392
    00406 void ShowDropDownList(Window *w, const DropDownList *list, int selected, int button, uint width, bool auto_width, bool instant_close)
    00407 {
    00408   /* Our parent's button widget is used to determine where to place the drop
    00409    * down list window. */
    00410   Rect wi_rect;
    00411   NWidgetCore *nwi = w->GetWidget<NWidgetCore>(button);
    00412   wi_rect.left   = nwi->pos_x;
    00413   wi_rect.right  = nwi->pos_x + nwi->current_x - 1;
    00414   wi_rect.top    = nwi->pos_y;
    00415   wi_rect.bottom = nwi->pos_y + nwi->current_y - 1;
    00416   Colours wi_colour = nwi->colour;
    00417
    00418   if ((nwi->type & WWT_MASK) == NWID_BUTTON_DROPDOWN) {
    00419     nwi->disp_flags |= ND_DROPDOWN_ACTIVE;
    00420   } else {
    00421     w->LowerWidget(button);
    00422   }
    00423   w->SetWidgetDirty(button);
    00424
    00425   if (width != 0) {
    00426     if (_current_text_dir == TD_RTL) {
    00427       wi_rect.left = wi_rect.right + 1 - width;
    00428     } else {
    00429       wi_rect.right = wi_rect.left + width - 1;
    00430     }
    00431   }
    00432
    00433   ShowDropDownListAt(w, list, selected, button, wi_rect, wi_colour, auto_width, instant_close);
    00434 }
    00435
    00447 void ShowDropDownMenu(Window *w, const StringID *strings, int selected, int button, uint32 disabled_mask, uint32 hidden_mask, uint width)
    00448 {
    00449   DropDownList *list = new DropDownList();
    00450
    00451   for (uint i = 0; strings[i] != INVALID_STRING_ID; i++) {
    00452     if (!HasBit(hidden_mask, i)) {
    00453       *list->Append() = new DropDownListStringItem(strings[i], i, HasBit(disabled_mask, i));
    00454     }
    00455   }
    00456
    00457   /* No entries in the list? */
    00458   if (list->Length() == 0) {
    00459     delete list;
    00460     return;
    00461   }
    00462
    00463   ShowDropDownList(w, list, selected, button, width);
    00464 }
    00465
    00471 int HideDropDownMenu(Window *pw)
    00472 {
    00473   Window *w;
    00474   FOR_ALL_WINDOWS_FROM_BACK(w) {
    00475     if (w->window_class != WC_DROPDOWN_MENU) continue;
    00476
    00477     DropdownWindow *dw = dynamic_cast<DropdownWindow*>(w);
    00478     assert(dw != NULL);
    00479     if (pw->window_class == dw->parent_wnd_class &&
    00480         pw->window_number == dw->parent_wnd_num) {
    00481       int parent_button = dw->parent_button;
    00482       delete dw;
    00483       return parent_button;
    00484     }
    00485   }
    00486
    00487   return -1;
    00488 }
    00489