1 /*
2  * simple file browser
3  * (c) 2001-03 Gerd Hoffmann <gerd@kraxel.org>
4  *
5  */
6 
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <dirent.h>
11 #include <errno.h>
12 #include <string.h>
13 #include <fnmatch.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 
17 #include <X11/Xlib.h>
18 #include <X11/Intrinsic.h>
19 #include <X11/extensions/XShm.h>
20 #include <Xm/Xm.h>
21 #include <Xm/Form.h>
22 #include <Xm/Label.h>
23 #include <Xm/RowColumn.h>
24 #include <Xm/PushB.h>
25 #include <Xm/CascadeB.h>
26 #include <Xm/ScrolledW.h>
27 #include <Xm/SelectioB.h>
28 #include <Xm/Transfer.h>
29 #include <Xm/TransferP.h>
30 #include <Xm/Container.h>
31 #include <Xm/Separator.h>
32 
33 #include "RegEdit.h"
34 #include "ida.h"
35 #include "readers.h"
36 #include "viewer.h"
37 #include "browser.h"
38 #include "filter.h"
39 #include "x11.h"
40 #include "dither.h"
41 #include "selections.h"
42 #include "filebutton.h"
43 #include "misc.h"
44 #include "idaconfig.h"
45 #include "desktop.h"
46 
47 /*----------------------------------------------------------------------*/
48 
49 struct browser_handle;
50 
51 struct browser_handle {
52     char                 *dirname;
53     char                 *lastdir;
54     char                 *filter;
55     Widget               shell;
56     Widget               scroll;
57     Widget               container;
58     Widget               status;
59     XmString             details[DETAIL_COUNT+1];
60 
61     struct list_head     files;
62     struct list_head     *item;
63     unsigned int         dirs,sfiles,afiles;
64 
65     XtWorkProcId         wproc;
66 };
67 
68 /*----------------------------------------------------------------------*/
69 
dir_info(struct file_button * file)70 static void dir_info(struct file_button *file)
71 {
72     char path[256];
73     char comment[256];
74     int len;
75 
76     snprintf(path,sizeof(path),"%s/.directory",file->filename);
77     len = desktop_read_entry(path, "Comment=", comment, sizeof(comment));
78 
79     if (len) {
80 	XmStringFree(file->details[DETAIL_COMMENT]);
81 	file->details[DETAIL_COMMENT] =
82 	    XmStringGenerate(comment, NULL, XmMULTIBYTE_TEXT,NULL);
83 	XtVaSetValues(file->widget,
84 		      XmNdetail, file->details,
85 		      XmNdetailCount, DETAIL_COUNT,
86 		      NULL);
87     }
88 }
89 
90 static Boolean
browser_statfiles(XtPointer clientdata)91 browser_statfiles(XtPointer clientdata)
92 {
93     struct browser_handle *h = clientdata;
94     struct file_button *file;
95     struct list_head *item;
96     char line[80];
97     XmString str;
98     Pixmap pix;
99     char *type;
100 
101     if (h->item == &h->files) {
102 	/* done => read thumbnails now */
103 	h->wproc = 0;
104 	list_for_each(item,&h->files) {
105 	    file = list_entry(item, struct file_button, window);
106 	    if ((file->st.st_mode & S_IFMT) != S_IFREG)
107 		continue;
108 	    fileinfo_queue(file);
109 	}
110 
111 	list_for_each(item,&h->files) {
112 	    file = list_entry(item, struct file_button, window);
113 	    if ((file->st.st_mode & S_IFMT) != S_IFDIR)
114 		continue;
115 	    dir_info(file);
116 	}
117 
118 	/* update status line */
119 	if (h->filter) {
120 	    snprintf(line, sizeof(line), "%u dirs, %u/%u files [%s]",
121 		     h->dirs,h->sfiles,h->afiles,h->filter);
122 	} else {
123 	    snprintf(line, sizeof(line), "%u dirs, %u files",
124 		     h->dirs,h->afiles);
125 	}
126 	str = XmStringGenerate(line, NULL, XmMULTIBYTE_TEXT, NULL);
127 	XtVaSetValues(h->status,XmNlabelString,str,NULL);
128 	XmStringFree(str);
129 	h->item = NULL;
130 	return TRUE;
131     }
132 
133     /* handle file */
134     file = list_entry(h->item, struct file_button, window);
135     switch (file->st.st_mode & S_IFMT) {
136     case S_IFDIR:
137 	type = "dir";
138 	break;
139     case S_IFREG:
140 	type = "file";
141 	break;
142     default:
143 	type = NULL;
144     }
145     if (type) {
146 	pix = XmGetPixmap(XtScreen(h->container),type,0,0);
147 	file_set_icon(file,pix,pix);
148     }
149 
150     h->item = h->item->next;
151     return FALSE;
152 }
153 
list_add_sorted(struct browser_handle * h,struct file_button * add)154 static void list_add_sorted(struct browser_handle *h, struct file_button *add)
155 {
156     struct file_button *file;
157     struct list_head *item;
158 
159     list_for_each(item,&h->files) {
160 	file = list_entry(item, struct file_button, window);
161 	if (file_cmp_alpha(add,file) <= 0) {
162 	    list_add_tail(&add->window,&file->window);
163 	    return;
164 	}
165     }
166     list_add_tail(&add->window,&h->files);
167 }
168 
browser_readdir(XtPointer clientdata)169 static Boolean browser_readdir(XtPointer clientdata)
170 {
171     struct browser_handle *h = clientdata;
172     struct file_button *file;
173     struct list_head *item;
174     struct dirent *dirent;
175     WidgetList children;
176     struct file_button *lastdir = NULL;
177     Cardinal nchildren;
178     XmString str,elem;
179     DIR *dir;
180     unsigned int len;
181 
182     /* status line */
183     str  = XmStringGenerate("scanning ", NULL, XmMULTIBYTE_TEXT, NULL);
184     elem = XmStringGenerate(h->dirname, NULL, XmMULTIBYTE_TEXT, NULL);
185     str  = XmStringConcatAndFree(str,elem);
186     elem = XmStringGenerate(" ...", NULL, XmMULTIBYTE_TEXT, NULL);
187     str  = XmStringConcatAndFree(str,elem);
188     XtVaSetValues(h->status,XmNlabelString,str,NULL);
189     XmStringFree(str);
190     ptr_busy();
191 
192     /* read + sort dir */
193     dir = opendir(h->dirname);
194     if (NULL == dir) {
195 	fprintf(stderr,"opendir %s: %s\n",h->dirname,strerror(errno));
196 	return -1;
197     }
198     h->dirs = 0;
199     h->sfiles = 0;
200     h->afiles = 0;
201     while (NULL != (dirent = readdir(dir))) {
202 	/* skip dotfiles */
203 	if (dirent->d_name[0] == '.' && 0 != strcmp(dirent->d_name,".."))
204 	    continue;
205 
206 	/* get memory */
207 	file = malloc(sizeof(*file));
208 	memset(file,0,sizeof(*file));
209 
210 	/* get file type */
211 	file->basename = strdup(dirent->d_name);
212 	file->d_type   = dirent->d_type;
213 	if (0 == strcmp(dirent->d_name, "..")) {
214 	    char *slash;
215 	    file->filename = strdup(h->dirname);
216 	    slash = strrchr(file->filename,'/');
217 	    if (slash == file->filename)
218 		slash++;
219 	    *slash = 0;
220 	} else {
221 	    len = strlen(h->dirname)+strlen(file->basename)+4;
222 	    file->filename = malloc(len);
223 	    if (0 == strcmp(h->dirname,"/")) {
224 		sprintf(file->filename,"/%s",file->basename);
225 	    } else {
226 		sprintf(file->filename,"%s/%s",h->dirname,
227 			file->basename);
228 	    }
229 	}
230 	if (file->d_type != DT_UNKNOWN) {
231 	    file->st.st_mode = DTTOIF(file->d_type);
232 	} else {
233 	    if (-1 == stat(file->filename, &file->st)) {
234 		fprintf(stderr,"stat %s: %s\n",
235 			file->filename,strerror(errno));
236 	    }
237 	}
238 
239 	/* user-specified filter */
240 	if (S_ISDIR(file->st.st_mode)) {
241 	    h->dirs++;
242 	    if (h->lastdir && 0 == strcmp(h->lastdir,file->filename)) {
243 		lastdir = file;
244 	    }
245 	} else {
246 	    h->afiles++;
247 	    if (h->filter && 0 != fnmatch(h->filter,dirent->d_name,0)) {
248 		free(file);
249 		continue;
250 	    } else
251 		h->sfiles++;
252 	}
253 
254 	list_add_sorted(h,file);
255     }
256     closedir(dir);
257 
258     /* create & manage widgets */
259     list_for_each(item,&h->files) {
260 	file = list_entry(item, struct file_button, window);
261 	file_createwidgets(h->container, file);
262     }
263     nchildren = XmContainerGetItemChildren(h->container,NULL,&children);
264     XtManageChildren(children,nchildren);
265     if (nchildren)
266 	XtFree((XtPointer)children);
267     container_relayout(h->container);
268     if (h->lastdir) {
269 	if (lastdir) {
270 	    if (debug)
271 		fprintf(stderr,"lastdir: %s\n",h->lastdir);
272 	    XtVaSetValues(h->container,
273 			  XmNinitialFocus, lastdir->widget,
274 			  NULL);
275 // 	    XmScrollVisible(h->scroll, lastdir->widget, 25, 25);
276 //	    XtSetKeyboardFocus(h->shell,h->container);
277 	}
278 	free(h->lastdir);
279 	h->lastdir = NULL;
280     }
281     XtVaSetValues(h->shell,XtNtitle,h->dirname,NULL);
282     ptr_idle();
283 
284     /* start bg processing */
285     h->item  = h->files.next;
286     h->wproc = XtAppAddWorkProc(app_context,browser_statfiles,h);
287     return TRUE;
288 }
289 
browser_bgcancel(struct browser_handle * h)290 static void browser_bgcancel(struct browser_handle *h)
291 {
292     if (h->wproc)
293 	XtRemoveWorkProc(h->wproc);
294     h->wproc = 0;
295 }
296 
297 /*----------------------------------------------------------------------*/
298 
299 static void
browser_cd(struct browser_handle * h,char * dir)300 browser_cd(struct browser_handle *h, char *dir)
301 {
302     /* build new dir path */
303     if (h->lastdir)
304 	free(h->lastdir);
305     h->lastdir = h->dirname;
306     h->dirname = strdup(dir);
307 
308     /* cleanup old stuff + read dir */
309     browser_bgcancel(h);
310     container_delwidgets(h->container);
311     h->wproc = XtAppAddWorkProc(app_context,browser_readdir,h);
312 }
313 
314 static void
browser_filter_done(Widget widget,XtPointer clientdata,XtPointer call_data)315 browser_filter_done(Widget widget, XtPointer clientdata, XtPointer call_data)
316 {
317     struct browser_handle *h = clientdata;
318     XmSelectionBoxCallbackStruct *cb = call_data;
319     char *filter;
320 
321     if (cb->reason == XmCR_OK) {
322 	filter = XmStringUnparse(cb->value,NULL,
323 				 XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
324 				 NULL,0,0);
325 	if (h->filter)
326 	    free(h->filter);
327 	h->filter = NULL;
328 	if (strlen(filter) > 0)
329 	    h->filter = strdup(filter);
330 	XtFree(filter);
331 
332 	if (debug)
333 	    fprintf(stderr,"filter: %s\n", h->filter ? h->filter : "[none]");
334 	browser_bgcancel(h);
335 	container_delwidgets(h->container);
336 	h->wproc = XtAppAddWorkProc(app_context,browser_readdir,h);
337     }
338     XtDestroyWidget(widget);
339 }
340 
341 static void
browser_filter(Widget widget,XtPointer clientdata,XtPointer call_data)342 browser_filter(Widget widget, XtPointer clientdata, XtPointer call_data)
343 {
344     struct browser_handle *h = clientdata;
345     Widget shell;
346 
347     shell = XmCreatePromptDialog(h->shell,"filter",NULL,0);
348     XtUnmanageChild(XmSelectionBoxGetChild(shell,XmDIALOG_HELP_BUTTON));
349     XtAddCallback(shell,XmNokCallback,browser_filter_done,h);
350     XtAddCallback(shell,XmNcancelCallback,browser_filter_done,h);
351     XtManageChild(shell);
352 }
353 
354 static void
browser_nofilter(Widget widget,XtPointer clientdata,XtPointer call_data)355 browser_nofilter(Widget widget, XtPointer clientdata, XtPointer call_data)
356 {
357     struct browser_handle *h = clientdata;
358 
359     if (!h->filter)
360 	return;
361     if (debug)
362 	fprintf(stderr,"filter: reset\n");
363     free(h->filter);
364     h->filter = NULL;
365 
366     browser_bgcancel(h);
367     container_delwidgets(h->container);
368     h->wproc = XtAppAddWorkProc(app_context,browser_readdir,h);
369 }
370 
371 /*----------------------------------------------------------------------*/
372 
373 static void
browser_action_cb(Widget widget,XtPointer clientdata,XtPointer call_data)374 browser_action_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
375 {
376     XmContainerSelectCallbackStruct *cd = call_data;
377     struct browser_handle *h = clientdata;
378     struct stat st;
379     char *file;
380 
381     if (XmCR_DEFAULT_ACTION == cd->reason && 1 == cd->selected_item_count) {
382 	file = XtName(cd->selected_items[0]);
383 	if (debug)
384 	    fprintf(stderr,"browser: action %s\n", file);
385 	if (-1 == stat(file,&st)) {
386 	    fprintf(stderr,"stat %s: %s\n",file,strerror(errno));
387 	    return;
388 	}
389 	if (S_ISDIR(st.st_mode))
390 	    browser_cd(h,file);
391 	if (S_ISREG(st.st_mode))
392 	    new_file(file,1);
393     }
394 }
395 
396 static void
browser_bookmark_cb(Widget widget,XtPointer clientdata,XtPointer call_data)397 browser_bookmark_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
398 {
399     struct browser_handle *h = clientdata;
400     browser_cd(h, cfg_get_str(O_BOOKMARKS, XtName(widget)));
401 }
402 
403 /*----------------------------------------------------------------------*/
404 
405 static void
browser_destroy(Widget widget,XtPointer clientdata,XtPointer call_data)406 browser_destroy(Widget widget, XtPointer clientdata, XtPointer call_data)
407 {
408     struct browser_handle *h = clientdata;
409 
410     if (debug)
411 	fprintf(stderr,"browser: destroy\n");
412     browser_bgcancel(h);
413     ptr_unregister(h->shell);
414     free(h);
415 }
416 
417 static char*
browser_fixpath(char * dir)418 browser_fixpath(char *dir)
419 {
420     char path[1024];
421     char *s,*d;
422 
423     memset(path,0,sizeof(path));
424     if (dir[0] == '/') {
425 	/* absolute */
426 	strncpy(path,dir,sizeof(path)-1);
427     } else {
428 	/* relative */
429 	getcwd(path,sizeof(path)-1);
430 	if (strlen(path)+strlen(dir)+4 < sizeof(path)) {
431 	    strcat(path,"/");
432 	    strcat(path,dir);
433 	}
434     }
435 
436     for (s = d = path; *s != 0;) {
437 	if (0 == strncmp(s,"//",2)) {
438 	    s++;
439 	    continue;
440 	}
441 	if (0 == strncmp(s,"/./",3)) {
442 	    s+=2;
443 	    continue;
444 	}
445 	if (0 == strcmp(s,"/"))
446 	    s++;
447 	if (0 == strcmp(s,"/."))
448 	    s+=2;
449 	*d = *s;
450 	s++, d++;
451     }
452     return strdup(path);
453 }
454 
455 void
browser_window(char * dirname)456 browser_window(char *dirname)
457 {
458     Widget form,menubar,menu,push,clip;
459     struct browser_handle *h;
460     Arg args[8];
461     char *list;
462     int n = 0;
463 
464     h = malloc(sizeof(*h));
465     if (NULL == h) {
466 	fprintf(stderr,"out of memory");
467 	return;
468     }
469     memset(h,0,sizeof(*h));
470     INIT_LIST_HEAD(&h->files);
471     h->dirname = browser_fixpath(dirname);
472 
473     h->shell = XtVaAppCreateShell("browser","Ida",
474 				  topLevelShellWidgetClass,
475 				  dpy,
476 				  XtNclientLeader,app_shell,
477 				  XmNdeleteResponse,XmDESTROY,
478 				  NULL);
479     XmdRegisterEditres(h->shell);
480     XtAddCallback(h->shell,XtNdestroyCallback,browser_destroy,h);
481 
482     /* widgets */
483     form = XtVaCreateManagedWidget("form", xmFormWidgetClass, h->shell,
484 				   NULL);
485     menubar = XmCreateMenuBar(form,"cbar",NULL,0);
486     XtManageChild(menubar);
487     h->status = XtVaCreateManagedWidget("status",xmLabelWidgetClass, form,
488 					NULL);
489 
490     /* scrolled container */
491     h->details[0] = XmStringGenerate("Image", NULL, XmMULTIBYTE_TEXT,NULL);
492     h->details[DETAIL_SIZE+1] =
493 	XmStringGenerate("Size", NULL, XmMULTIBYTE_TEXT,NULL);
494     h->details[DETAIL_COMMENT+1] =
495 	XmStringGenerate("Comment", NULL, XmMULTIBYTE_TEXT,NULL);
496     XtSetArg(args[n], XmNdetailColumnHeading, h->details); n++;
497     XtSetArg(args[n], XmNdetailColumnHeadingCount, DETAIL_COUNT+1);  n++;
498 
499     h->scroll = XmCreateScrolledWindow(form, "scroll", NULL, 0);
500     XtManageChild(h->scroll);
501     h->container = XmCreateContainer(h->scroll,"container",
502 				     args,n);
503     XtManageChild(h->container);
504     XtAddCallback(h->container,XmNdefaultActionCallback,
505 		  browser_action_cb,h);
506 
507     XtAddCallback(h->scroll, XmNtraverseObscuredCallback,
508 		  container_traverse_cb, NULL);
509     XtAddCallback(h->container,XmNconvertCallback,
510 		  container_convert_cb,h);
511 
512     XtVaGetValues(h->scroll,XmNclipWindow,&clip,NULL);
513     XtAddEventHandler(clip,StructureNotifyMask,True,container_resize_eh,NULL);
514 
515     /* menu - file */
516     menu = XmCreatePulldownMenu(menubar,"fileM",NULL,0);
517     XtVaCreateManagedWidget("file",xmCascadeButtonWidgetClass,menubar,
518 			    XmNsubMenuId,menu,NULL);
519     push = XtVaCreateManagedWidget("close",xmPushButtonWidgetClass,menu,NULL);
520     XtAddCallback(push,XmNactivateCallback,destroy_cb,h->shell);
521 
522     /* menu - edit */
523     menu = XmCreatePulldownMenu(menubar,"editM",NULL,0);
524     XtVaCreateManagedWidget("edit",xmCascadeButtonWidgetClass,menubar,
525 			    XmNsubMenuId,menu,NULL);
526     container_menu_edit(menu,h->container, 0,1,0,0);
527 
528     /* menu - view */
529     menu = XmCreatePulldownMenu(menubar,"viewM",NULL,0);
530     XtVaCreateManagedWidget("view",xmCascadeButtonWidgetClass,menubar,
531 			    XmNsubMenuId,menu,NULL);
532     container_menu_view(menu,h->container);
533     XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
534     push = XtVaCreateManagedWidget("filter",xmPushButtonWidgetClass,menu,NULL);
535     XtAddCallback(push,XmNactivateCallback,browser_filter,h);
536     push = XtVaCreateManagedWidget("freset",xmPushButtonWidgetClass,menu,NULL);
537     XtAddCallback(push,XmNactivateCallback,browser_nofilter,h);
538 
539     /* menu - ops */
540     menu = XmCreatePulldownMenu(menubar,"opsM",NULL,0);
541     XtVaCreateManagedWidget("ops",xmCascadeButtonWidgetClass,menubar,
542 			    XmNsubMenuId,menu,NULL);
543     container_menu_ops(menu,h->container);
544 
545     /* menu - dirs (bookmarks) */
546     menu = XmCreatePulldownMenu(menubar,"dirsM",NULL,0);
547     XtVaCreateManagedWidget("dirs",xmCascadeButtonWidgetClass,menubar,
548 			    XmNsubMenuId,menu,NULL);
549     for (list  = cfg_entries_first(O_BOOKMARKS);
550 	 list != NULL;
551 	 list  = cfg_entries_next(O_BOOKMARKS,list)) {
552 	push = XtVaCreateManagedWidget(list,xmPushButtonWidgetClass,menu,NULL);
553 	XtAddCallback(push,XmNactivateCallback,browser_bookmark_cb,h);
554     }
555 
556     /* read dir and show window */
557     container_spatial_cb(NULL,h->container,NULL);
558     browser_readdir(h);
559     XtPopup(h->shell,XtGrabNone);
560     ptr_register(h->shell);
561 }
562 
563 void
browser_ac(Widget widget,XEvent * event,String * params,Cardinal * num_params)564 browser_ac(Widget widget, XEvent *event,
565 	   String *params, Cardinal *num_params)
566 {
567     if (*num_params > 0)
568 	browser_window(params[0]);
569     else
570 	browser_window(".");
571 }
572