1 /*
2  * file list management ("virtual photo album").
3  * (c) 2003 Gerd Hoffmann <gerd@kraxel.org>
4  *
5  */
6 
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <stddef.h>
10 #include <unistd.h>
11 #include <dirent.h>
12 #include <errno.h>
13 #include <string.h>
14 #include <dirent.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 
18 #include <X11/Xlib.h>
19 #include <X11/Intrinsic.h>
20 #include <X11/extensions/XShm.h>
21 #include <Xm/Xm.h>
22 #include <Xm/Form.h>
23 #include <Xm/Label.h>
24 #include <Xm/RowColumn.h>
25 #include <Xm/PushB.h>
26 #include <Xm/CascadeB.h>
27 #include <Xm/ScrolledW.h>
28 #include <Xm/SelectioB.h>
29 #include <Xm/Transfer.h>
30 #include <Xm/TransferP.h>
31 #include <Xm/Container.h>
32 #include <Xm/FileSB.h>
33 #include <Xm/Separator.h>
34 
35 #include "RegEdit.h"
36 #include "ida.h"
37 #include "readers.h"
38 #include "viewer.h"
39 #include "browser.h"
40 #include "filter.h"
41 #include "x11.h"
42 #include "selections.h"
43 #include "filebutton.h"
44 #include "filelist.h"
45 #include "xdnd.h"
46 #include "idaconfig.h"
47 
48 /*----------------------------------------------------------------------*/
49 
50 struct list_handle;
51 
52 struct list_handle {
53     char              *filename;
54     struct list_head  files;
55 
56     Widget            shell;
57     Widget            scroll;
58     Widget            container;
59     Widget            status;
60     XmString          details[DETAIL_COUNT+1];
61 
62     Widget            loadbox;
63     Widget            savebox;
64 
65     XtWorkProcId      wproc;
66 };
67 
68 /* ---------------------------------------------------------------------- */
69 
filelist_add(struct list_handle * h,char * filename)70 static void filelist_add(struct list_handle *h, char *filename)
71 {
72     struct file_button *file;
73     char *tmp;
74 
75     /* fixup filename */
76     if (0 == strncmp(filename,"file:",5))
77 	filename += 5;
78     if (NULL != (tmp = strchr(filename,'\n')))
79 	*tmp = 0;
80     if (NULL != (tmp = strchr(filename,'\r')))
81 	*tmp = 0;
82     if (0 == strlen(filename))
83 	return;
84 
85     /* add file */
86     file = malloc(sizeof(*file));
87     memset(file,0,sizeof(*file));
88 
89     tmp = strrchr(filename,'/');
90     if (!tmp)
91 	goto oops;
92     file->basename = strdup(tmp+1);
93     file->filename = strdup(filename);
94 
95     if (-1 == stat(file->filename,&file->st)) {
96 	fprintf(stderr,"stat %s: %s\n",file->filename,strerror(errno));
97 	goto oops;
98     }
99     if (!S_ISREG(file->st.st_mode)) {
100 	fprintf(stderr,"%s: not a regular file\n",file->filename);
101 	goto oops;
102     }
103 
104     list_add_tail(&file->window,&h->files);
105     file_createwidgets(h->container, file);
106     XtManageChild(file->widget);
107     fileinfo_queue(file);
108     container_relayout(h->container);
109     return;
110 
111  oops:
112     if (file->filename)
113 	free(file->filename);
114     if (file->basename)
115 	free(file->basename);
116     free(file);
117 }
118 
filelist_file(struct list_handle * h,char * filename)119 static void filelist_file(struct list_handle *h, char *filename)
120 {
121     if (h->filename == filename)
122 	return;
123     if (h->filename)
124 	free(h->filename);
125     h->filename = strdup(filename);
126     XtVaSetValues(h->shell,XtNtitle,h->filename,NULL);
127 }
128 
filelist_read(struct list_handle * h,char * filename)129 static void filelist_read(struct list_handle *h, char *filename)
130 {
131     FILE *fp;
132     char line[128];
133 
134     fp = fopen(filename,"r");
135     if (NULL == fp) {
136 	fprintf(stderr,"open %s: %s\n",filename,strerror(errno));
137 	return;
138     }
139     while (NULL != fgets(line, sizeof(line), fp)) {
140 	filelist_add(h, line);
141     }
142     fclose(fp);
143     filelist_file(h,filename);
144     container_relayout(h->container);
145 }
146 
filelist_write(struct list_handle * h,char * filename)147 static void filelist_write(struct list_handle *h, char *filename)
148 {
149     struct file_button *file;
150     struct list_head *item;
151     FILE *fp;
152 
153     fp = fopen(filename,"w");
154     if (NULL == fp) {
155 	fprintf(stderr,"open %s: %s\n",filename,strerror(errno));
156 	return;
157     }
158     list_for_each(item, &h->files) {
159 	file = list_entry(item, struct file_button, window);
160 	fprintf(fp,"%s\n",file->filename);
161     }
162     fclose(fp);
163     filelist_file(h,filename);
164 }
165 
filelist_delall(struct list_handle * h)166 static void filelist_delall(struct list_handle *h)
167 {
168     struct file_button *file;
169     struct list_head *item;
170 
171     list_for_each(item, &h->files) {
172 	file = list_entry(item, struct file_button, window);
173 	XtUnmanageChild(file->widget);
174 	XtDestroyWidget(file->widget);
175     }
176 }
177 
178 /* ---------------------------------------------------------------------- */
179 /* receive data (drops, paste)                                            */
180 
181 static Atom targets[16];
182 static Cardinal ntargets;
183 
184 static void
filelist_xfer(Widget widget,XtPointer clientdata,XtPointer call_data)185 filelist_xfer(Widget widget, XtPointer clientdata, XtPointer call_data)
186 {
187     struct list_handle *h = clientdata;
188     XmSelectionCallbackStruct *scs = call_data;
189     unsigned char *cdata = scs->value;
190     unsigned long *ldata = scs->value;
191     Atom target = 0;
192     unsigned int i,j,pending;
193     char *file;
194 
195     if (debug) {
196 	char *y = !scs->type      ? NULL : XGetAtomName(dpy,scs->type);
197 	char *t = !scs->target    ? NULL : XGetAtomName(dpy,scs->target);
198 	char *s = !scs->selection ? NULL : XGetAtomName(dpy,scs->selection);
199 	fprintf(stderr,"list: id=%p target=%s type=%s selection=%s\n",
200 		scs->transfer_id,t,y,s);
201 	if (y) XFree(y);
202 	if (t) XFree(t);
203 	if (s) XFree(s);
204     }
205 
206     pending = scs->remaining;
207     if (scs->target == XA_TARGETS) {
208 	/* look if we find a target we can deal with ... */
209 	for (i = 0; !target && i < scs->length; i++) {
210 	    for (j = 0; j < ntargets; j++) {
211 		if (ldata[i] == targets[j]) {
212 		    target = ldata[i];
213 		    break;
214 		}
215 	    }
216 	}
217 	if (target) {
218 	    XmTransferValue(scs->transfer_id, target, filelist_xfer,
219 			    clientdata, XtLastTimestampProcessed(dpy));
220 	    pending++;
221 	}
222 	if (debug) {
223 	    fprintf(stderr,"list: available targets: ");
224 	    for (i = 0; i < scs->length; i++) {
225 		char *name = !ldata[i] ? NULL : XGetAtomName(dpy,ldata[i]);
226 		fprintf(stderr,"%s%s", i != 0 ? ", " : "", name);
227 		XFree(name);
228 	    }
229 	    fprintf(stderr,"\n");
230 	    if (0 == scs->length)
231 		fprintf(stderr,"list: Huh? no TARGETS available?\n");
232 	}
233     }
234 
235     if (scs->target == XA_FILE_NAME ||
236 	scs->target == XA_FILE) {
237 	/* load file */
238 	if (debug)
239 	    fprintf(stderr,"list: => \"%s\"\n",cdata);
240 	filelist_add(h,cdata);
241     }
242 
243     if (scs->target == _NETSCAPE_URL) {
244 	/* load file */
245 	if (debug)
246 	    fprintf(stderr,"list: => \"%s\"\n",cdata);
247 	filelist_add(h,cdata);
248     }
249 
250     if (scs->target == MIME_TEXT_URI_LIST) {
251 	/* load file(s) */
252 	for (file = strtok(cdata,"\r\n");
253 	     NULL != file;
254 	     file = strtok(NULL,"\r\n")) {
255 	    if (debug)
256 		fprintf(stderr,"list: => \"%s\"\n",file);
257 	    filelist_add(h,file);
258 	}
259     }
260 
261     XFree(scs->value);
262     if (1 == pending) {
263 	/* all done -- clean up */
264 	if (debug)
265 	    fprintf(stderr,"list: all done\n");
266 	XmTransferDone(scs->transfer_id, XmTRANSFER_DONE_SUCCEED);
267 	XdndDropFinished(widget,scs);
268     }
269 }
270 
271 static void
filelist_dest_cb(Widget w,XtPointer clientdata,XtPointer call_data)272 filelist_dest_cb(Widget  w, XtPointer clientdata, XtPointer call_data)
273 {
274     XmDestinationCallbackStruct *dcs = call_data;
275 
276     if (debug)
277 	fprintf(stderr,"list: xfer id=%p\n",dcs->transfer_id);
278     XmTransferValue(dcs->transfer_id, XA_TARGETS, filelist_xfer,
279 		    clientdata, XtLastTimestampProcessed(dpy));
280 }
281 
282 /*----------------------------------------------------------------------*/
283 
284 static void
filelist_new_cb(Widget widget,XtPointer clientdata,XtPointer call_data)285 filelist_new_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
286 {
287     struct list_handle *h = clientdata;
288 
289     filelist_delall(h);
290     if (h->filename) {
291 	free(h->filename);
292 	h->filename = NULL;
293     }
294     XtVaSetValues(h->shell,XtNtitle,"new list",NULL);
295 }
296 
297 static void
init_file_box(Widget box,char * filename)298 init_file_box(Widget box, char *filename)
299 {
300     char *dir,*file;
301     XmString s1;
302 
303     if (NULL == filename) {
304 	dir = strdup(ida_lists);
305     } else {
306 	dir = strdup(filename);
307 	file = strrchr(dir,'/');
308 	if (NULL == file)
309 	    return;
310 	*file = 0;
311 	file++;
312     }
313 
314     s1 = XmStringGenerate(dir, NULL, XmMULTIBYTE_TEXT, NULL);
315     XtVaSetValues(box,
316 		  XmNdirectory, s1,
317 		  XmNpattern,   NULL,
318 		  NULL);
319     XmFileSelectionDoSearch(box,NULL);
320     XmStringFree(s1);
321     free(dir);
322 }
323 
324 static void
load_done_cb(Widget widget,XtPointer clientdata,XtPointer call_data)325 load_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
326 {
327     XmFileSelectionBoxCallbackStruct *cb = call_data;
328     struct list_handle *h = clientdata;
329     char *filename;
330 
331     if (cb->reason == XmCR_OK) {
332 	filename = XmStringUnparse(cb->value,NULL,
333 				   XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
334 				   NULL,0,0);
335 	if (debug)
336 	    fprintf(stderr,"read list from %s\n",filename);
337 	filelist_read(h, filename);
338     }
339     XtUnmanageChild(widget);
340 }
341 
342 static void
filelist_load_cb(Widget widget,XtPointer clientdata,XtPointer call_data)343 filelist_load_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
344 {
345     struct list_handle *h = clientdata;
346     Widget help;
347 
348     if (NULL == h->loadbox) {
349 	h->loadbox = XmCreateFileSelectionDialog(h->shell,"load",NULL,0);
350 	help = XmFileSelectionBoxGetChild(h->loadbox,XmDIALOG_HELP_BUTTON);
351 	XtUnmanageChild(help);
352 	XtAddCallback(h->loadbox,XmNokCallback,load_done_cb,h);
353 	XtAddCallback(h->loadbox,XmNcancelCallback,load_done_cb,h);
354     }
355     init_file_box(h->loadbox,h->filename);
356     XtManageChild(h->loadbox);
357 }
358 
359 static void
save_done_cb(Widget widget,XtPointer clientdata,XtPointer call_data)360 save_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
361 {
362     XmFileSelectionBoxCallbackStruct *cb = call_data;
363     struct list_handle *h = clientdata;
364     char *filename;
365 
366     if (cb->reason == XmCR_OK) {
367 	filename = XmStringUnparse(cb->value,NULL,
368 				   XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
369 				   NULL,0,0);
370 	if (debug)
371 	    fprintf(stderr,"write list to %s\n",filename);
372 	filelist_write(h, filename);
373     }
374     XtUnmanageChild(widget);
375 }
376 
377 static void
filelist_save_as_cb(Widget widget,XtPointer clientdata,XtPointer call_data)378 filelist_save_as_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
379 {
380     struct list_handle *h = clientdata;
381     Widget help;
382 
383     if (NULL == h->savebox) {
384 	h->savebox = XmCreateFileSelectionDialog(h->shell,"save",NULL,0);
385 	help = XmFileSelectionBoxGetChild(h->savebox,XmDIALOG_HELP_BUTTON);
386 	XtUnmanageChild(help);
387 
388 	XtAddCallback(h->savebox,XmNokCallback,save_done_cb,h);
389 	XtAddCallback(h->savebox,XmNcancelCallback,save_done_cb,h);
390     }
391     init_file_box(h->savebox,h->filename);
392     XtManageChild(h->savebox);
393 }
394 
395 static void
filelist_save_cb(Widget widget,XtPointer clientdata,XtPointer call_data)396 filelist_save_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
397 {
398     struct list_handle *h = clientdata;
399 
400     if (h->filename) {
401 	filelist_write(h, h->filename);
402     } else {
403 	filelist_save_as_cb(widget, h, call_data);
404     }
405 }
406 
407 static void
filelist_destroy(Widget widget,XtPointer clientdata,XtPointer call_data)408 filelist_destroy(Widget widget, XtPointer clientdata, XtPointer call_data)
409 {
410     struct list_handle *h = clientdata;
411 
412     if (h->filename)
413 	free(h->filename);
414     ptr_unregister(h->shell);
415     free(h);
416 }
417 
418 static void
filelist_list_load(Widget widget,XtPointer clientdata,XtPointer call_data)419 filelist_list_load(Widget widget, XtPointer clientdata, XtPointer call_data)
420 {
421     struct list_handle *h = clientdata;
422 
423     filelist_delall(h);
424     filelist_read(h, XtName(widget));
425 }
426 
filelist_builddir(Widget menu,char * path,XtPointer clientdata)427 static void filelist_builddir(Widget menu, char *path, XtPointer clientdata)
428 {
429     Widget push,submenu;
430     XmString str;
431     char filename[1024];
432     struct dirent *ent;
433     struct stat st;
434     DIR *dir;
435 
436     dir = opendir(path);
437     while (NULL != (ent = readdir(dir))) {
438 	if (ent->d_name[0] == '.')
439 	    continue;
440 	snprintf(filename,sizeof(filename),"%s/%s",
441 		 path,ent->d_name);
442 	if (-1 == lstat(filename,&st))
443 	    continue;
444 
445 	str = XmStringGenerate(ent->d_name,NULL, XmMULTIBYTE_TEXT,NULL);
446 	if (S_ISREG(st.st_mode)) {
447 	    push = XtVaCreateManagedWidget(filename,
448 					   xmPushButtonWidgetClass,menu,
449 					   XmNlabelString,str,
450 					   NULL);
451 	    XtAddCallback(push,XmNactivateCallback,filelist_list_load,clientdata);
452 	}
453 	if (S_ISDIR(st.st_mode)) {
454 	    submenu = XmCreatePulldownMenu(menu,"subdirM",NULL,0);
455 	    XtVaCreateManagedWidget("subdir",xmCascadeButtonWidgetClass,menu,
456 				    XmNlabelString,str,
457 				    XmNsubMenuId,submenu,
458 				    NULL);
459 	    filelist_builddir(submenu,filename,clientdata);
460 	}
461 	XmStringFree(str);
462     }
463     closedir(dir);
464 }
465 
466 static void
filelist_lists(Widget widget,XtPointer clientdata,XtPointer call_data)467 filelist_lists(Widget widget, XtPointer clientdata, XtPointer call_data)
468 {
469     WidgetList children,list;
470     Cardinal nchildren;
471     int i;
472 
473     XtVaGetValues(widget,
474 		  XtNchildren,&children,
475 		  XtNnumChildren,&nchildren,
476 		  NULL);
477     list = malloc(sizeof(Widget*)*nchildren);
478     memcpy(list,children,sizeof(Widget*)*nchildren);
479     for (i = 0; i < nchildren; i++)
480         XtDestroyWidget(list[i]);
481     free(list);
482 
483     filelist_builddir(widget,ida_lists,clientdata);
484 }
485 
486 static void
filelist_action_cb(Widget widget,XtPointer clientdata,XtPointer call_data)487 filelist_action_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
488 {
489     XmContainerSelectCallbackStruct *cd = call_data;
490     char *file;
491 
492     if (XmCR_DEFAULT_ACTION == cd->reason && 1 == cd->selected_item_count) {
493 	file = XtName(cd->selected_items[0]);
494 	if (debug)
495 	    fprintf(stderr,"browser: action %s\n", file);
496 	new_file(file,1);
497     }
498 }
499 
500 /*----------------------------------------------------------------------*/
501 
502 void
filelist_window(void)503 filelist_window(void)
504 {
505     Widget form,clip,menubar,menu,push;
506     struct list_handle *h;
507     Arg args[8];
508     int n = 0;
509 
510     if (0 == ntargets) {
511 	/* first time init */
512 	targets[ntargets++] = MIME_TEXT_URI_LIST;
513 	targets[ntargets++] = XA_FILE_NAME;
514 	targets[ntargets++] = XA_FILE;
515 	targets[ntargets++] = _NETSCAPE_URL;
516     }
517 
518     h = malloc(sizeof(*h));
519     if (NULL == h) {
520 	fprintf(stderr,"out of memory");
521 	return;
522     }
523     memset(h,0,sizeof(*h));
524     INIT_LIST_HEAD(&h->files);
525 
526     h->shell = XtVaAppCreateShell("filelist","Ida",
527 				  topLevelShellWidgetClass,
528 				  dpy,
529 				  XtNclientLeader,app_shell,
530 				  XmNdeleteResponse,XmDESTROY,
531 				  XtNtitle,"new list",
532 				  NULL);
533     XmdRegisterEditres(h->shell);
534     XtAddCallback(h->shell,XtNdestroyCallback,filelist_destroy,h);
535 
536     /* widgets */
537     form = XtVaCreateManagedWidget("form", xmFormWidgetClass, h->shell,
538 				   NULL);
539     menubar = XmCreateMenuBar(form,"cbar",NULL,0);
540     XtManageChild(menubar);
541     h->status = XtVaCreateManagedWidget("status",xmLabelWidgetClass, form,
542 					NULL);
543 
544     /* scrolled container */
545     h->details[0] = XmStringGenerate("Image", NULL, XmMULTIBYTE_TEXT,NULL);
546     h->details[DETAIL_SIZE+1] =
547 	XmStringGenerate("Size", NULL, XmMULTIBYTE_TEXT,NULL);
548     h->details[DETAIL_COMMENT+1] =
549 	XmStringGenerate("Comment", NULL, XmMULTIBYTE_TEXT,NULL);
550     XtSetArg(args[n], XmNdetailColumnHeading, h->details); n++;
551     XtSetArg(args[n], XmNdetailColumnHeadingCount, DETAIL_COUNT+1);  n++;
552 
553     h->scroll = XmCreateScrolledWindow(form, "scroll", NULL, 0);
554     XtManageChild(h->scroll);
555     h->container = XmCreateContainer(h->scroll,"container",
556 				     args,n);
557     XtManageChild(h->container);
558     XdndDropSink(h->container);
559 
560     XtAddCallback(h->scroll, XmNtraverseObscuredCallback,
561 		  container_traverse_cb, NULL);
562     XtAddCallback(h->container,XmNdefaultActionCallback,
563 		  filelist_action_cb,h);
564     XtAddCallback(h->container,XmNconvertCallback,
565 		  container_convert_cb,h);
566     XtAddCallback(h->container,XmNdestinationCallback,
567 		  filelist_dest_cb,h);
568 
569     XtVaGetValues(h->scroll,XmNclipWindow,&clip,NULL);
570     XtAddEventHandler(clip,StructureNotifyMask,True,container_resize_eh,NULL);
571 
572     /* menu - file */
573     menu = XmCreatePulldownMenu(menubar,"fileM",NULL,0);
574     XtVaCreateManagedWidget("file",xmCascadeButtonWidgetClass,menubar,
575 			    XmNsubMenuId,menu,NULL);
576     push = XtVaCreateManagedWidget("new",xmPushButtonWidgetClass,menu,NULL);
577     XtAddCallback(push,XmNactivateCallback,filelist_new_cb,h);
578     push = XtVaCreateManagedWidget("load",xmPushButtonWidgetClass,menu,NULL);
579     XtAddCallback(push,XmNactivateCallback,filelist_load_cb,h);
580     push = XtVaCreateManagedWidget("save",xmPushButtonWidgetClass,menu,NULL);
581     XtAddCallback(push,XmNactivateCallback,filelist_save_cb,h);
582     push = XtVaCreateManagedWidget("saveas",xmPushButtonWidgetClass,menu,NULL);
583     XtAddCallback(push,XmNactivateCallback,filelist_save_as_cb,h);
584 
585     XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
586     push = XtVaCreateManagedWidget("close",xmPushButtonWidgetClass,menu,NULL);
587     XtAddCallback(push,XmNactivateCallback,destroy_cb,h->shell);
588 
589     /* menu - edit */
590     menu = XmCreatePulldownMenu(menubar,"editM",NULL,0);
591     XtVaCreateManagedWidget("edit",xmCascadeButtonWidgetClass,menubar,
592 			    XmNsubMenuId,menu,NULL);
593     container_menu_edit(menu,h->container, 0,1,1,1);
594 
595     /* menu - view */
596     menu = XmCreatePulldownMenu(menubar,"viewM",NULL,0);
597     XtVaCreateManagedWidget("view",xmCascadeButtonWidgetClass,menubar,
598 			    XmNsubMenuId,menu,NULL);
599     container_menu_view(menu,h->container);
600 
601     /* menu - lists */
602     menu = XmCreatePulldownMenu(menubar,"listsM",NULL,0);
603     XtVaCreateManagedWidget("lists",xmCascadeButtonWidgetClass,menubar,
604 			    XmNsubMenuId,menu,NULL);
605     XtAddCallback(menu, XmNmapCallback, filelist_lists, h);
606 
607     /* read dir and show window */
608     container_detail_cb(NULL,h->container,NULL);
609     XtPopup(h->shell,XtGrabNone);
610     ptr_register(h->shell);
611 }
612 
613 void
filelist_ac(Widget widget,XEvent * event,String * params,Cardinal * num_params)614 filelist_ac(Widget widget, XEvent *event,
615 	    String *params, Cardinal *num_params)
616 {
617     filelist_window();
618 }
619