- More heapification.
[wine] / programs / winecfg / driveui.c
1 /*
2  * Drive management UI code
3  *
4  * Copyright 2003 Mark Westcott
5  * Copyright 2004 Chris Morgan
6  * Copyright 2003-2004 Mike Hearn
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #include <stdarg.h>
25 #include <stdio.h>
26
27 #include <windef.h>
28 #include <winbase.h>
29 #include <winreg.h>
30 #include <shellapi.h>
31 #include <objbase.h>
32 #include <shlguid.h>
33 #include <shlwapi.h>
34 #include <shlobj.h>
35 #include <winuser.h>
36
37 #include <wine/debug.h>
38
39 #include "winecfg.h"
40 #include "resource.h"
41
42 WINE_DEFAULT_DEBUG_CHANNEL(winecfg);
43
44 #define BOX_MODE_CD_ASSIGN 1
45 #define BOX_MODE_CD_AUTODETECT 2
46 #define BOX_MODE_NONE 3
47 #define BOX_MODE_NORMAL 4
48
49 static BOOL advanced = FALSE;
50 static BOOL updating_ui = FALSE;
51 static struct drive* current_drive;
52
53 static void get_etched_rect(HWND dialog, RECT *rect);
54
55 static void set_advanced(HWND dialog)
56 {
57     int state;
58     char *text;
59     RECT rect;
60
61     /* FIXME: internationalization  */
62     if (advanced)
63     {
64         state = SW_NORMAL;
65         text = "&Hide Advanced";
66     }
67     else
68     {
69         state = SW_HIDE;
70         text = "&Show Advanced";
71     }
72
73     ShowWindow(GetDlgItem(dialog, IDC_RADIO_AUTODETECT), state);
74     ShowWindow(GetDlgItem(dialog, IDC_RADIO_ASSIGN), state);
75     ShowWindow(GetDlgItem(dialog, IDC_EDIT_LABEL), state);
76     ShowWindow(GetDlgItem(dialog, IDC_EDIT_DEVICE), state);
77     ShowWindow(GetDlgItem(dialog, IDC_STATIC_LABEL), state);
78     ShowWindow(GetDlgItem(dialog, IDC_BUTTON_BROWSE_DEVICE), state);
79     ShowWindow(GetDlgItem(dialog, IDC_EDIT_SERIAL), state);
80     ShowWindow(GetDlgItem(dialog, IDC_STATIC_SERIAL), state);
81     ShowWindow(GetDlgItem(dialog, IDC_LABELSERIAL_STATIC), state);
82
83     /* update the button text based on the state */
84     SetWindowText(GetDlgItem(dialog, IDC_BUTTON_SHOW_HIDE_ADVANCED), text);
85
86     /* redraw for the etched line */
87     get_etched_rect(dialog, &rect);
88     InflateRect(&rect, 5, 5);
89     InvalidateRect(dialog, &rect, TRUE);
90 }
91
92 struct drive_typemap {
93     const uint sCode;
94     const char *sDesc;
95 };
96
97 static struct drive_typemap type_pairs[] = {
98   { DRIVE_FIXED,      "Local hard disk" },
99   { DRIVE_REMOTE,     "Network share"   },
100   { DRIVE_REMOVABLE,  "Floppy disk"     },
101   { DRIVE_CDROM,      "CD-ROM"          }
102 };
103
104 #define DRIVE_TYPE_DEFAULT 1
105
106 void fill_drive_droplist(long mask, char curletter, HWND dialog)
107 {
108     int i;
109     int selection;
110     int count;
111     int next_letter;
112     char sName[4] = "A:";
113
114     for (i = 0, count = 0, selection = -1, next_letter = -1; i <= 'Z'-'A'; ++i)
115     {
116         if (mask & DRIVE_MASK_BIT('A' + i))
117         {
118             int index;
119
120             sName[0] = 'A' + i;
121             index = SendDlgItemMessage(dialog, IDC_COMBO_LETTER, CB_ADDSTRING, 0, (LPARAM) sName);
122
123             if (toupper(curletter) == 'A' + i)
124             {
125                 selection = count;
126             }
127
128             if (i >= 2 && next_letter == -1)
129             {
130                 /* default drive is first one of C-Z */
131                 next_letter = count;
132             }
133
134             count++;
135         }
136     }
137
138     if (selection == -1)
139     {
140         selection = next_letter;
141     }
142
143     SendDlgItemMessage(dialog, IDC_COMBO_LETTER, CB_SETCURSEL, selection, 0);
144 }
145
146
147 void enable_labelserial_box(HWND dialog, int mode)
148 {
149     WINE_TRACE("mode=%d\n", mode);
150
151     switch (mode)
152     {
153         case BOX_MODE_CD_ASSIGN:
154             enable(IDC_RADIO_ASSIGN);
155             disable(IDC_EDIT_DEVICE);
156             disable(IDC_BUTTON_BROWSE_DEVICE);
157             enable(IDC_EDIT_SERIAL);
158             enable(IDC_EDIT_LABEL);
159             enable(IDC_STATIC_SERIAL);
160             enable(IDC_STATIC_LABEL);
161             break;
162
163         case BOX_MODE_CD_AUTODETECT:
164             enable(IDC_RADIO_ASSIGN);
165             enable(IDC_EDIT_DEVICE);
166             enable(IDC_BUTTON_BROWSE_DEVICE);
167             disable(IDC_EDIT_SERIAL);
168             disable(IDC_EDIT_LABEL);
169             disable(IDC_STATIC_SERIAL);
170             disable(IDC_STATIC_LABEL);
171             break;
172
173         case BOX_MODE_NONE:
174             disable(IDC_RADIO_ASSIGN);
175             disable(IDC_EDIT_DEVICE);
176             disable(IDC_BUTTON_BROWSE_DEVICE);
177             disable(IDC_EDIT_SERIAL);
178             disable(IDC_EDIT_LABEL);
179             disable(IDC_STATIC_SERIAL);
180             disable(IDC_STATIC_LABEL);
181             break;
182
183         case BOX_MODE_NORMAL:
184             enable(IDC_RADIO_ASSIGN);
185             disable(IDC_EDIT_DEVICE);
186             disable(IDC_BUTTON_BROWSE_DEVICE);
187             enable(IDC_EDIT_SERIAL);
188             enable(IDC_EDIT_LABEL);
189             enable(IDC_STATIC_SERIAL);
190             enable(IDC_STATIC_LABEL);
191             break;
192     }
193 }
194
195 int fill_drives_list(HWND dialog)
196 {
197     int count = 0;
198     BOOL drivec_present = FALSE;
199     int i;
200     int prevsel = -1;
201
202     WINE_TRACE("\n");
203
204     updating_ui = TRUE;
205
206     prevsel = SendDlgItemMessage(dialog, IDC_LIST_DRIVES, LB_GETCURSEL, 0, 0);
207
208     /* Clear the listbox */
209     SendMessage(GetDlgItem(dialog, IDC_LIST_DRIVES), LB_RESETCONTENT, 0, 0);
210
211     for(i = 0; i < 26; i++)
212     {
213         char *title = 0;
214         int len;
215         int index;
216
217         /* skip over any unused drives */
218         if (!drives[i].in_use)
219             continue;
220
221         if (drives[i].letter == 'C')
222             drivec_present = TRUE;
223
224         len = snprintf(title, 0, "%c:    %s", 'A' + i,
225                        drives[i].unixpath);
226         len++; /* add a byte for the trailing null */
227
228         title = HeapAlloc(GetProcessHeap(), 0, len);
229
230         /* the %s in the item label will be replaced by the drive letter, so -1, then
231            -2 for the second %s which will be expanded to the label, finally + 1 for terminating #0 */
232         snprintf(title, len, "%c:    %s", 'A' + i,
233                  drives[i].unixpath);
234
235         WINE_TRACE("title is '%s'\n", title);
236
237         /* the first SendMessage call adds the string and returns the index, the second associates that index with it */
238         index = SendMessage(GetDlgItem(dialog, IDC_LIST_DRIVES), LB_ADDSTRING ,(WPARAM) -1, (LPARAM) title);
239         SendMessage(GetDlgItem(dialog, IDC_LIST_DRIVES), LB_SETITEMDATA, index, (LPARAM) &drives[i]);
240
241         HeapFree(GetProcessHeap(), 0, title);
242         count++;
243     }
244
245     WINE_TRACE("loaded %d drives\n", count);
246
247     /* show the warning if there is no Drive C */
248     if (!drivec_present)
249         ShowWindow(GetDlgItem(dialog, IDS_DRIVE_NO_C), SW_NORMAL);
250     else
251         ShowWindow(GetDlgItem(dialog, IDS_DRIVE_NO_C), SW_HIDE);
252
253     SendDlgItemMessage(dialog, IDC_LIST_DRIVES, LB_SETCURSEL, prevsel == -1 ? 0 : prevsel, 0);
254
255     updating_ui = FALSE;
256     return count;
257 }
258
259
260 void on_add_click(HWND dialog)
261 {
262     /* we should allocate a drive letter automatically. We also need
263        some way to let the user choose the mapping point, for now we
264        will just force them to enter a path automatically, with / being
265        the default. In future we should be able to temporarily map /
266        then invoke the directory chooser dialog. */
267
268     char new = 'C'; /* we skip A and B, they are historically floppy drives */
269     long mask = ~drive_available_mask(0); /* the mask is now which drives aren't available */
270     int i, c;
271
272     while (mask & (1 << (new - 'A')))
273     {
274         new++;
275         if (new > 'Z')
276         {
277             MessageBox(dialog, "You cannot add any more drives.\n\nEach drive must have a letter, from A to Z, so you cannot have more than 26", "", MB_OK | MB_ICONEXCLAMATION);
278             return;
279         }
280     }
281
282     WINE_TRACE("allocating drive letter %c\n", new);
283
284     if (new == 'C') add_drive(new, "../drive_c", "System Drive", "", DRIVE_FIXED);
285     else add_drive(new, "/", "", "", DRIVE_FIXED);
286
287     fill_drives_list(dialog);
288
289     /* select the newly created drive */
290     mask = ~drive_available_mask(0);
291     c = 0;
292     for (i = 0; i < 26; i++)
293     {
294         if ('A' + i == new) break;
295         if ((1 << i) & mask) c++;
296     }
297     SendDlgItemMessage(dialog, IDC_LIST_DRIVES, LB_SETCURSEL, c, 0);
298
299     SetFocus(GetDlgItem(dialog, IDC_LIST_DRIVES));
300 }
301
302 void on_remove_click(HWND dialog)
303 {
304     int item;
305     struct drive *drive;
306
307     item = SendDlgItemMessage(dialog, IDC_LIST_DRIVES,  LB_GETCURSEL, 0, 0);
308     if (item == -1) return; /* no selection */
309
310     drive = (struct drive *) SendDlgItemMessage(dialog, IDC_LIST_DRIVES, LB_GETITEMDATA, item, 0);
311
312     if (drive->letter == 'C')
313     {
314         DWORD result = MessageBox(dialog, "Are you sure you want to delete drive C?\n\nMost Windows applications expect drive C to exist, and will die messily if it doesn't. If you proceed remember to recreate it!", "", MB_YESNO | MB_ICONEXCLAMATION);
315         if (result == IDNO) return;
316     }
317
318     delete_drive(drive);
319
320     fill_drives_list(dialog);
321
322     item = item - 1;
323     if (item < 0) item = 0;
324     SendDlgItemMessage(dialog, IDC_LIST_DRIVES, LB_SETCURSEL, item, 0);   /* previous item */
325
326     SetFocus(GetDlgItem(dialog, IDC_LIST_DRIVES));
327 }
328
329 void update_controls(HWND dialog) {
330     char *path;
331     uint type;
332     char *label;
333     char *serial;
334     char *device;
335     int i, selection = -1;
336
337     updating_ui = TRUE;
338
339     i = SendDlgItemMessage(dialog, IDC_LIST_DRIVES, LB_GETCURSEL, 0, 0);
340     if (i == -1)
341     {
342         /* no selection? let's select something for the user. this will re-enter */
343         SendDlgItemMessage(dialog, IDC_LIST_DRIVES, LB_SETCURSEL, 0, 0);
344         return;
345     }
346     current_drive = (struct drive *) SendDlgItemMessage(dialog, IDC_LIST_DRIVES, LB_GETITEMDATA, i, 0);
347
348     WINE_TRACE("Updating sheet for drive %c\n", current_drive->letter);
349
350     /* Drive letters */
351     fill_drive_droplist(drive_available_mask(current_drive->letter), current_drive->letter, dialog);
352
353     /* path */
354     path = current_drive->unixpath;
355     WINE_TRACE("set path control text to '%s'\n", path);
356     set_text(dialog, IDC_EDIT_PATH, path);
357
358     /* drive type */
359     type = current_drive->type;
360     if (type)
361     {
362         for (i = 0; i < sizeof(type_pairs) / sizeof(struct drive_typemap); i++)
363         {
364             SendDlgItemMessage(dialog, IDC_COMBO_TYPE, CB_ADDSTRING, 0, (LPARAM) type_pairs[i].sDesc);
365
366             if (type_pairs[i].sCode ==  type)
367             {
368                 selection = i;
369             }
370         }
371
372         if (selection == -1) selection = DRIVE_TYPE_DEFAULT;
373         SendDlgItemMessage(dialog, IDC_COMBO_TYPE, CB_SETCURSEL, selection, 0);
374     } else WINE_WARN("no Type field?\n");
375
376
377     /* removeable media properties */
378     label = current_drive->label;
379     set_text(dialog, IDC_EDIT_LABEL, label);
380
381     /* set serial edit text */
382     serial = current_drive->serial;
383     set_text(dialog, IDC_EDIT_SERIAL, serial);
384
385     /* TODO: get the device here to put into the edit box */
386     device = "Not implemented yet";
387     set_text(dialog, IDC_EDIT_DEVICE, device);
388     device = NULL;
389
390     selection = IDC_RADIO_ASSIGN;
391     if ((type == DRIVE_CDROM) || (type == DRIVE_REMOVABLE))
392     {
393         if (device)
394         {
395             selection = IDC_RADIO_AUTODETECT;
396             enable_labelserial_box(dialog, BOX_MODE_CD_AUTODETECT);
397         }
398         else
399         {
400             selection = IDC_RADIO_ASSIGN;
401             enable_labelserial_box(dialog, BOX_MODE_CD_ASSIGN);
402         }
403     }
404     else
405     {
406         enable_labelserial_box(dialog, BOX_MODE_NORMAL);
407         selection = IDC_RADIO_ASSIGN;
408     }
409
410     CheckRadioButton(dialog, IDC_RADIO_AUTODETECT, IDC_RADIO_ASSIGN, selection);
411
412     updating_ui = FALSE;
413
414     return;
415 }
416
417 void on_edit_changed(HWND dialog, WORD id)
418 {
419     if (updating_ui) return;
420
421     WINE_TRACE("edit id %d changed\n", id);
422
423     /* using fill_drives_list here is pretty lazy, but i'm tired
424
425        fortunately there are only 26 letters in the alphabet, so
426        we don't have to worry about efficiency too much here :)  */
427
428     switch (id)
429     {
430         case IDC_EDIT_LABEL:
431         {
432             char *label;
433
434             label = get_text(dialog, id);
435             if (current_drive->label) HeapFree(GetProcessHeap(), 0, current_drive->label);
436             current_drive->label = label ? label :  strdupA("");
437
438             WINE_TRACE("set label to %s\n", current_drive->label);
439
440             fill_drives_list(dialog);
441             break;
442         }
443
444         case IDC_EDIT_PATH:
445         {
446             char *path;
447
448             path = get_text(dialog, id);
449             if (current_drive->unixpath) HeapFree(GetProcessHeap(), 0, current_drive->unixpath);
450             current_drive->unixpath = path ? path : strdupA("drive_c");
451
452             WINE_TRACE("set path to %s\n", current_drive->unixpath);
453
454             fill_drives_list(dialog);
455             break;
456         }
457
458         case IDC_EDIT_SERIAL:
459         {
460             char *serial;
461
462             serial = get_text(dialog, id);
463             if (current_drive->serial) HeapFree(GetProcessHeap(), 0, current_drive->serial);
464             current_drive->serial = serial ? serial : strdupA("");
465
466             WINE_TRACE("set serial to %s", current_drive->serial);
467
468             break;
469         }
470
471         case IDC_EDIT_DEVICE:
472         {
473             char *device = get_text(dialog, id);
474             /* TODO: handle device if/when it makes sense to do so.... */
475             if (device) HeapFree(GetProcessHeap(), 0, device);
476             fill_drives_list(dialog);
477             break;
478         }
479     }
480 }
481
482 static void get_etched_rect(HWND dialog, RECT *rect)
483 {
484     GetClientRect(dialog, rect);
485
486     /* these dimensions from the labelserial static in En.rc  */
487     rect->top = 258;
488     rect->bottom = 258;
489     rect->left += 35;
490     rect->right -= 25;
491 }
492
493 /* this just draws a nice line to separate the advanced gui from the n00b gui :) */
494 static void paint(HWND dialog)
495 {
496     PAINTSTRUCT ps;
497
498     BeginPaint(dialog, &ps);
499
500     if (advanced)
501     {
502         RECT rect;
503
504         get_etched_rect(dialog, &rect);
505
506         DrawEdge(ps.hdc, &rect, EDGE_ETCHED, BF_TOP);
507     }
508
509     EndPaint(dialog, &ps);
510 }
511
512 INT_PTR CALLBACK
513 DriveDlgProc (HWND dialog, UINT msg, WPARAM wParam, LPARAM lParam)
514 {
515     int item;
516     struct drive *drive;
517
518     switch (msg)
519     {
520         case WM_INITDIALOG:
521             load_drives();
522
523             if (!drives[2].in_use)
524                 MessageBox(dialog, "You don't have a drive C. This is not so great.\n\nRemember to click 'Add' in the Drives tab to create one!\n", "", MB_OK | MB_ICONEXCLAMATION);
525
526             fill_drives_list(dialog);
527             update_controls(dialog);
528             /* put in non-advanced mode by default  */
529             set_advanced(dialog);
530             break;
531
532         case WM_SHOWWINDOW:
533             set_window_title(dialog);
534             break;
535
536         case WM_PAINT:
537             paint(dialog);
538             break;
539
540         case WM_COMMAND:
541             if (HIWORD(wParam) == EN_CHANGE)
542             {
543                 on_edit_changed(dialog, LOWORD(wParam));
544                 break;
545             }
546
547             switch (LOWORD(wParam))
548             {
549                 case IDC_LIST_DRIVES:
550                     if (HIWORD(wParam) == LBN_SELCHANGE)
551                         update_controls(dialog);
552
553                     break;
554
555                 case IDC_BUTTON_ADD:
556                     if (HIWORD(wParam) != BN_CLICKED) break;
557                     on_add_click(dialog);
558                     break;
559
560                 case IDC_BUTTON_REMOVE:
561                     if (HIWORD(wParam) != BN_CLICKED) break;
562                     on_remove_click(dialog);
563                     break;
564
565                 case IDC_BUTTON_EDIT:
566                     if (HIWORD(wParam) != BN_CLICKED) break;
567                     item = SendMessage(GetDlgItem(dialog, IDC_LIST_DRIVES),  LB_GETCURSEL, 0, 0);
568                     drive = (struct drive *) SendMessage(GetDlgItem(dialog, IDC_LIST_DRIVES), LB_GETITEMDATA, item, 0);
569                     /*DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_DRIVE_EDIT), NULL, (DLGPROC) DriveEditDlgProc, (LPARAM) drive); */
570                     break;
571
572                 case IDC_BUTTON_AUTODETECT:
573                     autodetect_drives();
574                     fill_drives_list(dialog);
575                     break;
576
577                 case IDC_BUTTON_SHOW_HIDE_ADVANCED:
578                     advanced = !advanced;
579                     set_advanced(dialog);
580                     break;
581
582                 case IDC_BUTTON_BROWSE_PATH:
583                     MessageBox(dialog, "", "Write me!", MB_OK);
584                     break;
585
586                 case IDC_RADIO_ASSIGN:
587                 {
588                     char *str;
589
590                     str = get_text(dialog, IDC_EDIT_LABEL);
591                     if (current_drive->label) HeapFree(GetProcessHeap(), 0, current_drive->label);
592                     current_drive->label = str ? str : strdupA("");
593
594                     str = get_text(dialog, IDC_EDIT_SERIAL);
595                     if (current_drive->serial) HeapFree(GetProcessHeap(), 0, current_drive->serial);
596                     current_drive->serial = str ? str : strdupA("");
597
598                     /* TODO: we don't have a device at this point */
599
600                     enable_labelserial_box(dialog, BOX_MODE_CD_ASSIGN);
601
602                     break;
603                 }
604
605
606                 case IDC_COMBO_TYPE:
607                 {
608                     int mode = BOX_MODE_NORMAL;
609                     int selection;
610
611                     if (HIWORD(wParam) != CBN_SELCHANGE) break;
612
613                     selection = SendDlgItemMessage(dialog, IDC_COMBO_TYPE, CB_GETCURSEL, 0, 0);
614
615                     if (selection == 2 || selection == 3) /* cdrom or floppy */
616                     {
617                         if (IsDlgButtonChecked(dialog, IDC_RADIO_AUTODETECT))
618                             mode = BOX_MODE_CD_AUTODETECT;
619                         else
620                             mode = BOX_MODE_CD_ASSIGN;
621                     }
622
623                     enable_labelserial_box(dialog, mode);
624
625                     current_drive->type = type_pairs[selection].sCode;
626                     break;
627                 }
628
629             }
630             break;
631
632         case WM_NOTIFY:
633             switch (((LPNMHDR)lParam)->code)
634             {
635                 case PSN_KILLACTIVE:
636                     WINE_TRACE("PSN_KILLACTIVE\n");
637                     SetWindowLong(dialog, DWL_MSGRESULT, FALSE);
638                     break;
639                 case PSN_APPLY:
640                     apply_drive_changes();
641                     SetWindowLong(dialog, DWL_MSGRESULT, PSNRET_NOERROR);
642                     break;
643                 case PSN_SETACTIVE:
644                     break;
645             }
646             break;
647     }
648
649     return FALSE;
650 }