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