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