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