dropdown.cpp

Go to the documentation of this file.
00001 /* $Id: dropdown.cpp 26652 2014-06-17 19:08:07Z frosch $ */
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     assert(list->Length() > 0);
00118 
00119     this->position = position;
00120 
00121     this->CreateNestedTree();
00122 
00123     this->vscroll = this->GetScrollbar(WID_DM_SCROLL);
00124 
00125     uint items_width = size.width - (scroll ? NWidgetScrollbar::GetVerticalDimension().width : 0);
00126     NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_DM_ITEMS);
00127     nwi->SetMinimalSize(items_width, size.height + 4);
00128     nwi->colour = wi_colour;
00129 
00130     nwi = this->GetWidget<NWidgetCore>(WID_DM_SCROLL);
00131     nwi->colour = wi_colour;
00132 
00133     this->GetWidget<NWidgetStacked>(WID_DM_SHOW_SCROLL)->SetDisplayedPlane(scroll ? 0 : SZSP_NONE);
00134 
00135     this->FinishInitNested(0);
00136     CLRBITS(this->flags, WF_WHITE_BORDER);
00137 
00138     /* Total length of list */
00139     int list_height = 0;
00140     for (const DropDownListItem * const *it = list->Begin(); it != list->End(); ++it) {
00141       const DropDownListItem *item = *it;
00142       list_height += item->Height(items_width);
00143     }
00144 
00145     /* Capacity is the average number of items visible */
00146     this->vscroll->SetCapacity(size.height * (uint16)list->Length() / list_height);
00147     this->vscroll->SetCount((uint16)list->Length());
00148 
00149     this->parent_wnd_class = parent->window_class;
00150     this->parent_wnd_num   = parent->window_number;
00151     this->parent_button    = button;
00152     this->list             = list;
00153     this->selected_index   = selected;
00154     this->click_delay      = 0;
00155     this->drag_mode        = true;
00156     this->instant_close    = instant_close;
00157   }
00158 
00159   ~DropdownWindow()
00160   {
00161     /* Make the dropdown "invisible", so it doesn't affect new window placement.
00162      * Also mark it dirty in case the callback deals with the screen. (e.g. screenshots). */
00163     this->window_class = WC_INVALID;
00164     this->SetDirty();
00165 
00166     Window *w2 = FindWindowById(this->parent_wnd_class, this->parent_wnd_num);
00167     if (w2 != NULL) {
00168       Point pt = _cursor.pos;
00169       pt.x -= w2->left;
00170       pt.y -= w2->top;
00171       w2->OnDropdownClose(pt, this->parent_button, this->selected_index, this->instant_close);
00172     }
00173     delete this->list;
00174   }
00175 
00176   virtual Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number)
00177   {
00178     return this->position;
00179   }
00180 
00186   bool GetDropDownItem(int &value)
00187   {
00188     if (GetWidgetFromPos(this, _cursor.pos.x - this->left, _cursor.pos.y - this->top) < 0) return false;
00189 
00190     NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_DM_ITEMS);
00191     int y     = _cursor.pos.y - this->top - nwi->pos_y - 2;
00192     int width = nwi->current_x - 4;
00193     int pos   = this->vscroll->GetPosition();
00194 
00195     const DropDownList *list = this->list;
00196 
00197     for (const DropDownListItem * const *it = list->Begin(); it != list->End(); ++it) {
00198       /* Skip items that are scrolled up */
00199       if (--pos >= 0) continue;
00200 
00201       const DropDownListItem *item = *it;
00202       int item_height = item->Height(width);
00203 
00204       if (y < item_height) {
00205         if (item->masked || !item->Selectable()) return false;
00206         value = item->result;
00207         return true;
00208       }
00209 
00210       y -= item_height;
00211     }
00212 
00213     return false;
00214   }
00215 
00216   virtual void DrawWidget(const Rect &r, int widget) const
00217   {
00218     if (widget != WID_DM_ITEMS) return;
00219 
00220     Colours colour = this->GetWidget<NWidgetCore>(widget)->colour;
00221 
00222     int y = r.top + 2;
00223     int pos = this->vscroll->GetPosition();
00224     for (const DropDownListItem * const *it = this->list->Begin(); it != this->list->End(); ++it) {
00225       const DropDownListItem *item = *it;
00226       int item_height = item->Height(r.right - r.left + 1);
00227 
00228       /* Skip items that are scrolled up */
00229       if (--pos >= 0) continue;
00230 
00231       if (y + item_height < r.bottom) {
00232         bool selected = (this->selected_index == item->result);
00233         if (selected) GfxFillRect(r.left + 2, y, r.right - 1, y + item_height - 1, PC_BLACK);
00234 
00235         item->Draw(r.left, r.right, y, y + item_height, selected, colour);
00236 
00237         if (item->masked) {
00238           GfxFillRect(r.left + 1, y, r.right - 1, y + item_height - 1, _colour_gradient[colour][5], FILLRECT_CHECKER);
00239         }
00240       }
00241       y += item_height;
00242     }
00243   }
00244 
00245   virtual void OnClick(Point pt, int widget, int click_count)
00246   {
00247     if (widget != WID_DM_ITEMS) return;
00248     int item;
00249     if (this->GetDropDownItem(item)) {
00250       this->click_delay = 4;
00251       this->selected_index = item;
00252       this->SetDirty();
00253     }
00254   }
00255 
00256   virtual void OnTick()
00257   {
00258     if (this->scrolling != 0) {
00259       int pos = this->vscroll->GetPosition();
00260 
00261       this->vscroll->UpdatePosition(this->scrolling);
00262       this->scrolling = 0;
00263 
00264       if (pos != this->vscroll->GetPosition()) {
00265         this->SetDirty();
00266       }
00267     }
00268   }
00269 
00270   virtual void OnMouseLoop()
00271   {
00272     Window *w2 = FindWindowById(this->parent_wnd_class, this->parent_wnd_num);
00273     if (w2 == NULL) {
00274       delete this;
00275       return;
00276     }
00277 
00278     if (this->click_delay != 0 && --this->click_delay == 0) {
00279       /* Make the dropdown "invisible", so it doesn't affect new window placement.
00280        * Also mark it dirty in case the callback deals with the screen. (e.g. screenshots). */
00281       this->window_class = WC_INVALID;
00282       this->SetDirty();
00283 
00284       w2->OnDropdownSelect(this->parent_button, this->selected_index);
00285       delete this;
00286       return;
00287     }
00288 
00289     if (this->drag_mode) {
00290       int item;
00291 
00292       if (!_left_button_clicked) {
00293         this->drag_mode = false;
00294         if (!this->GetDropDownItem(item)) {
00295           if (this->instant_close) delete this;
00296           return;
00297         }
00298         this->click_delay = 2;
00299       } else {
00300         if (_cursor.pos.y <= this->top + 2) {
00301           /* Cursor is above the list, set scroll up */
00302           this->scrolling = -1;
00303           return;
00304         } else if (_cursor.pos.y >= this->top + this->height - 2) {
00305           /* Cursor is below list, set scroll down */
00306           this->scrolling = 1;
00307           return;
00308         }
00309 
00310         if (!this->GetDropDownItem(item)) return;
00311       }
00312 
00313       if (this->selected_index != item) {
00314         this->selected_index = item;
00315         this->SetDirty();
00316       }
00317     }
00318   }
00319 };
00320 
00335 void ShowDropDownListAt(Window *w, const DropDownList *list, int selected, int button, Rect wi_rect, Colours wi_colour, bool auto_width, bool instant_close)
00336 {
00337   DeleteWindowById(WC_DROPDOWN_MENU, 0);
00338 
00339   /* The preferred position is just below the dropdown calling widget */
00340   int top = w->top + wi_rect.bottom + 1;
00341 
00342   /* The preferred width equals the calling widget */
00343   uint width = wi_rect.right - wi_rect.left + 1;
00344 
00345   uint max_item_width = 0;
00346 
00347   if (auto_width) {
00348     /* Find the longest item in the list */
00349     for (const DropDownListItem * const *it = list->Begin(); it != list->End(); ++it) {
00350       const DropDownListItem *item = *it;
00351       max_item_width = max(max_item_width, item->Width() + 5);
00352     }
00353   }
00354 
00355   /* Total length of list */
00356   int list_height = 0;
00357 
00358   for (const DropDownListItem * const *it = list->Begin(); it != list->End(); ++it) {
00359     const DropDownListItem *item = *it;
00360     list_height += item->Height(width);
00361   }
00362 
00363   /* Height of window visible */
00364   int height = list_height;
00365 
00366   /* Check if the status bar is visible, as we don't want to draw over it */
00367   int screen_bottom = GetMainViewBottom();
00368   bool scroll = false;
00369 
00370   /* Check if the dropdown will fully fit below the widget */
00371   if (top + height + 4 >= screen_bottom) {
00372     /* If not, check if it will fit above the widget */
00373     if (w->top + wi_rect.top - height > GetMainViewTop()) {
00374       top = w->top + wi_rect.top - height - 4;
00375     } else {
00376       /* ... and lastly if it won't, enable the scroll bar and fit the
00377        * list in below the widget */
00378       int avg_height = list_height / (int)list->Length();
00379       int rows = (screen_bottom - 4 - top) / avg_height;
00380       height = rows * avg_height;
00381       scroll = true;
00382       /* Add space for the scroll bar if we automatically determined
00383        * the width of the list. */
00384       max_item_width += NWidgetScrollbar::GetVerticalDimension().width;
00385     }
00386   }
00387 
00388   if (auto_width) width = max(width, max_item_width);
00389 
00390   Point dw_pos = { w->left + (_current_text_dir == TD_RTL ? wi_rect.right + 1 - width : wi_rect.left), top};
00391   Dimension dw_size = {width, height};
00392   new DropdownWindow(w, list, selected, button, instant_close, dw_pos, dw_size, wi_colour, scroll);
00393 }
00394 
00408 void ShowDropDownList(Window *w, const DropDownList *list, int selected, int button, uint width, bool auto_width, bool instant_close)
00409 {
00410   /* Our parent's button widget is used to determine where to place the drop
00411    * down list window. */
00412   Rect wi_rect;
00413   NWidgetCore *nwi = w->GetWidget<NWidgetCore>(button);
00414   wi_rect.left   = nwi->pos_x;
00415   wi_rect.right  = nwi->pos_x + nwi->current_x - 1;
00416   wi_rect.top    = nwi->pos_y;
00417   wi_rect.bottom = nwi->pos_y + nwi->current_y - 1;
00418   Colours wi_colour = nwi->colour;
00419 
00420   if ((nwi->type & WWT_MASK) == NWID_BUTTON_DROPDOWN) {
00421     nwi->disp_flags |= ND_DROPDOWN_ACTIVE;
00422   } else {
00423     w->LowerWidget(button);
00424   }
00425   w->SetWidgetDirty(button);
00426 
00427   if (width != 0) {
00428     if (_current_text_dir == TD_RTL) {
00429       wi_rect.left = wi_rect.right + 1 - width;
00430     } else {
00431       wi_rect.right = wi_rect.left + width - 1;
00432     }
00433   }
00434 
00435   ShowDropDownListAt(w, list, selected, button, wi_rect, wi_colour, auto_width, instant_close);
00436 }
00437 
00449 void ShowDropDownMenu(Window *w, const StringID *strings, int selected, int button, uint32 disabled_mask, uint32 hidden_mask, uint width)
00450 {
00451   DropDownList *list = new DropDownList();
00452 
00453   for (uint i = 0; strings[i] != INVALID_STRING_ID; i++) {
00454     if (!HasBit(hidden_mask, i)) {
00455       *list->Append() = new DropDownListStringItem(strings[i], i, HasBit(disabled_mask, i));
00456     }
00457   }
00458 
00459   /* No entries in the list? */
00460   if (list->Length() == 0) {
00461     delete list;
00462     return;
00463   }
00464 
00465   ShowDropDownList(w, list, selected, button, width);
00466 }
00467 
00473 int HideDropDownMenu(Window *pw)
00474 {
00475   Window *w;
00476   FOR_ALL_WINDOWS_FROM_BACK(w) {
00477     if (w->window_class != WC_DROPDOWN_MENU) continue;
00478 
00479     DropdownWindow *dw = dynamic_cast<DropdownWindow*>(w);
00480     assert(dw != NULL);
00481     if (pw->window_class == dw->parent_wnd_class &&
00482         pw->window_number == dw->parent_wnd_num) {
00483       int parent_button = dw->parent_button;
00484       delete dw;
00485       return parent_button;
00486     }
00487   }
00488 
00489   return -1;
00490 }
00491