widget disabling and autosize with minimum
[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 struct wgt_widget *wgt_combo(struct wgt_window *win, const char **items, int num_items,
289                         int sel, int x, int y, int width, int height, wgt_callback modfunc)
290 {
291         int i, max_width, max_height, sum_height, res;
292         struct wgt_widget *w;
293         struct wgt_rect textsz;
294
295         if(!(w = calloc(1, sizeof *w))) {
296                 fprintf(stderr, "wgt_combo: failed to allocate widget structure\n");
297                 return 0;
298         }
299         w->win = win;
300
301         if(!(w->itemlist = malloc(num_items * sizeof *w->itemlist))) {
302                 fprintf(stderr, "wgt_combo: failed to allocate item list\n");
303                 free(w);
304                 return 0;
305         }
306
307         wgt_string_size(win, "00", &textsz);
308         max_width = width < 0 && width != WGT_AUTO ? -width : textsz.width;
309         max_height = height < 0 && height != WGT_AUTO ? -height : textsz.height;
310         sum_height = num_items ? 0 : max_height;
311
312         for(i=0; i<num_items; i++) {
313                 wgt_string_size(win, items[i], &textsz);
314                 if(textsz.width > max_width) max_width = textsz.width;
315                 if(textsz.height > max_height) max_height = textsz.height;
316                 sum_height += textsz.height;
317         }
318         if(width < 0) width = max_width * 3 / 2;
319         if(height < 0) height = sum_height * 2;
320
321
322         if(!(w->handle = CreateWindow("COMBOBOX", items[0] ? items[0] : "",
323                         WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS,
324                         x, y, width, height, win->handle, 0, GetModuleHandle(0), 0))) {
325                 fprintf(stderr, "wgt_combo: failed to create window\n");
326                 free(w);
327                 return 0;
328         }
329
330         for(i=0; i<num_items; i++) {
331                 if(!(w->itemlist[i] = strdup(items[i]))) {
332                         fprintf(stderr, "wgt_combo: failed to allocate item\n");
333                         while(--i >= 0) free(w->itemlist[i]);
334                         DestroyWindow(w->handle);
335                         free(w->itemlist);
336                         free(w);
337                         return 0;
338                 }
339                 if((res = SendMessage(w->handle, CB_ADDSTRING, 0, (long)items[i])) != i) {
340                         fprintf(stderr, "wgt_combo: failed to add item\n");
341                 }
342         }
343         w->num_items = num_items;
344
345         if(sel < 0) sel = 0;
346         if(sel >= num_items) sel = num_items - 1;
347         if(sel >= 0) {
348                 SendMessage(w->handle, CB_SETCURSEL, sel, 0);
349         }
350
351         w->rect.x = x;
352         w->rect.y = y;
353         w->rect.width = width;
354         w->rect.height = max_height;
355
356         w->cb_modify = modfunc;
357         w->next = win->wlist;
358         win->wlist = w;
359         return w;
360 }
361
362 int wgt_combo_selected(struct wgt_widget *w)
363 {
364         int sel = SendMessage(w->handle, CB_GETCURSEL, 0, 0);
365         if(sel == CB_ERR) return -1;
366         return sel;
367 }
368
369 const char *wgt_get_combo_item(struct wgt_widget *w, int idx)
370 {
371         if(idx < 0 || idx >= w->num_items) {
372                 return 0;
373         }
374         return w->itemlist[idx];
375 }
376
377 static LRESULT WINAPI event_handler(HWND wnd, unsigned int msg, WPARAM wparam, LPARAM lparam)
378 {
379         int ncode;
380         struct wgt_widget *w;
381         struct wgt_window *win;
382
383         win = (struct wgt_window*)GetWindowLong(wnd, GWL_USERDATA);
384
385         switch(msg) {
386         case WM_CLOSE:
387         case WM_DESTROY:
388                 PostQuitMessage(0);
389                 break;
390
391         case WM_COMMAND:
392                 if(!win || !(w = find_widget(win, (HWND)lparam))) {
393                         break;
394                 }
395                 ncode = HIWORD(wparam);
396
397                 switch(ncode) {
398                 case BN_CLICKED:
399                         if(w->cb_click) {
400                                 w->cb_click(w);
401                         }
402                         break;
403
404                 case CBN_SELCHANGE:
405                         if(w->cb_modify) {
406                                 w->cb_modify(w);
407                         }
408                         break;
409
410                 default:
411                         break;
412                 }
413
414         default:
415                 return DefWindowProc(wnd, msg, wparam, lparam);
416         }
417         return 0;
418 }
419
420 static struct wgt_widget *find_widget(struct wgt_window *win, HWND handle)
421 {
422         struct wgt_widget *w;
423
424         w = win->wlist;
425         while(w) {
426                 if(w->handle == handle) return w;
427                 w = w->next;
428         }
429         return 0;
430 }