00001
00002
00003
00004
00005
00006
00007
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
00020 void DropDownListItem::Draw(int left, int right, int top, int bottom, bool sel, int bg_colour) const
00021 {
00022 int c1 = _colour_gradient[bg_colour][3];
00023 int c2 = _colour_gradient[bg_colour][7];
00024
00025 int mid = top + this->Height(0) / 2;
00026 GfxFillRect(left + 1, mid - 2, right - 1, mid - 2, c1);
00027 GfxFillRect(left + 1, mid - 1, right - 1, mid - 1, c2);
00028 }
00029
00030 uint DropDownListStringItem::Width() const
00031 {
00032 char buffer[512];
00033 GetString(buffer, this->String(), lastof(buffer));
00034 return GetStringBoundingBox(buffer).width;
00035 }
00036
00037 void DropDownListStringItem::Draw(int left, int right, int top, int bottom, bool sel, int bg_colour) const
00038 {
00039 DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, this->String(), sel ? TC_WHITE : TC_BLACK);
00040 }
00041
00042 StringID DropDownListParamStringItem::String() const
00043 {
00044 for (uint i = 0; i < lengthof(this->decode_params); i++) SetDParam(i, this->decode_params[i]);
00045 return this->string;
00046 }
00047
00048 uint DropDownListCharStringItem::Width() const
00049 {
00050 return GetStringBoundingBox(this->string).width;
00051 }
00052
00053 void DropDownListCharStringItem::Draw(int left, int right, int top, int bottom, bool sel, int bg_colour) const
00054 {
00055 DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, this->string, sel ? TC_WHITE : TC_BLACK);
00056 }
00057
00062 static void DeleteDropDownList(DropDownList *list)
00063 {
00064 for (DropDownList::iterator it = list->begin(); it != list->end(); ++it) {
00065 DropDownListItem *item = *it;
00066 delete item;
00067 }
00068 delete list;
00069 }
00070
00072 enum DropdownMenuWidgets {
00073 DDM_ITEMS,
00074 DDM_SHOW_SCROLL,
00075 DDM_SCROLL,
00076 };
00077
00078 static const NWidgetPart _nested_dropdown_menu_widgets[] = {
00079 NWidget(NWID_HORIZONTAL),
00080 NWidget(WWT_PANEL, COLOUR_END, DDM_ITEMS), SetMinimalSize(1, 1), SetScrollbar(DDM_SCROLL), EndContainer(),
00081 NWidget(NWID_SELECTION, INVALID_COLOUR, DDM_SHOW_SCROLL),
00082 NWidget(NWID_VSCROLLBAR, COLOUR_END, DDM_SCROLL),
00083 EndContainer(),
00084 EndContainer(),
00085 };
00086
00087 const WindowDesc _dropdown_desc(
00088 WDP_MANUAL, 0, 0,
00089 WC_DROPDOWN_MENU, WC_NONE,
00090 0,
00091 _nested_dropdown_menu_widgets, lengthof(_nested_dropdown_menu_widgets)
00092 );
00093
00095 struct DropdownWindow : Window {
00096 WindowClass parent_wnd_class;
00097 WindowNumber parent_wnd_num;
00098 byte parent_button;
00099 DropDownList *list;
00100 int selected_index;
00101 byte click_delay;
00102 bool drag_mode;
00103 bool instant_close;
00104 int scrolling;
00105 Point position;
00106 Scrollbar *vscroll;
00107
00121 DropdownWindow(Window *parent, DropDownList *list, int selected, int button, bool instant_close, const Point &position, const Dimension &size, Colours wi_colour, bool scroll) : Window()
00122 {
00123 this->position = position;
00124
00125 this->CreateNestedTree(&_dropdown_desc);
00126
00127 this->vscroll = this->GetScrollbar(DDM_SCROLL);
00128
00129 uint items_width = size.width - (scroll ? WD_VSCROLLBAR_WIDTH : 0);
00130 NWidgetCore *nwi = this->GetWidget<NWidgetCore>(DDM_ITEMS);
00131 nwi->SetMinimalSize(items_width, size.height + 4);
00132 nwi->colour = wi_colour;
00133
00134 nwi = this->GetWidget<NWidgetCore>(DDM_SCROLL);
00135 nwi->colour = wi_colour;
00136
00137 this->GetWidget<NWidgetStacked>(DDM_SHOW_SCROLL)->SetDisplayedPlane(scroll ? 0 : SZSP_NONE);
00138
00139 this->FinishInitNested(&_dropdown_desc, 0);
00140 this->flags4 &= ~WF_WHITE_BORDER_MASK;
00141
00142
00143 int list_height = 0;
00144 for (DropDownList::const_iterator it = list->begin(); it != list->end(); ++it) {
00145 DropDownListItem *item = *it;
00146 list_height += item->Height(items_width);
00147 }
00148
00149
00150 this->vscroll->SetCapacity(size.height * (uint16)list->size() / list_height);
00151 this->vscroll->SetCount((uint16)list->size());
00152
00153 this->parent_wnd_class = parent->window_class;
00154 this->parent_wnd_num = parent->window_number;
00155 this->parent_button = button;
00156 this->list = list;
00157 this->selected_index = selected;
00158 this->click_delay = 0;
00159 this->drag_mode = true;
00160 this->instant_close = instant_close;
00161 }
00162
00163 ~DropdownWindow()
00164 {
00165 Window *w2 = FindWindowById(this->parent_wnd_class, this->parent_wnd_num);
00166 if (w2 != NULL) {
00167 if (w2->nested_array != NULL) {
00168 NWidgetCore *nwi2 = w2->GetWidget<NWidgetCore>(this->parent_button);
00169 if (nwi2->type == NWID_BUTTON_DROPDOWN) {
00170 nwi2->disp_flags &= ~ND_DROPDOWN_ACTIVE;
00171 } else {
00172 w2->RaiseWidget(this->parent_button);
00173 }
00174 } else {
00175 w2->RaiseWidget(this->parent_button);
00176 }
00177 w2->SetWidgetDirty(this->parent_button);
00178 }
00179
00180 DeleteDropDownList(this->list);
00181 }
00182
00183 virtual Point OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
00184 {
00185 return this->position;
00186 }
00187
00193 bool GetDropDownItem(int &value)
00194 {
00195 if (GetWidgetFromPos(this, _cursor.pos.x - this->left, _cursor.pos.y - this->top) < 0) return false;
00196
00197 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(DDM_ITEMS);
00198 int y = _cursor.pos.y - this->top - nwi->pos_y - 2;
00199 int width = nwi->current_x - 4;
00200 int pos = this->vscroll->GetPosition();
00201
00202 const DropDownList *list = this->list;
00203
00204 for (DropDownList::const_iterator it = list->begin(); it != list->end(); ++it) {
00205
00206 if (--pos >= 0) continue;
00207
00208 const DropDownListItem *item = *it;
00209 int item_height = item->Height(width);
00210
00211 if (y < item_height) {
00212 if (item->masked || !item->Selectable()) return false;
00213 value = item->result;
00214 return true;
00215 }
00216
00217 y -= item_height;
00218 }
00219
00220 return false;
00221 }
00222
00223 virtual void DrawWidget(const Rect &r, int widget) const
00224 {
00225 if (widget != DDM_ITEMS) return;
00226
00227 TextColour colour = (TextColour)this->GetWidget<NWidgetCore>(widget)->colour;
00228
00229 int y = r.top + 2;
00230 int pos = this->vscroll->GetPosition();
00231 for (DropDownList::const_iterator it = this->list->begin(); it != this->list->end(); ++it) {
00232 const DropDownListItem *item = *it;
00233 int item_height = item->Height(r.right - r.left + 1);
00234
00235
00236 if (--pos >= 0) continue;
00237
00238 if (y + item_height < r.bottom) {
00239 bool selected = (this->selected_index == item->result);
00240 if (selected) GfxFillRect(r.left + 2, y, r.right - 1, y + item_height - 1, 0);
00241
00242 item->Draw(r.left, r.right, y, y + item_height, selected, colour);
00243
00244 if (item->masked) {
00245 GfxFillRect(r.left + 1, y, r.right - 1, y + item_height - 1, _colour_gradient[colour][5], FILLRECT_CHECKER);
00246 }
00247 }
00248 y += item_height;
00249 }
00250 }
00251
00252 virtual void OnClick(Point pt, int widget, int click_count)
00253 {
00254 if (widget != DDM_ITEMS) return;
00255 int item;
00256 if (this->GetDropDownItem(item)) {
00257 this->click_delay = 4;
00258 this->selected_index = item;
00259 this->SetDirty();
00260 }
00261 }
00262
00263 virtual void OnTick()
00264 {
00265 if (this->scrolling != 0) {
00266 int pos = this->vscroll->GetPosition();
00267
00268 this->vscroll->UpdatePosition(this->scrolling);
00269 this->scrolling = 0;
00270
00271 if (pos != this->vscroll->GetPosition()) {
00272 this->SetDirty();
00273 }
00274 }
00275 }
00276
00277 virtual void OnMouseLoop()
00278 {
00279 Window *w2 = FindWindowById(this->parent_wnd_class, this->parent_wnd_num);
00280 if (w2 == NULL) {
00281 delete this;
00282 return;
00283 }
00284
00285 if (this->click_delay != 0 && --this->click_delay == 0) {
00286
00287
00288 this->window_class = WC_INVALID;
00289 this->SetDirty();
00290
00291 w2->OnDropdownSelect(this->parent_button, this->selected_index);
00292 delete this;
00293 return;
00294 }
00295
00296 if (this->drag_mode) {
00297 int item;
00298
00299 if (!_left_button_clicked) {
00300 this->drag_mode = false;
00301 if (!this->GetDropDownItem(item)) {
00302 if (this->instant_close) {
00303
00304
00305 this->window_class = WC_INVALID;
00306 this->SetDirty();
00307
00308 if (GetWidgetFromPos(w2, _cursor.pos.x - w2->left, _cursor.pos.y - w2->top) == this->parent_button) {
00309
00310
00311 w2->OnDropdownSelect(this->parent_button, this->selected_index);
00312 }
00313 delete this;
00314 }
00315 return;
00316 }
00317 this->click_delay = 2;
00318 } else {
00319 if (_cursor.pos.y <= this->top + 2) {
00320
00321 this->scrolling = -1;
00322 return;
00323 } else if (_cursor.pos.y >= this->top + this->height - 2) {
00324
00325 this->scrolling = 1;
00326 return;
00327 }
00328
00329 if (!this->GetDropDownItem(item)) return;
00330 }
00331
00332 if (this->selected_index != item) {
00333 this->selected_index = item;
00334 this->SetDirty();
00335 }
00336 }
00337 }
00338 };
00339
00340 void ShowDropDownList(Window *w, DropDownList *list, int selected, int button, uint width, bool auto_width, bool instant_close)
00341 {
00342 DeleteWindowById(WC_DROPDOWN_MENU, 0);
00343
00344
00345
00346 Rect wi_rect;
00347 Colours wi_colour;
00348 NWidgetCore *nwi = w->GetWidget<NWidgetCore>(button);
00349 wi_rect.left = nwi->pos_x;
00350 wi_rect.right = nwi->pos_x + nwi->current_x - 1;
00351 wi_rect.top = nwi->pos_y;
00352 wi_rect.bottom = nwi->pos_y + nwi->current_y - 1;
00353 wi_colour = nwi->colour;
00354
00355 if (nwi->type == NWID_BUTTON_DROPDOWN) {
00356 nwi->disp_flags |= ND_DROPDOWN_ACTIVE;
00357 } else {
00358 w->LowerWidget(button);
00359 }
00360 w->SetWidgetDirty(button);
00361
00362
00363 int top = w->top + wi_rect.bottom + 1;
00364
00365 if (width == 0) width = wi_rect.right - wi_rect.left + 1;
00366
00367 uint max_item_width = 0;
00368
00369 if (auto_width) {
00370
00371 for (DropDownList::const_iterator it = list->begin(); it != list->end(); ++it) {
00372 const DropDownListItem *item = *it;
00373 max_item_width = max(max_item_width, item->Width() + 5);
00374 }
00375 }
00376
00377
00378 int list_height = 0;
00379
00380 for (DropDownList::const_iterator it = list->begin(); it != list->end(); ++it) {
00381 DropDownListItem *item = *it;
00382 list_height += item->Height(width);
00383 }
00384
00385
00386 int height = list_height;
00387
00388
00389 int screen_bottom = GetMainViewBottom();
00390 bool scroll = false;
00391
00392
00393 if (top + height + 4 >= screen_bottom) {
00394
00395 if (w->top + wi_rect.top - height > GetMainViewTop()) {
00396 top = w->top + wi_rect.top - height - 4;
00397 } else {
00398
00399
00400 int avg_height = list_height / (int)list->size();
00401 int rows = (screen_bottom - 4 - top) / avg_height;
00402 height = rows * avg_height;
00403 scroll = true;
00404
00405
00406 max_item_width += WD_VSCROLLBAR_WIDTH;
00407 }
00408 }
00409
00410 if (auto_width) width = max(width, max_item_width);
00411
00412 Point dw_pos = { w->left + (_current_text_dir == TD_RTL ? wi_rect.right + 1 - width : wi_rect.left), top};
00413 Dimension dw_size = {width, height};
00414 new DropdownWindow(w, list, selected, button, instant_close, dw_pos, dw_size, wi_colour, scroll);
00415 }
00416
00428 void ShowDropDownMenu(Window *w, const StringID *strings, int selected, int button, uint32 disabled_mask, uint32 hidden_mask, uint width)
00429 {
00430 DropDownList *list = new DropDownList();
00431
00432 for (uint i = 0; strings[i] != INVALID_STRING_ID; i++) {
00433 if (!HasBit(hidden_mask, i)) {
00434 list->push_back(new DropDownListStringItem(strings[i], i, HasBit(disabled_mask, i)));
00435 }
00436 }
00437
00438
00439 if (list->size() == 0) {
00440 DeleteDropDownList(list);
00441 return;
00442 }
00443
00444 ShowDropDownList(w, list, selected, button, width);
00445 }
00446
00452 int HideDropDownMenu(Window *pw)
00453 {
00454 Window *w;
00455 FOR_ALL_WINDOWS_FROM_BACK(w) {
00456 if (w->window_class != WC_DROPDOWN_MENU) continue;
00457
00458 DropdownWindow *dw = dynamic_cast<DropdownWindow*>(w);
00459 if (pw->window_class == dw->parent_wnd_class &&
00460 pw->window_number == dw->parent_wnd_num) {
00461 int parent_button = dw->parent_button;
00462 delete dw;
00463 return parent_button;
00464 }
00465 }
00466
00467 return -1;
00468 }
00469