82e49339f3577975543d9e3a7d6e13921daf7576
[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         sum_height = SendMessage(cbwnd, CB_GETITEMHEIGHT, -1, 0);
298         sz->y = sum_height;
299
300         for(i=0; i<num_items; i++) {
301                 wgt_string_size(win, items[i], &textsz);
302                 if(textsz.width > max_width) max_width = textsz.width;
303                 sum_height += SendMessage(cbwnd, CB_GETITEMHEIGHT, i, 0);
304         }
305         if(width < 0) width = max_width * 3 / 2;
306         if(height < 0) height = sum_height;
307
308         sz->width = width;
309         sz->height = height;
310 }
311
312 struct wgt_widget *wgt_combo(struct wgt_window *win, const char **items, int num_items,
313                         int sel, int x, int y, int width, int height, wgt_callback modfunc)
314 {
315         struct wgt_widget *w;
316         struct wgt_rect rect;
317
318         if(!(w = calloc(1, sizeof *w))) {
319                 fprintf(stderr, "wgt_combo: failed to allocate widget structure\n");
320                 return 0;
321         }
322         w->win = win;
323
324         if(!(w->handle = CreateWindow("COMBOBOX", items[0] ? items[0] : "",
325                         WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS,
326                         x, y, 16, 16, win->handle, 0, GetModuleHandle(0), 0))) {
327                 fprintf(stderr, "wgt_combo: failed to create window\n");
328                 free(w);
329                 return 0;
330         }
331
332         combosize(win, w->handle, items, num_items, width, height, &rect);
333         w->rect.x = x;
334         w->rect.y = y;
335         w->rect.width = rect.width;
336         w->rect.height = rect.y;        /* single line height for layout purposes */
337         MoveWindow(w->handle, x, y, rect.width, rect.height, FALSE);
338
339         wgt_combo_setitems(w, items, num_items);
340
341         if(sel < 0) sel = 0;
342         if(sel >= num_items) sel = num_items - 1;
343         if(sel >= 0) {
344                 SendMessage(w->handle, CB_SETCURSEL, sel, 0);
345         }
346
347
348         w->cb_modify = modfunc;
349         w->next = win->wlist;
350         win->wlist = w;
351         return w;
352 }
353
354 void wgt_checkbox_check(struct wgt_widget *w)
355 {
356         SendMessage(w->handle, BM_SETCHECK, BST_CHECKED, 0);
357 }
358
359 void wgt_checkbox_uncheck(struct wgt_widget *w)
360 {
361         SendMessage(w->handle, BM_SETCHECK, BST_UNCHECKED, 0);
362 }
363
364 int wgt_checkbox_checked(struct wgt_widget *w)
365 {
366         return SendMessage(w->handle, BM_GETCHECK, 0, 0) == BST_CHECKED;
367 }
368
369 int wgt_combo_setitems(struct wgt_widget *w, const char **items, int num_items)
370 {
371         int i;
372         char **newit;
373         struct wgt_rect size;
374
375         if(!(newit = malloc(num_items * sizeof *newit))) {
376                 fprintf(stderr, "wgt_combo_setitems: failed to allocate item list\n");
377                 return -1;
378         }
379         for(i=0; i<num_items; i++) {
380                 if(!(newit[i] = strdup(items[i]))) {
381                         fprintf(stderr, "wgt_combo_setitems: failed to allocate item\n");
382                         while(--i >= 0) {
383                                 free(newit[i]);
384                         }
385                         free(newit);
386                         return -1;
387                 }
388         }
389
390         for(i=0; i<w->num_items; i++) {
391                 free(w->itemlist[i]);
392         }
393         free(w->itemlist);
394         SendMessage(w->handle, CB_RESETCONTENT, 0, 0);
395
396         w->itemlist = newit;
397         w->num_items = num_items;
398
399         for(i=0; i<num_items; i++) {
400                 SendMessage(w->handle, CB_ADDSTRING, 0, (long)w->itemlist[i]);
401         }
402
403         combosize(w->win, w->handle, items, num_items, w->rect.width, WGT_AUTO, &size);
404         MoveWindow(w->handle, w->rect.x, w->rect.y, w->rect.width, size.height, TRUE);
405         return 0;
406 }
407
408 int wgt_combo_selected(struct wgt_widget *w)
409 {
410         int sel = SendMessage(w->handle, CB_GETCURSEL, 0, 0);
411         if(sel == CB_ERR) return -1;
412         return sel;
413 }
414
415 const char *wgt_get_combo_item(struct wgt_widget *w, int idx)
416 {
417         if(idx < 0 || idx >= w->num_items) {
418                 return 0;
419         }
420         return w->itemlist[idx];
421 }
422
423 static LRESULT WINAPI event_handler(HWND wnd, unsigned int msg, WPARAM wparam, LPARAM lparam)
424 {
425         int ncode;
426         struct wgt_widget *w;
427         struct wgt_window *win;
428
429         win = (struct wgt_window*)GetWindowLong(wnd, GWL_USERDATA);
430
431         switch(msg) {
432         case WM_CLOSE:
433         case WM_DESTROY:
434                 PostQuitMessage(0);
435                 break;
436
437         case WM_COMMAND:
438                 if(!win || !(w = find_widget(win, (HWND)lparam))) {
439                         break;
440                 }
441                 ncode = HIWORD(wparam);
442
443                 switch(ncode) {
444                 case BN_CLICKED:
445                         if(w->cb_click) {
446                                 w->cb_click(w);
447                         }
448                         break;
449
450                 case CBN_SELCHANGE:
451                         if(w->cb_modify) {
452                                 w->cb_modify(w);
453                         }
454                         break;
455
456                 default:
457                         break;
458                 }
459
460         default:
461                 return DefWindowProc(wnd, msg, wparam, lparam);
462         }
463         return 0;
464 }
465
466 static struct wgt_widget *find_widget(struct wgt_window *win, HWND handle)
467 {
468         struct wgt_widget *w;
469
470         w = win->wlist;
471         while(w) {
472                 if(w->handle == handle) return w;
473                 w = w->next;
474         }
475         return 0;
476 }