"fixed" the combobox issue
[instimg] / src / widgets.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <assert.h>
5 #include <windows.h>
6 #include "widgets.h"
7
8 struct wgt_window {
9         HWND handle;
10         struct wgt_rect rect;
11
12         HDC dc;
13         char cname[32];
14         struct wgt_widget *wlist;
15 };
16
17 struct wgt_widget {
18         HWND handle;
19         struct wgt_rect rect;
20
21         struct wgt_window *win;
22         struct wgt_widget *next;
23
24         char **itemlist;
25         int num_items;
26
27         int disabled;
28
29         wgt_callback cb_click;
30         wgt_callback cb_modify;
31 };
32
33
34 static LRESULT WINAPI event_handler(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam);
35 static struct wgt_widget *find_widget(struct wgt_window *win, HWND handle);
36
37 struct wgt_window *wgt_window(const char *title, int width, int height)
38 {
39         struct wgt_window *win;
40         int tlen;
41         WNDCLASS wc;
42         HINSTANCE hinst;
43         DWORD err;
44         RECT rect;
45
46         if(!(win = malloc(sizeof *win))) {
47                 fprintf(stderr, "wgl_create_window: failed to allocate window structure\n");
48                 return 0;
49         }
50         win->wlist = 0;
51
52         tlen = strlen(title);
53         if(tlen > sizeof win->cname - 10) {
54                 tlen = sizeof win->cname - 10;
55         }
56         strcpy(win->cname, "WGTCLASS-");
57         memcpy(win->cname + 9, title, tlen);
58         win->cname[tlen + 9] = 0;
59
60         hinst = GetModuleHandle(0);
61
62         memset(&wc, 0, sizeof wc);
63         wc.hInstance = hinst;
64         wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
65         wc.hCursor = LoadCursor(0, IDC_ARROW);
66         wc.hIcon = LoadIcon(0, IDI_APPLICATION);
67         wc.lpszClassName = win->cname;
68         wc.lpfnWndProc = event_handler;
69         wc.cbWndExtra = 4;
70         if(!RegisterClass(&wc)) {
71                 fprintf(stderr, "wgt_create_window: failed to register window class\n");
72                 return 0;
73         }
74
75         if(!(win->handle = CreateWindow(win->cname, title, WS_OVERLAPPEDWINDOW,
76                         CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, hinst, 0))) {
77                 fprintf(stderr, "wgt_create_window: failed to create window\n");
78                 UnregisterClass(title, hinst);
79                 return 0;
80         }
81         win->dc = GetDC(win->handle);
82         SetLastError(0);
83         if(!SetWindowLong(win->handle, GWL_USERDATA, (long)win) && (err = GetLastError())) {
84                 fprintf(stderr, "wgt_create_window: failed to store window user data (err: %d)\n", (int)err);
85                 UnregisterClass(title, hinst);
86                 DestroyWindow(win->handle);
87                 return 0;
88         }
89         ShowWindow(win->handle, 1);
90
91         GetWindowRect(win->handle, &rect);
92
93         win->rect.x = rect.left;
94         win->rect.y = rect.top;
95         win->rect.width = rect.right - rect.left;
96         win->rect.height = rect.bottom - rect.top;
97
98         return win;
99 }
100
101 void wgt_destroy_window(struct wgt_window *win)
102 {
103         while(win->wlist) {
104                 struct wgt_widget *w = win->wlist;
105                 win->wlist = win->wlist->next;
106                 wgt_destroy_widget(w);
107                 free(w);
108         }
109
110         ReleaseDC(win->handle, win->dc);
111         DestroyWindow(win->handle);
112         UnregisterClass(win->cname, GetModuleHandle(0));
113
114         free(win);
115 }
116
117 void wgt_destroy_widget(struct wgt_widget *w)
118 {
119         DestroyWindow(w->handle);
120         free(w->itemlist);
121 }
122
123 void wgt_enable_widget(struct wgt_widget *w)
124 {
125         w->disabled = 0;
126         EnableWindow(w->handle, 1);
127 }
128
129 void wgt_disable_widget(struct wgt_widget *w)
130 {
131         w->disabled = 1;
132         EnableWindow(w->handle, 0);
133 }
134
135 int wgt_widget_enabled(struct wgt_widget *w)
136 {
137         return !w->disabled;
138 }
139
140 struct wgt_window *wgt_widget_window(struct wgt_widget *w)
141 {
142         return w->win;
143 }
144
145 struct wgt_rect *wgt_widget_rect(struct wgt_widget *w, struct wgt_rect *rect)
146 {
147         if(rect) {
148                 *rect = w->rect;
149         }
150         return &w->rect;
151 }
152
153 int wgt_xpos_after(struct wgt_widget *w, int pad)
154 {
155         if(pad == WGT_AUTO) {
156                 pad = 8;
157         }
158         return w->rect.x + w->rect.width + pad;
159 }
160
161 int wgt_ypos_after(struct wgt_widget *w, int pad)
162 {
163         if(pad == WGT_AUTO) {
164                 pad = 8;
165         }
166         return w->rect.y + w->rect.height + pad;
167 }
168
169 int wgt_string_size(struct wgt_window *win, const char *s, struct wgt_rect *rect)
170 {
171         SIZE sz;
172
173         GetTextExtentPoint32(win->dc, (char*)s, strlen(s), &sz);
174         if(rect) {
175                 rect->x = rect->y = 0;
176                 rect->width = sz.cx;
177                 rect->height = sz.cy;
178         }
179         return sz.cx;
180 }
181
182
183 struct wgt_widget *wgt_label(struct wgt_window *win, const char *text, int x, int y)
184 {
185         struct wgt_widget *w;
186         struct wgt_rect textsz;
187
188         if(!(w = calloc(1, sizeof *w))) {
189                 fprintf(stderr, "wgt_label: failed to allocate widget structure\n");
190                 return 0;
191         }
192         w->win = win;
193
194         wgt_string_size(win, text, &textsz);
195
196         if(!(w->handle = CreateWindow("STATIC", text, WS_CHILD | WS_VISIBLE | SS_SIMPLE,
197                         x, y, textsz.width, textsz.height, win->handle, 0, GetModuleHandle(0), 0))) {
198                 fprintf(stderr, "wgt_label: failed to create window\n");
199                 free(w);
200                 return 0;
201         }
202
203         w->rect.x = x;
204         w->rect.y = y;
205         w->rect.width = textsz.width;
206         w->rect.height = textsz.height;
207
208         w->next = win->wlist;
209         win->wlist = w;
210         return w;
211 }
212
213 struct wgt_widget *wgt_button(struct wgt_window *win, const char *text, int x, int y,
214                         int width, int height, wgt_callback clickfunc)
215 {
216         struct wgt_widget *w;
217         struct wgt_rect textsz;
218
219         if(!(w = calloc(1, sizeof *w))) {
220                 fprintf(stderr, "wgt_button: failed to allocate widget structure\n");
221                 return 0;
222         }
223         w->win = win;
224
225         if(width < 0 || height < 0) {
226                 wgt_string_size(win, text, &textsz);
227                 if(width < 0) width = textsz.width * 5 / 3;
228                 if(height < 0) height = textsz.height * 10 / 7;
229         }
230
231         if(!(w->handle = CreateWindow("BUTTON", text, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
232                         x, y, width, height, win->handle, 0, GetModuleHandle(0), 0))) {
233                 fprintf(stderr, "wgt_button: failed to create window\n");
234                 free(w);
235                 return 0;
236         }
237
238         w->rect.x = x;
239         w->rect.y = y;
240         w->rect.width = width;
241         w->rect.height = height;
242
243         w->cb_click = clickfunc;
244         w->next = win->wlist;
245         win->wlist = w;
246         return w;
247 }
248
249 struct wgt_widget *wgt_checkbox(struct wgt_window *win, const char *text, int on,
250                         int x, int y, int width, int height, wgt_callback modfunc)
251 {
252         struct wgt_widget *w;
253         struct wgt_rect textsz;
254         int check_width;
255
256         if(!(w = calloc(1, sizeof *w))) {
257                 fprintf(stderr, "wgt_checkbox: failed to allocate widget structure\n");
258                 return 0;
259         }
260         w->win = win;
261
262         check_width = GetSystemMetrics(SM_CXMENUCHECK);
263
264         if(width < 0 || height < 0) {
265                 wgt_string_size(win, text, &textsz);
266                 if(width < 0) width = textsz.width + check_width + 10;
267                 if(height < 0) height = textsz.height;
268         }
269
270         if(!(w->handle = CreateWindow("BUTTON", text, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
271                         x, y, width, height, win->handle, 0, GetModuleHandle(0), 0))) {
272                 fprintf(stderr, "wgt_checkbox: failed to create window\n");
273                 free(w);
274                 return 0;
275         }
276
277         w->rect.x = x;
278         w->rect.y = y;
279         w->rect.width = width;
280         w->rect.height = height;
281
282         w->cb_click = modfunc;  /* BN_CLICKED is sent for checkbox state changes */
283         w->next = win->wlist;
284         win->wlist = w;
285         return w;
286 }
287
288 static void combosize(struct wgt_window *win, HWND cbwnd, const char **items, int num_items,
289                 int width, int height, struct wgt_rect *sz)
290 {
291         int i, max_width, sum_height;
292         struct wgt_rect textsz;
293
294         wgt_string_size(win, "00", &textsz);
295         max_width = width < 0 && width != WGT_AUTO ? -width : textsz.width;
296
297         /* first get the height of the editbox */
298         sum_height = SendMessage(cbwnd, CB_GETITEMHEIGHT, -1, 0);
299         sz->y = sum_height;
300         /* then add the height of the dropdown list items */
301         /* XXX I can't figure out why, but it's not sufficient to show all items
302          * without some more height, so let's double it...
303          */
304         sum_height *= 2;
305         sum_height += SendMessage(cbwnd, CB_GETITEMHEIGHT, 0, 0) * num_items;
306         sum_height += GetSystemMetrics(SM_CYEDGE) * 2;
307
308         if(width < 0) {
309                 int sbar_width;
310                 for(i=0; i<num_items; i++) {
311                         wgt_string_size(win, items[i], &textsz);
312                         if(textsz.width > max_width) max_width = textsz.width;
313                 }
314                 sbar_width = GetSystemMetrics(SM_CXVSCROLL);
315                 width = max_width + sbar_width + GetSystemMetrics(SM_CXEDGE) * 2;
316         }
317         if(height < 0) height = sum_height;
318
319         sz->width = width;
320         sz->height = height;
321 }
322
323 struct wgt_widget *wgt_combo(struct wgt_window *win, const char **items, int num_items,
324                         int sel, int x, int y, int width, int height, wgt_callback modfunc)
325 {
326         struct wgt_widget *w;
327         struct wgt_rect rect;
328
329         if(!(w = calloc(1, sizeof *w))) {
330                 fprintf(stderr, "wgt_combo: failed to allocate widget structure\n");
331                 return 0;
332         }
333         w->win = win;
334
335         if(!(w->handle = CreateWindow("COMBOBOX", items[0] ? items[0] : "",
336                         WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS,
337                         x, y, 16, 16, win->handle, 0, GetModuleHandle(0), 0))) {
338                 fprintf(stderr, "wgt_combo: failed to create window\n");
339                 free(w);
340                 return 0;
341         }
342
343         combosize(win, w->handle, items, num_items, width, height, &rect);
344         w->rect.x = x;
345         w->rect.y = y;
346         w->rect.width = rect.width;
347         w->rect.height = rect.y;        /* single line height for layout purposes */
348         MoveWindow(w->handle, x, y, rect.width, rect.height, FALSE);
349
350         wgt_combo_setitems(w, items, num_items);
351
352         if(sel < 0) sel = 0;
353         if(sel >= num_items) sel = num_items - 1;
354         if(sel >= 0) {
355                 SendMessage(w->handle, CB_SETCURSEL, sel, 0);
356         }
357
358         w->cb_modify = modfunc;
359         w->next = win->wlist;
360         win->wlist = w;
361         return w;
362 }
363
364 void wgt_checkbox_check(struct wgt_widget *w)
365 {
366         SendMessage(w->handle, BM_SETCHECK, BST_CHECKED, 0);
367 }
368
369 void wgt_checkbox_uncheck(struct wgt_widget *w)
370 {
371         SendMessage(w->handle, BM_SETCHECK, BST_UNCHECKED, 0);
372 }
373
374 int wgt_checkbox_checked(struct wgt_widget *w)
375 {
376         return SendMessage(w->handle, BM_GETCHECK, 0, 0) == BST_CHECKED;
377 }
378
379 int wgt_combo_setitems(struct wgt_widget *w, const char **items, int num_items)
380 {
381         int i;
382         char **newit;
383         struct wgt_rect size;
384
385         if(!(newit = malloc(num_items * sizeof *newit))) {
386                 fprintf(stderr, "wgt_combo_setitems: failed to allocate item list\n");
387                 return -1;
388         }
389         for(i=0; i<num_items; i++) {
390                 if(!(newit[i] = strdup(items[i]))) {
391                         fprintf(stderr, "wgt_combo_setitems: failed to allocate item\n");
392                         while(--i >= 0) {
393                                 free(newit[i]);
394                         }
395                         free(newit);
396                         return -1;
397                 }
398         }
399
400         for(i=0; i<w->num_items; i++) {
401                 free(w->itemlist[i]);
402         }
403         free(w->itemlist);
404         SendMessage(w->handle, CB_RESETCONTENT, 0, 0);
405
406         w->itemlist = newit;
407         w->num_items = num_items;
408
409         for(i=0; i<num_items; i++) {
410                 SendMessage(w->handle, CB_ADDSTRING, 0, (long)w->itemlist[i]);
411         }
412
413         combosize(w->win, w->handle, items, num_items, w->rect.width, WGT_AUTO, &size);
414         MoveWindow(w->handle, w->rect.x, w->rect.y, w->rect.width, size.height, TRUE);
415
416         SendMessage(w->handle, CB_SETCURSEL, 0, 0);
417         return 0;
418 }
419
420 int wgt_combo_selected(struct wgt_widget *w)
421 {
422         int sel = SendMessage(w->handle, CB_GETCURSEL, 0, 0);
423         if(sel == CB_ERR) return -1;
424         return sel;
425 }
426
427 const char *wgt_get_combo_item(struct wgt_widget *w, int idx)
428 {
429         if(idx < 0 || idx >= w->num_items) {
430                 return 0;
431         }
432         return w->itemlist[idx];
433 }
434
435 static LRESULT WINAPI event_handler(HWND wnd, unsigned int msg, WPARAM wparam, LPARAM lparam)
436 {
437         int ncode;
438         struct wgt_widget *w;
439         struct wgt_window *win;
440
441         win = (struct wgt_window*)GetWindowLong(wnd, GWL_USERDATA);
442
443         switch(msg) {
444         case WM_CLOSE:
445         case WM_DESTROY:
446                 PostQuitMessage(0);
447                 break;
448
449         case WM_COMMAND:
450                 if(!win || !(w = find_widget(win, (HWND)lparam))) {
451                         break;
452                 }
453                 ncode = HIWORD(wparam);
454
455                 switch(ncode) {
456                 case BN_CLICKED:
457                         if(w->cb_click) {
458                                 w->cb_click(w);
459                         }
460                         break;
461
462                 case CBN_SELCHANGE:
463                         if(w->cb_modify) {
464                                 w->cb_modify(w);
465                         }
466                         break;
467
468                 default:
469                         break;
470                 }
471
472         default:
473                 return DefWindowProc(wnd, msg, wparam, lparam);
474         }
475         return 0;
476 }
477
478 static struct wgt_widget *find_widget(struct wgt_window *win, HWND handle)
479 {
480         struct wgt_widget *w;
481
482         w = win->wlist;
483         while(w) {
484                 if(w->handle == handle) return w;
485                 w = w->next;
486         }
487         return 0;
488 }