src/widgets/dropdown.cpp
branchNewGRF_ports
changeset 6872 1c4a4a609f85
child 6878 7d1ff2f621c7
equal deleted inserted replaced
6871:5a9dc001e1ad 6872:1c4a4a609f85
       
     1 /* $Id$ */
       
     2 
       
     3 #include "../stdafx.h"
       
     4 #include "../openttd.h"
       
     5 #include "../strings_type.h"
       
     6 #include "../window_gui.h"
       
     7 #include "../strings_func.h"
       
     8 #include "../strings_type.h"
       
     9 #include "../gfx_func.h"
       
    10 #include "../window_func.h"
       
    11 #include "../core/math_func.hpp"
       
    12 #include "dropdown_type.h"
       
    13 #include "dropdown_func.h"
       
    14 
       
    15 #include "../table/sprites.h"
       
    16 #include "table/strings.h"
       
    17 
       
    18 StringID DropDownListItem::String() const
       
    19 {
       
    20 	return STR_NULL;
       
    21 }
       
    22 
       
    23 StringID DropDownListStringItem::String() const
       
    24 {
       
    25 	return this->string;
       
    26 }
       
    27 
       
    28 StringID DropDownListParamStringItem::String() const
       
    29 {
       
    30 	for (uint i = 0; i < lengthof(this->decode_params); i++) SetDParam(i, this->decode_params[i]);
       
    31 	return this->string;
       
    32 }
       
    33 
       
    34 /**
       
    35  * Delete all items of a drop down list and the list itself
       
    36  * @param list List to delete.
       
    37  */
       
    38 static void DeleteDropDownList(DropDownList *list)
       
    39 {
       
    40 	for (DropDownList::iterator it = list->begin(); it != list->end(); ++it) {
       
    41 		DropDownListItem *item = *it;
       
    42 		delete item;
       
    43 	}
       
    44 	delete list;
       
    45 }
       
    46 
       
    47 struct dropdown_d {
       
    48 	WindowClass parent_wnd_class;
       
    49 	WindowNumber parent_wnd_num;
       
    50 	byte parent_button;
       
    51 	DropDownList *list;
       
    52 	byte selected_index;
       
    53 	byte click_delay;
       
    54 	bool drag_mode;
       
    55 	int scrolling;
       
    56 };
       
    57 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(dropdown_d));
       
    58 
       
    59 static const Widget _dropdown_menu_widgets[] = {
       
    60 {      WWT_PANEL,   RESIZE_NONE,     0,     0, 0,     0, 0, 0x0, STR_NULL},
       
    61 {  WWT_SCROLLBAR,   RESIZE_NONE,     0,     0, 0,     0, 0, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST},
       
    62 {   WIDGETS_END},
       
    63 };
       
    64 
       
    65 static int GetDropDownItem(const Window *w)
       
    66 {
       
    67 	if (GetWidgetFromPos(w, _cursor.pos.x - w->left, _cursor.pos.y - w->top) < 0) return -1;
       
    68 
       
    69 	int y = _cursor.pos.y - w->top - 2 + w->vscroll.pos * 10;
       
    70 	if (y < 0) return -1;
       
    71 
       
    72 	uint selected_row = y / 10;
       
    73 	const DropDownList *list = WP(w, dropdown_d).list;
       
    74 
       
    75 	if (selected_row >= list->size()) return -1;
       
    76 
       
    77 	for (DropDownList::const_iterator it = list->begin(); it != list->end(); ++it, selected_row--) {
       
    78 		if (selected_row == 0) {
       
    79 			const DropDownListItem *item = *it;
       
    80 			if (item->masked || item->String() == STR_NULL) return -1;
       
    81 			return item->result;
       
    82 		}
       
    83 	}
       
    84 
       
    85 	return -1;
       
    86 }
       
    87 
       
    88 static void DropDownMenuWndProc(Window *w, WindowEvent *e)
       
    89 {
       
    90 	switch (e->event) {
       
    91 		case WE_PAINT: {
       
    92 			DrawWindowWidgets(w);
       
    93 
       
    94 			int x = 1;
       
    95 			int y = 2 - w->vscroll.pos * 10;
       
    96 
       
    97 			int sel    = WP(w, dropdown_d).selected_index;
       
    98 			int width  = w->widget[0].right - 3;
       
    99 			int height = w->widget[0].bottom - 3;
       
   100 
       
   101 			DropDownList *list = WP(w, dropdown_d).list;
       
   102 
       
   103 			for (DropDownList::const_iterator it = list->begin(); it != list->end(); ++it) {
       
   104 				if (y >= 0 && y <= height) {
       
   105 					const DropDownListItem *item = *it;
       
   106 					if (item->String() != STR_NULL) {
       
   107 						if (sel == item->result) GfxFillRect(x + 1, y, x + width, y + 9, 0);
       
   108 
       
   109 						DrawStringTruncated(x + 2, y, item->String(), sel == item->result ? TC_WHITE : TC_BLACK, x + width);
       
   110 
       
   111 						if (item->masked) {
       
   112 							GfxFillRect(x, y, x + width, y + 9,
       
   113 								(1 << PALETTE_MODIFIER_GREYOUT) | _colour_gradient[w->widget[0].color][5]
       
   114 							);
       
   115 						}
       
   116 					} else {
       
   117 						int c1 = _colour_gradient[w->widget[0].color][3];
       
   118 						int c2 = _colour_gradient[w->widget[0].color][7];
       
   119 
       
   120 						GfxFillRect(x + 1, y + 3, x + w->width - 5, y + 3, c1);
       
   121 						GfxFillRect(x + 1, y + 4, x + w->width - 5, y + 4, c2);
       
   122 					}
       
   123 				}
       
   124 				y += 10;
       
   125 			}
       
   126 		} break;
       
   127 
       
   128 		case WE_CLICK: {
       
   129 			if (e->we.click.widget != 0) break;
       
   130 			int item = GetDropDownItem(w);
       
   131 			if (item >= 0) {
       
   132 				WP(w, dropdown_d).click_delay = 4;
       
   133 				WP(w, dropdown_d).selected_index = item;
       
   134 				SetWindowDirty(w);
       
   135 			}
       
   136 		} break;
       
   137 
       
   138 		case WE_TICK:
       
   139 			if (WP(w, dropdown_d).scrolling == -1) {
       
   140 				w->vscroll.pos = max(0, w->vscroll.pos - 1);
       
   141 				SetWindowDirty(w);
       
   142 			} else if (WP(w, dropdown_d).scrolling == 1) {
       
   143 				w->vscroll.pos = min(w->vscroll.count - w->vscroll.cap, w->vscroll.pos + 1);
       
   144 				SetWindowDirty(w);
       
   145 			}
       
   146 			WP(w, dropdown_d).scrolling = 0;
       
   147 			break;
       
   148 
       
   149 		case WE_MOUSELOOP: {
       
   150 			Window *w2 = FindWindowById(WP(w, dropdown_d).parent_wnd_class, WP(w,dropdown_d).parent_wnd_num);
       
   151 			if (w2 == NULL) {
       
   152 				DeleteWindow(w);
       
   153 				return;
       
   154 			}
       
   155 
       
   156 			if (WP(w, dropdown_d).click_delay != 0 && --WP(w,dropdown_d).click_delay == 0) {
       
   157 				WindowEvent e;
       
   158 				e.event = WE_DROPDOWN_SELECT;
       
   159 				e.we.dropdown.button = WP(w, dropdown_d).parent_button;
       
   160 				e.we.dropdown.index  = WP(w, dropdown_d).selected_index;
       
   161 				w2->wndproc(w2, &e);
       
   162 				DeleteWindow(w);
       
   163 				return;
       
   164 			}
       
   165 
       
   166 			if (WP(w, dropdown_d).drag_mode) {
       
   167 				int item = GetDropDownItem(w);
       
   168 
       
   169 				if (!_left_button_clicked) {
       
   170 					WP(w, dropdown_d).drag_mode = false;
       
   171 					if (item < 0) return;
       
   172 					WP(w, dropdown_d).click_delay = 2;
       
   173 				} else {
       
   174 					if (_cursor.pos.y <= w->top + 2) {
       
   175 						/* Cursor is above the list, set scroll up */
       
   176 						WP(w, dropdown_d).scrolling = -1;
       
   177 						return;
       
   178 					} else if (_cursor.pos.y >= w->top + w->height - 2) {
       
   179 						/* Cursor is below list, set scroll down */
       
   180 						WP(w, dropdown_d).scrolling = 1;
       
   181 						return;
       
   182 					}
       
   183 
       
   184 					if (item < 0) return;
       
   185 				}
       
   186 
       
   187 				WP(w, dropdown_d).selected_index = item;
       
   188 				SetWindowDirty(w);
       
   189 			}
       
   190 		} break;
       
   191 
       
   192 		case WE_DESTROY: {
       
   193 			Window *w2 = FindWindowById(WP(w, dropdown_d).parent_wnd_class, WP(w,dropdown_d).parent_wnd_num);
       
   194 			if (w2 != NULL) {
       
   195 				w2->RaiseWidget(WP(w, dropdown_d).parent_button);
       
   196 				w2->InvalidateWidget(WP(w, dropdown_d).parent_button);
       
   197 			}
       
   198 
       
   199 			DeleteDropDownList(WP(w, dropdown_d).list);
       
   200 		} break;
       
   201 	}
       
   202 }
       
   203 
       
   204 void ShowDropDownList(Window *w, DropDownList *list, int selected, int button)
       
   205 {
       
   206 	bool is_dropdown_menu_shown = w->IsWidgetLowered(button);
       
   207 
       
   208 	DeleteWindowById(WC_DROPDOWN_MENU, 0);
       
   209 
       
   210 	if (is_dropdown_menu_shown) {
       
   211 		DeleteDropDownList(list);
       
   212 		return;
       
   213 	}
       
   214 
       
   215 	w->LowerWidget(button);
       
   216 	w->InvalidateWidget(button);
       
   217 
       
   218 	/* Our parent's button widget is used to determine where to place the drop
       
   219 	 * down list window. */
       
   220 	const Widget *wi = &w->widget[button];
       
   221 
       
   222 	/* The preferred position is just below the dropdown calling widget */
       
   223 	int top = w->top + wi->bottom + 1;
       
   224 	int height = list->size() * 10 + 4;
       
   225 
       
   226 	/* Check if the status bar is visible, as we don't want to draw over it */
       
   227 	Window *w3 = FindWindowById(WC_STATUS_BAR, 0);
       
   228 	int screen_bottom = w3 == NULL ? _screen.height : w3->top;
       
   229 
       
   230 	bool scroll = false;
       
   231 
       
   232 	/* Check if the dropdown will fully fit below the widget */
       
   233 	if (top + height >= screen_bottom) {
       
   234 		w3 = FindWindowById(WC_MAIN_TOOLBAR, 0);
       
   235 		int screen_top = w3 == NULL ? 0 : w3->top + w3->height;
       
   236 
       
   237 		/* If not, check if it will fit above the widget */
       
   238 		if (w->top + wi->top - height > screen_top) {
       
   239 			top = w->top + wi->top - height;
       
   240 		} else {
       
   241 			/* ... and lastly if it won't, enable the scroll bar and fit the
       
   242 			 * list in below the widget */
       
   243 			int rows = (screen_bottom - 4 - top) / 10;
       
   244 			height = rows * 10 + 4;
       
   245 			scroll = true;
       
   246 		}
       
   247 	}
       
   248 
       
   249 	Window *dw = AllocateWindow(
       
   250 		w->left + wi->left,
       
   251 		top,
       
   252 		wi->right - wi->left + 1,
       
   253 		height,
       
   254 		DropDownMenuWndProc,
       
   255 		WC_DROPDOWN_MENU,
       
   256 		_dropdown_menu_widgets);
       
   257 
       
   258 	dw->widget[0].color = wi->color;
       
   259 	dw->widget[0].right = wi->right - wi->left;
       
   260 	dw->widget[0].bottom = height - 1;
       
   261 
       
   262 	dw->SetWidgetHiddenState(1, !scroll);
       
   263 
       
   264 	if (scroll) {
       
   265 		/* We're scrolling, so enable the scroll bar and shrink the list by
       
   266 		 * the scrollbar's width */
       
   267 		dw->widget[1].color  = wi->color;
       
   268 		dw->widget[1].right  = dw->widget[0].right;
       
   269 		dw->widget[1].left   = dw->widget[1].right - 11;
       
   270 		dw->widget[1].bottom = height - 1;
       
   271 		dw->widget[0].right -= 12;
       
   272 
       
   273 		dw->vscroll.cap   = (height - 4) / 10;
       
   274 		dw->vscroll.count = list->size();
       
   275 	}
       
   276 
       
   277 	dw->desc_flags = WDF_DEF_WIDGET;
       
   278 	dw->flags4 &= ~WF_WHITE_BORDER_MASK;
       
   279 
       
   280 	WP(dw, dropdown_d).parent_wnd_class = w->window_class;
       
   281 	WP(dw, dropdown_d).parent_wnd_num   = w->window_number;
       
   282 	WP(dw, dropdown_d).parent_button    = button;
       
   283 	WP(dw, dropdown_d).list             = list;
       
   284 	WP(dw, dropdown_d).selected_index   = selected;
       
   285 	WP(dw, dropdown_d).click_delay      = 0;
       
   286 	WP(dw, dropdown_d).drag_mode        = true;
       
   287 }
       
   288 
       
   289 void ShowDropDownMenu(Window *w, const StringID *strings, int selected, int button, uint32 disabled_mask, uint32 hidden_mask)
       
   290 {
       
   291 	/* Don't create a new list if we're just closing an existing menu */
       
   292 	if (w->IsWidgetLowered(button)) {
       
   293 		DeleteWindowById(WC_DROPDOWN_MENU, 0);
       
   294 		return;
       
   295 	}
       
   296 
       
   297 	uint result = 0;
       
   298 	DropDownList *list = new DropDownList();
       
   299 
       
   300 	for (uint i = 0; strings[i] != INVALID_STRING_ID; i++) {
       
   301 		if (!HasBit(hidden_mask, i)) {
       
   302 			list->push_back(new DropDownListStringItem(strings[i], result, HasBit(disabled_mask, i)));
       
   303 		}
       
   304 		result++;
       
   305 	}
       
   306 
       
   307 	/* No entries in the list? */
       
   308 	if (list->size() == 0) {
       
   309 		DeleteDropDownList(list);
       
   310 		return;
       
   311 	}
       
   312 
       
   313 	ShowDropDownList(w, list, selected, button);
       
   314 }
       
   315 
       
   316 void HideDropDownMenu(Window *pw)
       
   317 {
       
   318 	Window **wz;
       
   319 	FOR_ALL_WINDOWS(wz) {
       
   320 		if ((*wz)->window_class != WC_DROPDOWN_MENU) continue;
       
   321 
       
   322 		if (pw->window_class == WP(*wz, dropdown_d).parent_wnd_class &&
       
   323 				pw->window_number == WP(*wz, dropdown_d).parent_wnd_num) {
       
   324 			DeleteWindow(*wz);
       
   325 			break;
       
   326 		}
       
   327 	}
       
   328 }
       
   329