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