Hotlist accessory The hotlist accessory conforms to the W3A specification for user functions. It exports three functions: [[initHotlist]], [[doHotlist]] and [[eventHotlist]]. [[initHotlist]] creates an ID. Since there is only one instance of each accessory (the browser should take care of that), the accessory doesn't need to know its own ID. In conformance to the recommended way for creating a unique ID, the [[initHotlist]] function allocates some space on the heap and returns the pointer as its ID.. [[doHotlist]] has two modes of operation: when it is called with code 1 ([[ADD_CURRENT]]), it adds the current document's URL to the hotlist file; when it is called with code 2 ([[OPEN_DIALOG]]), it opens a dialog box with a list of hotlist entries and some buttons. The box is created if it doesn't exist. [[eventHotlist]] is called by the browser whenever a W3A event occurs. The only event that is useful is event 1 ([[NEW_DOCUMENT]]). By keeping track of these events, the hotlist accessory knows what the `current document' is, when the user asks it to `add the current document to the hotlist'. TO DO: use resources to set the name of the hotlist file. TO DO: use `hierarchical hotlist', with folders and sub-folders. TO DO: display hotlist as an HTML page in the main browser (?) Bert Bos , 18 Oct 1994 29 Jun '95: bugfix: delete_item didn't decrement nritems, effect: the last item in the list was written to file twice. <<*>>= #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "add.xpm" #include "add1.xpm" #include "open.xpm" #include "open1.xpm" #include "delete.xpm" #include "delete1.xpm" #include "hotlist.xpm" #define ADD_CURRENT 1 /* Codes for doHotlist() */ #define OPEN_DIALOG 2 #define HOTLIST_NAME ".argo_hotlist" #define MAGIC "ncsa-xmosaic-hotlist-format-1\nDefault\n" #ifndef PATH_MAX #define PATH_MAX 1024 #endif #define INCR 256 #define max(a, b) ((a) > (b) ? (a) : (b)) typedef struct {char *title, *url, *time;} *ListType; static W3ADocumentInfo *curdoc = NULL; static Widget dialog = NULL, listw = NULL; static ListType list = NULL; static int nritems = 0; static Bool file_has_been_read = FALSE; static char *filename = HOTLIST_NAME; /* hotlistname -- return absolute pathname of hotlist file */ static char *hotlistname(void) { static char s[PATH_MAX]; struct passwd *pw; if (filename[0] == '/') strcpy(s, filename); else if ((pw = getpwnam(getlogin())) || (pw = getpwuid(getuid()))) sprintf(s, "%s/%s", pw->pw_dir, filename); else strcpy(s, filename); return s; } @ The title and URL of the hotlist items are kept in three different places: the hotlist file, the accessory's own in-memory list, and in the List widget. The three are kept synchronized, except that the in-memory list and the List widget are not updated if they have not yet been created. <<*>>= /* add_current -- append URL of current document to the hotlist file */ static Bool add_current(void) { struct stat statbuf; char s[PATH_MAX + 50]; XmString ms; time_t tm; FILE *f; if (curdoc == NULL) return FALSE; tm = time(NULL); /* Add it to the in-memory list if that list exists */ if (file_has_been_read) { renewarray(list, nritems + 1); list[nritems].title = newstring(curdoc->title ? curdoc->title : curdoc->url); list[nritems].url = newstring(curdoc->url); list[nritems].time = newstring(ctime(&tm)); nritems++; } /* Add it to the list widget in the dialog, if it exists */ if (listw) { ms = XmStringCreateLocalized (curdoc->title ? curdoc->title : curdoc->url); XmListAddItem(listw, ms, 0); XmStringFree(ms); } /* Append it to the hotlist file */ if (stat(hotlistname(), &statbuf) != 0) { /* File exists? */ if (! (f = fopen(hotlistname(), "w"))) { /* Create it */ sprintf(s, "Couldn't create %s", hotlistname()); XtAppWarning(XtWidgetToApplicationContext(W3Atoplevel()), s); return FALSE; } fputs(MAGIC, f); } else if (! (f = fopen(hotlistname(), "a"))) { sprintf(s, "Couldn't write %s", hotlistname()); XtAppWarning(XtWidgetToApplicationContext(W3Atoplevel()), s); return FALSE; } fprintf(f, "%s %s%s%s\n", curdoc->url, ctime(&tm), curdoc->title ? "" : "Untitled: ", curdoc->title ? curdoc->title : curdoc->url); fclose(f); return TRUE; } /* read_hotlist -- read the hotlist file into list */ static Bool read_hotlist(void) { char s[PATH_MAX + 50], line[BUFSIZ], url[BUFSIZ], title[BUFSIZ]; char time[BUFSIZ]; FILE *f; int listlen = 0; if (! (f = fopen(hotlistname(), "r")) || ! fgets(line, sizeof(line), f)) { /* File doesn't exist, or empty file (probably...) */ fclose(f); file_has_been_read = TRUE; return TRUE; } if (! eq(line, "ncsa-xmosaic-hotlist-format-1\n") || ! fgets(line, sizeof(line), f) || ! eq(line, "Default\n")) { fclose(f); sprintf(s, "File is not a hotlist: %s", hotlistname()); XtAppWarning(XtWidgetToApplicationContext(W3Atoplevel()), s); return FALSE; } while (fscanf(f, " %s %[^\n] %[^\n]", url, time, title) == 3) { if (listlen != ((nritems + INCR)/INCR) * INCR) { listlen = ((nritems + INCR)/INCR) * INCR; renewarray(list, listlen); } list[nritems].url = newstring(url); list[nritems].title = newstring(title); list[nritems].time = newstring(time); nritems++; } fclose(f); file_has_been_read = TRUE; return TRUE; } /* rewrite_hotlist_file -- write the whole hotlist file anew */ static void rewrite_hotlist_file(void) { char s[PATH_MAX + 50]; FILE *f; int i; if (! (f = fopen(hotlistname(), "w"))) { sprintf(s, "Couldn't write %s", hotlistname()); XtAppWarning(XtWidgetToApplicationContext(W3Atoplevel()), s); return; } fputs(MAGIC, f); for (i = 0; i < nritems; i++) fprintf(f, "%s %s\n%s\n", list[i].url, list[i].time, list[i].title); fclose(f); } /* open_document_cb -- handle a double click on a list item */ static void open_document_cb(Widget w, XtPointer client_data, XtPointer call_data) { W3ADocumentInfo *doc; int *selected, nrselected, item; #if 1 XtPopdown(dialog); #endif if (XmListGetSelectedPos(listw, &selected, &nrselected)) { item = selected[0] - 1; XtFree((char *) selected); doc = new_doc(); doc->url = newstring(list[item].url); doc->title = newstring(list[item].title); (void) W3Aprocess(doc, GET_METHOD, NULL, 0); dispose_doc(doc); } } /* add_cb -- handle a click on the `add current' button */ static void add_cb(Widget w, XtPointer client_data, XtPointer call_data) { (void) add_current(); } /* cancel_cb -- handle a click on the `cancel' button */ static void cancel_cb(Widget w, XtPointer client_data, XtPointer call_data) { #if 1 XtPopdown(dialog); #endif } /* delete_cb -- handle a click on the `delete' button */ static void delete_cb(Widget w, XtPointer client_data, XtPointer call_data) { int *selected, nrselected, i; assert(listw != NULL); if (XmListGetSelectedPos(listw, &selected, &nrselected)) { assert(list != NULL); XmListDeletePos(listw, selected[0]); assert(selected[0] <= nritems); for (i = selected[0]; i < nritems; i++) list[i-1] = list[i]; nritems--; rewrite_hotlist_file(); XtFree((char *) selected); } } /* create_dialog -- create the dialog box, install callbacks */ static Bool create_dialog(void) { Widget formw, addw, gotow, cancelw, deletew; Atom WM_Delete_Window; XmString ms; int i; #if 0 dialog = XtVaCreatePopupShell("hotlist", xmDialogShellWidgetClass, W3Atoplevel(), XtNtitle, "Hotlist", NULL); #else dialog = XtVaCreatePopupShell("hotlist", transientShellWidgetClass, W3Atoplevel(), XtNtitle, "Hotlist", NULL); #endif formw = XtVaCreateManagedWidget ("form", xmFormWidgetClass, dialog, XmNmarginHeight, 10, XmNmarginWidth, 10, XmNhorizontalSpacing, 10, XmNverticalSpacing, 10, NULL); addw = XmCreatePixmapPushButtonGadget (formw, "add current", add, add1, NULL, NULL, 0); XtManageChild(addw); gotow = XmCreatePixmapPushButtonGadget (formw, "open", open, open1, NULL, NULL, 0); XtManageChild(gotow); deletew = XmCreatePixmapPushButtonGadget (formw, "delete", delete, delete1, NULL, NULL, 0); XtManageChild(deletew); #if 0 cancelw = XtVaCreateManagedWidget ("cancel", xmPushButtonGadgetClass, formw, NULL); #else cancelw = XmCreateStandardPushButtonGadget (formw, "cancel", XmPUSHBUTTON_CANCEL, NULL, 0); XtManageChild(cancelw); #endif listw = XmCreateScrolledList(formw, "list", NULL, 0); XtManageChild(listw); XtAddCallback(addw, XmNactivateCallback, add_cb, NULL); XtAddCallback(gotow, XmNactivateCallback, open_document_cb, NULL); XtAddCallback(deletew, XmNactivateCallback, delete_cb, NULL); XtAddCallback(cancelw, XmNactivateCallback, cancel_cb, NULL); XtAddCallback(listw, XmNdefaultActionCallback, open_document_cb, NULL); XtVaSetValues (XtParent(listw), XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_WIDGET, XmNrightWidget, addw, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, NULL); XtVaSetValues (addw, XmNtopAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, NULL); XtVaSetValues (gotow, XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, XmNleftWidget, addw, XmNleftOffset, 0, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, addw, XmNrightAttachment, XmATTACH_FORM, NULL); XtVaSetValues (deletew, XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, XmNleftWidget, gotow, XmNleftOffset, 0, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, gotow, XmNrightAttachment, XmATTACH_FORM, NULL); XtVaSetValues (cancelw, XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, XmNleftWidget, deletew, XmNleftOffset, 0, XmNrightAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, NULL); /* Intercept the WM_DELETE_WINDOW protocol */ XtVaSetValues(dialog, XmNdeleteResponse, XmUNMAP, NULL); XtVaSetValues(formw, XmNcancelButton, cancelw, NULL); for (i = 0; i < nritems; i++) { ms = XmStringCreateLocalized(list[i].title); XmListAddItem(listw, ms, 0); XmStringFree(ms); } return TRUE; } /* open_dialog -- open a dialog and let users select a hotlist entry */ static Bool open_dialog(int mid_x, int mid_y) { int width, height; if (! file_has_been_read && ! read_hotlist()) return FALSE; if (! dialog && ! create_dialog()) return FALSE; XtVaGetValues(dialog, XmNwidth, &width, XmNheight, &height, NULL); XtVaSetValues(dialog, XmNx, max(0, mid_x - width/2), XmNy, max(0, mid_y - height/2), NULL); #if 1 XtPopup(dialog, XtGrabNonexclusive); #else XtManageChild(dialog); #endif return TRUE; } EXPORT Bool initHotlist(long id, char ***labels, ThreeIcons *icons_data[], int *nrlabels) { static char *labs[] = { "Add current to hotlist", "Hotlist"}; #if 0 static char **data[][3] = { /* 3 icons per label */ {add, add1, NULL}, {hotlist, NULL, NULL}}; #else static ThreeIcons data[] = { /* 3 icons per label */ {add, add1, NULL}, {hotlist, NULL, NULL}}; #endif *labels = labs; *icons_data = data; *nrlabels = XtNumber(labs); return True; } EXPORT Bool doHotlist(int code, int mid_x, int mid_y) { switch (code) { case ADD_CURRENT: return add_current(); case OPEN_DIALOG: return open_dialog(mid_x, mid_y); default: assert(! "Cannot happen"); } } EXPORT void eventHotlist(long sourceid, long eventtype, void *params) { if (eventtype == NEW_DOCUMENT) { if (curdoc) dispose_doc(curdoc); curdoc = new_doc(); copy_doc(curdoc, *((W3ADocumentInfo *) params)); } }