/* File: gex.c * Author: Simon Kelley (srk@sanger.ac.uk) * Copyright (C) J Thierry-Mieg and R Durbin, 1991 * ------------------------------------------------------------------- * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * ------------------------------------------------------------------- * This file is part of the ACEDB genome database package, written by * Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and * Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr * * Description: * Functions for the integration of GUI code using the * graph package with GUI code using Gtk * Depends on the Gtk back-end for the graph package * Exported functions: gexGraphHbox * gexGraphVbox * gexSignalConnect * gexDestroy * gexGraph2Widget * gexWindowCreate * gexCleanup * * HISTORY: * Last edited: Sep 13 11:57 2001 (edgrif) * CVS info: $Id: gex.c,v 1.54 2002/02/22 18:13:32 srk Exp $ *------------------------------------------------------------------- */ #ifdef __CYGWIN__ #include #include #include #define COBJMACROS #include typedef POINT WIN_PT ; #define POINT acePOINT #undef TRANSPARENT #endif /* CYGWIN */ #include #include #include #include #include #include #include #include #include #include static void intProc (int sig); static FILE *gexQueryOpen (char *dname, char *fname, char *end, char *spec, char *title); static BOOL gexQuery (char *text); static void gexOut (char *mesg_buf, void *user_pointer); static void gexExit (char *mesg_buf, void *user_pointer); static void showMesg(char *mesg_buf, void *user_pointer, BOOL exit); static ACEIN gexPrompt (char *prompt, char *dfault, char *fmt, STORE_HANDLE handle); static BOOL modalDialog (char *text, char *buttonLabel, BOOL query, BOOL msgopt) ; /* default locations to store persistent information for the file chooser */ static char defaultDir[DIR_BUFFER_SIZE], defaultFile[FIL_BUFFER_SIZE]; /* Ugghhh, static for message popup vs. list stuff, I think there should be */ /* a gex context to hold info. like this. */ static BOOL msg_use_list_G = FALSE ; /* message list window or popup ? */ static int msg_list_length_G = 0 ; /* length of message list. */ static MessListCB msg_prefcb_G = NULL ; /* callback to handle switch to message window. */ /************************************************************/ void gexInit (int *argcptr, char **argv) { char *custom_font = NULL ; uid_t savuid = geteuid(); /* preserve effective uid */ GraphDevFunc functable; int i; *defaultDir = 0; *defaultFile = 0; seteuid(getuid()); /* set the effective uid to be the real uid here, so that X access control can read the .Xauthority file of the user. Note that this executes before the user id munging in session.c, we restore the status quo ante so as not to upset that code. */ gtk_init(argcptr, &argv); seteuid(savuid); /* restore */ signal (SIGINT, intProc) ; for(i=1;i<*argcptr;i++) { if (!strcmp(argv[i],"-acefont")) { if ((*argcptr - i) < 2) { /* no argument to -acefont option */ messExit ("No argument specified for -acefont option"); } custom_font = strnew(argv[i+1], 0); /* clear both arguments from the list */ for(i+=2;i<*argcptr;i++) argv[i-2] = argv[i]; argv[*argcptr-2] = 0; (*argcptr) -= 2; break; } } functable = (GraphDevFunc)messalloc(sizeof(GraphDevFuncRec)); graphgdk_switch_init(functable); graphgdkremote_switch_init(functable); graphgtk_switch_init(functable); /* tell the graph package that we exist */ graphCBSetFuncTable(functable); graphGdkFontInit(custom_font); graphGdkGcInit(); graphGdkColorInit(); graphGdkRemoteInit(); graphGtkInit(); gexSetMessCB() ; /* internal slim-line browser as default help-viewer */ { struct helpContextStruct helpContext = { gexHtmlViewer, NULL }; helpOnRegister (helpContext); } /* register graphical filechooser */ filQueryOpenRegister (gexQueryOpen) ; return; } /* gexInit */ void gexSetConfigFiles(char *rcFile, char *fontFile) { graphGdkSetConfigFiles(rcFile, fontFile); } /* Set messout/messerror callbacks to output messages as popup dialogs OR */ /* as a message list depending on user preferences. */ /* MUST be called BEFORE gexSetMessPopUps(). */ void gexSetMessPrefs(BOOL use_msg_list, int msg_list_length, MessListCB prefcb) { msg_use_list_G = use_msg_list ; msg_list_length_G = msg_list_length ; msg_prefcb_G = prefcb ; gexSetMessPopUps(msg_use_list_G, msg_list_length_G) ; return ; } /* Called by gexSetMessPrefs when preferences for messaging are first set up,*/ /* after that its a called from acedb code which controls the switching of */ /* message styles. */ void gexSetMessPopUps(BOOL msg_list, int list_length) { /* These gex routines manage the flip-flopping between the popup and list */ /* messages, this is important because the list code expects the caller */ /* to manage this bit. */ if (msg_list) { struct messContextStruct graphContext = {mesgListAdd, NULL} ; /* ok, need to init system, set up contexts etc. */ mesgListCreate() ; msg_use_list_G = msg_list ; /* must be before gexSetMessListSize() */ msg_list_length_G = list_length ; gexSetMessListSize(msg_list_length_G) ; /* use the graphical dialog to output any messages */ messOutRegister(graphContext) ; messErrorRegister(graphContext) ; } else { /* use the graphical dialog to output any messages */ struct messContextStruct gexContext = { gexOut, NULL }; /* If there is a message list then destroy it. */ if (msg_use_list_G) { mesgListDestroy() ; } msg_use_list_G = msg_list ; /* must be after mesgListDestroy() */ messOutRegister (gexContext); messErrorRegister (gexContext); } return ; } /* Called by gexSetMessPrefs when preferences for messaging are first set up,*/ /* after that its a callback from prefsubs.c for when user sets message */ /* list size. */ /* */ void gexSetMessListSize(int list_length) { if (msg_use_list_G) { mesgListSetSize(list_length) ; } return ; } /* Cover function for graph level routine, called by prefsubs during */ /* initialisation. */ int gexGetDefListSize(int curr_value) { return (mesgListGetDefaultListSize(curr_value)) ; } /* Cover function for graph level routine called by preference code to check */ /* int value for preference. */ BOOL gexCheckListSize(int curr_value) { return (mesgListCheckListSize(curr_value)) ; } /* Allows an application using gex to set/reset the messubs callbacks, it */ /* may need to reset them after temporarily altering them for some reason. */ /* */ void gexSetMessCB(void) { struct messContextStruct outContext = { gexOut, NULL }; struct messContextStruct exitContext = { gexExit, NULL }; /* use the graphical dialog to output any messages */ messOutRegister (outContext); messErrorRegister (outContext); messExitRegister (exitContext); messCrashRegister (exitContext); messQueryRegister (gexQuery); messPromptRegister (gexPrompt); return ; } /***************************************************************************/ /* Signal handling - we cannot call gtk from a signal handler, so we set an idle loop function to do it. For the case of a tight loop, where the idle function never gets called, or where the user is lazy, an second signal will exit without attempting to get further confirmation. */ static BOOL awaitingQuery = FALSE; static BOOL queryShown = FALSE; static gint abortP(gpointer data) { queryShown = TRUE; if (messQuery ("Do you want to abort?")) messExit ("User initiated abort") ; queryShown = awaitingQuery = FALSE; return FALSE; } static void intProc (int sig) { if (queryShown) return; if (awaitingQuery) { struct messContextStruct nullExitContext = { NULL, NULL }; messExitRegister (nullExitContext); messExit ("User initiated abort") ; } awaitingQuery = TRUE; gtk_idle_add(abortP, NULL); } /***********************************************************************/ int gexMainLoop(GtkWidget *win) { return graphGtkMainLoop(win); } void gexLoopReturn(int retval) { graphGtkLoopReturn(retval); } /* Used to detect whether user selected to switch to popup informational/ */ /* error messages. */ /* */ static gint msgWindowSig(GtkWidget *w, gpointer data) { BOOL *msg_state = (BOOL *)data ; *msg_state = !(*msg_state) ; return FALSE ; } gint gexLoopReturnSig(GtkWidget *w, gpointer data) { graphGtkLoopReturn((BOOL)data); return TRUE; } gint gexLoopReturnEvent(GtkWidget *w, GdkEventAny *event, gpointer data) { graphGtkLoopReturn((BOOL)data); return TRUE; } void gexSetModalWin(GtkWidget *win, BOOL modal) { graphGtkSetModalWin(win, modal); } GtkWidget *gexGraphHbox(Graph graph) { GraphDev dev = graphCBGraph2Dev(graph); if (dev) dev->hasChrome = TRUE; /* change TAB key function to gtk widget select */ return dev ? dev->hbox : NULL; } GtkWidget *gexGraphVbox(Graph graph) { GraphDev dev = graphCBGraph2Dev(graph); if (dev) dev->hasChrome = TRUE; /* change TAB key function to gtk widget select */ return dev ? dev->vbox : NULL; } static gint activateGraph(GtkWidget *widget, gpointer data) { graphActivate(((Graph) data)); return FALSE; /* So that the real signal handler gets called too */ } /* arrange that when signal is emitted in widget, the active graph is set */ void gexSignalConnect(Graph graph, GtkObject *widget, gchar *signal, GtkSignalFunc func, gpointer data) { gtk_signal_connect(widget, signal, (GtkSignalFunc) activateGraph, (gpointer) graph); gtk_signal_connect(widget, signal, func, data); } GtkWidget *gexGraph2Widget(Graph g) { GraphDev dev = graphCBGraph2Dev(g); if (!dev) return NULL; else if (dev->topWindow) return dev->topWindow; else if (dev->scroll) return dev->scroll; else return dev->drawingArea; } /* does the same as gtk_window_new(GTK_WINDOW_TOPLEVEL) but registers the window so that busy cursors and graphCleanup() work. Also registers a call-back on the destroy signal which de-registers */ static gint deregister(GtkObject *widget, gpointer data) { GraphDev dev = (GraphDev) data; if (dev->destroyFunc) dev->destroyFunc(GTK_WIDGET(widget)); graphGtkWinRemove(dev); graphGtkOnModalWinDestroy(dev->topWindow); /* in case is has a grab */ messfree(dev); return TRUE; } static void gexConnectWindow(GtkWidget *window) { GraphDev dev = (GraphDev) messalloc (sizeof(GraphDevStruct)) ; graphGtkSetSplash(0); /* kill any splash screen */ graphGtkWinInsert(dev); /* for gexDestroyRegister */ gtk_object_set_data(GTK_OBJECT(window), "_dev_struct", (gpointer)dev); dev->topWindow = window; dev->graph = NULL; dev->destroyFunc = NULL; gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(deregister), (gpointer) dev); } GtkWidget *gexWindowCreate(void) { GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gexConnectWindow(window); return window; } void gexDestroyRegister(GtkWidget *window, void (*func)(GtkWidget *)) { /* Simple call-back register, replaces graphRegister(DESTROY) for simle cases where people are allergic to the gtk signal system */ GraphDev dev = (GraphDev)gtk_object_get_data(GTK_OBJECT(window), "_dev_struct"); if (dev) dev->destroyFunc = func; } void gexCleanup(GtkWidget *save) /* Like graphcleanup, except that the one window not destroyed is the the GtkWindow specified in the "save" argument. NOTE: this cleans up both graph windows and gtk windows created using gexWindowCreate. */ { graphGtkWinCleanup(save); } /* Put up a splash screen. Note that any graphPackage or Gex call which creates as window will kill the splash screen. This is important to avoid obscuring dialog boxes, and is assumed by the main() code which doesn't explicitly remove the splash screen. */ void gexSplash(char **splash_xpm) { GtkWidget *window = NULL; GdkBitmap *mask; GdkPixmap *pixmap; GtkWidget *pixmapwidg; window= gtk_window_new(GTK_WINDOW_POPUP); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_widget_realize(window); pixmap = gdk_pixmap_create_from_xpm_d(window->window, &mask, NULL, (gchar **)splash_xpm); pixmapwidg = gtk_pixmap_new( pixmap, mask ); gdk_pixmap_unref(pixmap); gdk_pixmap_unref(mask); gtk_container_add(GTK_CONTAINER(window), pixmapwidg); gtk_widget_show(pixmapwidg); gtk_widget_show_now(window); while(g_main_iteration(FALSE)); graphGtkSetSplash(window); } /* Set the default icon for windows created by gex or graph */ void gexSetIconFromXPM(char *xpm) { graphGtkSetIcon(xpm); } /**************************************************************************/ /* File chooser, on unix we use the standard GTK one, but extend it a bit */ /* On windows we use native controls (God help us.) */ /**************************************************************************/ #ifdef __CYGWIN__ static FILE *gexQueryOpen(char *dname, char *fname, char *end, char *spec, char *title) { char selectedPath[MAX_PATH]; char winPath[MAX_PATH]; char *dirName = dname ? dname : defaultDir; char *fileName = fname ? fname : defaultFile; char path[FIL_BUFFER_SIZE+DIR_BUFFER_SIZE]; graphBusyCursorAll(); /* while creating */ graphGtkSetSplash(0); /* kill any splashscreen */ if (spec[1] == 'd') { BROWSEINFO bi; LPITEMIDLIST pidlBrowse; LPMALLOC shell_malloc; (void)SHGetMalloc(&shell_malloc); bi.hwndOwner = NULL; bi.pidlRoot = NULL; bi.pszDisplayName = winPath; bi.lpszTitle = title; bi.ulFlags = BIF_RETURNONLYFSDIRS; bi.lpfn = NULL; pidlBrowse = SHBrowseForFolder(&bi); if (!pidlBrowse) return NULL; if (!SHGetPathFromIDList(pidlBrowse, selectedPath)) { IMalloc_Free(shell_malloc, pidlBrowse); return NULL; } IMalloc_Free(shell_malloc, pidlBrowse); cygwin_conv_to_full_posix_path(selectedPath, dirName); if (!filCheckName(dirName, 0, spec)) { messout("Cannot access %s", fname); return NULL; } /* It doesn't make sense to return a FILE * for directories as these are not permitted in general (and break under cygwin) We return 0x1 to indicte success, filclose will notice this and ignore it. Note that all of this is a temporary kludge, Fred will upgrade all of this as part of the aceIn/Out effort */ return (FILE *) 0x1; } else { OPENFILENAME ofn; DWORD flags = OFN_EXTENSIONDIFFERENT | OFN_LONGNAMES | OFN_HIDEREADONLY; unsigned char *cp; char types_buff[400]; /* So sue me */ if (spec[0] == 'w') flags |= OFN_OVERWRITEPROMPT; else flags |= OFN_FILEMUSTEXIST; strcpy(selectedPath, fileName); cygwin_conv_to_full_win32_path(dirName, winPath); if (end && *end) { sprintf(types_buff, "%s Files (*.%s)\01*.%s\01" "All Files (*.*)\01*.*\01", end, end, end); *types_buff = toupper(*types_buff); for (cp = types_buff; *cp; cp++) if (*cp == 1) *cp = 0; ofn.lpstrFilter = types_buff; } else ofn.lpstrFilter = NULL; ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.hInstance = NULL; ofn.lpstrCustomFilter = NULL; ofn.nMaxCustFilter = 0; ofn.nFilterIndex = 0; ofn.lpstrFile = selectedPath; ofn.nMaxFile = MAX_PATH; ofn.lpstrFileTitle = NULL; ofn.lpstrInitialDir = winPath; ofn.lpstrTitle = title; ofn.Flags = flags; ofn.lpstrDefExt = end; if (spec[0] == 'w') { if (!GetSaveFileName(&ofn)) return NULL; } else { if (!GetOpenFileName(&ofn)) return NULL; } selectedPath[ofn.nFileOffset-1] = 0; cygwin_conv_to_full_posix_path(selectedPath, dirName); strcpy(fileName, selectedPath+ofn.nFileOffset); sprintf(path, "%s%s%s", dirName, (strlen(dirName) && strlen(fileName)) ? "/" : "", fileName); return filopen(path, "", spec); } } #else /* __CYGWIN__ */ static char *dirName, *fileName; static FILE *filep; static char *file_spec; static char *file_end; static gint fileSelOk(GtkWidget *w, gpointer data) { int i,j; char *fname = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data)); i = strlen(fname); while (i && (fname[i-1] != '/')) i--; strcpy(fileName, &fname[i]); for (j=0; jselection_entry; if (file_end && strlen(file_end)) { if (file_spec[0] == 'r') gtk_entry_set_text(GTK_ENTRY(entry), messprintf("*.%s", file_end)); else if ((file_spec[0] == 'a') || (file_spec[0] == 'w')) { gtk_entry_set_text(GTK_ENTRY(entry), messprintf(".%s", file_end)); gtk_entry_set_position(GTK_ENTRY(entry),0); } } } static void ace_file_selection_dir_button (GtkWidget *widget, gint row, gint column, GdkEventButton *bevent, gpointer user_data) { if (bevent && bevent->type == GDK_2BUTTON_PRESS) add_template(GTK_WIDGET(user_data)); } static void ace_file_selection_dir_button_first (GtkWidget *widget, gint row, gint column, GdkEventButton *bevent, gpointer user_data) { graphBusyCursorAll(); } static gint fileSelMail(GtkWidget *w, gpointer data) { ACEIN addr_in; if ((addr_in = messPrompt("Please give email address.", "", "t", 0))) { filep = filmail(aceInWord(addr_in)); aceInDestroy (addr_in); gexLoopReturn(TRUE); } return TRUE; } static FILE *gexQueryOpen(char *dname, char *fname, char *end, char *spec, char *title) { GtkFileSelection *filew = GTK_FILE_SELECTION(gtk_file_selection_new(title)); GtkWidget *mailButton; char path[FIL_BUFFER_SIZE+DIR_BUFFER_SIZE]; BOOL isOk; Graph old = graphActive(); gtk_window_set_default_size(GTK_WINDOW(filew), 600, 400); gexConnectWindow(GTK_WIDGET(filew)); graphGtkSetModalWin(GTK_WIDGET(filew), TRUE); graphBusyCursorAll(); /* while creating */ /* provide defaults */ dirName = dname ? dname : defaultDir; fileName = fname ? fname : defaultFile; file_end = end; file_spec = spec; filep = NULL; /* in case of destroy or cancel */ /* I have altered the code here so that if we just have a directory then */ /* we still append a "/" to the end, if you don't do this then */ /* the selection box shows the directory _below_ the one you want. */ sprintf(path, "%s%s%s", dirName, (strlen(dirName)) ? "/" : "", fileName); /* Set default file path. */ gtk_file_selection_set_filename (filew, path); /* But also set the file as a pattern to be matched. */ gtk_file_selection_complete (filew, path); add_template(GTK_WIDGET(filew)); gtk_signal_connect(GTK_OBJECT(filew->cancel_button), "clicked", GTK_SIGNAL_FUNC(gexLoopReturnSig), (gpointer) FALSE); gtk_signal_connect(GTK_OBJECT(filew), "delete_event", (GtkSignalFunc) gexLoopReturnEvent, (gpointer) FALSE); gtk_signal_connect(GTK_OBJECT(filew->ok_button), "clicked", (GtkSignalFunc) fileSelOk, (gpointer) filew); gtk_signal_connect(GTK_OBJECT(filew->dir_list), "button_press_event", (GtkSignalFunc) ace_file_selection_dir_button_first, (gpointer) filew); gtk_signal_connect_after(GTK_OBJECT(filew->dir_list), "select_row", (GtkSignalFunc) ace_file_selection_dir_button, (gpointer) filew); if (strcmp(spec, "w") == 0) { mailButton = gtk_button_new_with_label("Send as Mail"); gtk_box_pack_start (GTK_BOX (GTK_FILE_SELECTION(filew)->button_area), mailButton, TRUE, TRUE, 0); gtk_widget_show(mailButton); gtk_signal_connect(GTK_OBJECT(mailButton), "clicked", (GtkSignalFunc) fileSelMail, (gpointer) filew); } gtk_widget_show(GTK_WIDGET(filew)); isOk = gexMainLoop(GTK_WIDGET(filew)); gtk_widget_destroy(GTK_WIDGET(filew)); /* Reset to avoid obscure behaviour. */ *defaultDir = 0; *defaultFile = 0; graphActivate(old); return isOk ? filep : NULL; } #endif /* !__CYGWIN__ */ /***********************************************************************/ /* Modal dialogs */ /* I know gtklabels can split text, but this does it better */ static char *split_message(char *text) { char *cp, *cp1, *ourtext; int j, i=0; cp = ourtext = strnew(text, 0); /* break long messages */ while (*(++cp)) { if ((*cp) == '\n') i = 0; if ((i++>30) && (*cp == ' ')) { cp1 = cp; for( j = 0; j<10; j++,cp1++) if ((!(*cp1)) || ((*cp1) == '\n')) goto skip; i=0; *cp = '\n'; skip:; } } return ourtext; } /* XPM */ static char *q_mark3[] = { /* width height num_colors chars_per_pixel */ " 35 34 5 1", /* colors */ ". c #000000", "# c #808080", "a c None", "b c #ffff00", "c c #ffffff", /* pixels */ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaa........aaaaaaaaaaaaaaa", "aaaaaaaaaa..cbcbcbcb..aaaaaaaaaaaaa", "aaaaaaaaa.bcbcbcbcbcbc.aaaaaaaaaaaa", "aaaaaaaa.........bcbcbb.aaaaaaaaaaa", "aaaaaaa.#bbbbbbbb#.cbbbb.aaaaaaaaaa", "aaaaaa.bbbbbbbbbbbb.bbba.aaaaaaaaaa", "aaaaa.bbbbbbbbbbbbbb.bab#.aaaaaaaaa", "aaaa.bbbbbbbbbbbbbbbb.b#b.aaaaaaaaa", "aaaa.bbbbbbbbbbbbbbbb##b#.aaaaaaaaa", "aaa.bbbbbbbbbbbbbbbbbb.#b.aaaaaaaaa", "aaa.bbbbbbb...bbbbbbbb.b#.aaaaaaaaa", "aa.bbbbbbb.#.a.bbbbbbb.##.aaaaaaaaa", "aa.bbbbbb.#b.aa.bbbbbb.#.aaaaaaaaaa", "aa.bbbbbb.b.aaa.bbbbbb.#.aaaaaaaaaa", "aa.bbbbbb..aaaa.bbbbbb..aaaaaaaaaaa", "aa........aaaa.bbbbbb#.aaaaaaaaaaaa", "aaaaaaaaaaaaa.bbbbbbb.aaaaaaaaaaaaa", "aaaaaaaaaaaa.bbbbbbb............aaa", "aaaaaaaaaaa.bbbbbbb..............aa", "aaaaaaaaaaa#bbbbbb.b.............aa", "aaaaaaaaaa.bbbbbb#.#.............aa", "aaaaaaaaaa.bbbbbb.#b.aaaa........aa", "aaaaaaaaaa.bbbbbb.b.aa.........aaaa", "aaaaaaaaaa.bbbbbb..#........aaaaaaa", "aaaaaaaaaa........b.......aaaaaaaaa", "aaaaaaaaaaa.bbbbbb.#....aaaaaaaaaaa", "aaaaaaaaaa........#b...aaaaaaaaaaaa", "aaaaaaaaaa.bbbbbb.b#..aaaaaaaaaaaaa", "aaaaaaaaaa.bbbbbb.#b.aaaaaaaaaaaaaa", "aaaaaaaaaa.bbbbbb.b.aaaaaaaaaaaaaaa", "aaaaaaaaaa.bbbbbb..aaaaaaaaaaaaaaaa", "aaaaaaaaaa........aaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }; /* XPM */ static char *warning1[] = { /* width height num_colors chars_per_pixel */ " 30 29 4 1", /* colors */ ". c #000000", "# c None", "a c #ff0000", "b c #ffffff", /* pixels */ "##############################", "#############aaaa#############", "############aaaaa#############", "############aaaaaa############", "###########aaaaaaa############", "###########aaaaaaaa###########", "##########aaaaaaaaa###########", "##########aaaaaaaaaa##########", "#########aaaaaaaaaaa##########", "#########aaaaaaaaaaaa#########", "########aaaaaabbaaaaa#########", "########aaaaaabbaaaaaa########", "#######aaaaaab..baaaaa########", "#######aaaaaab..baaaaaa#######", "######aaaaaabb..bbaaaaa#######", "######aaaaaabb..bbaaaaaa######", "#####aaaaaabbb..bbbaaaaa######", "#####aaaaaabbb..bbbaaaaaa#####", "####aaaaaabbbb..bbbbaaaaa#####", "####aaaaaabbbbbbbbbbaaaaaa####", "###aaaaaabbbbb..bbbbbaaaaa####", "###aaaaaabbbbb..bbbbbaaaaaa###", "##aaaaaabbbbbbbbbbbbbbaaaaa###", "##aaaaaaaaaaaaaaaaaaaaaaaaaa##", "#aaaaaaaaaaaaaaaaaaaaaaaaaaaa#", "#aaaaaaaaaaaaaaaaaaaaaaaaaaaa#", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa#", "#aaaaaaaaaaaaaaaaaaaaaaaaaaa##", "##############################" }; /* Display all acedb popup messages. */ /* query: some messages need "Yes" and "No" buttons. */ /* msgopt: some messages should offer the user the chance to see all further */ /* messages in a scrolled window list. */ static BOOL modalDialog (char *text, char *buttonLabel, BOOL query, BOOL msgopt) { GdkPixmap *pixmap; GtkStyle *style; GdkBitmap *mask; GtkWidget *pixmapwid; GtkWidget *topbox; GtkWidget *hbox; GtkWidget *checkbox ; GtkWidget *yes; GtkWidget *no = NULL; GtkWidget *dialog = gtk_dialog_new(); char *ourtext = split_message(text); GtkWidget *label = gtk_label_new(ourtext); char **icon = query ? q_mark3 : warning1; BOOL isOk; BOOL msg_window = FALSE ; Graph old = graphActive(); graphGtkSetSplash(0); /* kill any splash screen */ messfree(ourtext); GTK_WINDOW(dialog)->type = GTK_WINDOW_DIALOG; /* make sure the underlying X resources exist before doing the pixmap */ gtk_widget_realize(dialog); style = gtk_widget_get_style(GTK_WIDGET(dialog)); pixmap = gdk_pixmap_create_from_xpm_d(dialog->window, &mask, &style->bg[GTK_STATE_NORMAL], (gchar **)icon ); pixmapwid = gtk_pixmap_new( pixmap, mask ); /* Holds popup message + maybe other buttons. */ topbox = gtk_vbox_new(FALSE, 0); /* Holds the message + icon. */ hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), pixmapwid, FALSE, FALSE, 5); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10); gtk_box_pack_start(GTK_BOX(topbox), hbox, FALSE, FALSE, 5); /* If required, create a checkbox to allow user to switch to a scrolled */ /* message window. */ if (msgopt) { checkbox = gtk_check_button_new_with_label("Switch to Scrolled Message Window") ; gtk_box_pack_start(GTK_BOX(topbox), checkbox, TRUE, TRUE, 5); gtk_signal_connect(GTK_OBJECT(checkbox), "toggled", (GtkSignalFunc)msgWindowSig, (gpointer)&msg_window); } if (query) { yes = gtk_button_new_with_label("Yes"); no = gtk_button_new_with_label("No"); } else yes = gtk_button_new_with_label(buttonLabel); gtk_window_set_title(GTK_WINDOW(dialog), "Please Reply"); gtk_signal_connect(GTK_OBJECT(dialog), "delete_event", (GtkSignalFunc) gexLoopReturnEvent, (gpointer)FALSE); gtk_signal_connect(GTK_OBJECT(yes), "clicked", (GtkSignalFunc) gexLoopReturnSig, (gpointer) TRUE); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), yes, TRUE, TRUE, 0); if (query) { gtk_signal_connect(GTK_OBJECT(no), "clicked", (GtkSignalFunc) gexLoopReturnSig, (gpointer) FALSE); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), no, TRUE, TRUE, 0); } gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), topbox, TRUE, TRUE, 5); GTK_WIDGET_SET_FLAGS (yes, GTK_CAN_DEFAULT); gtk_widget_grab_default(yes); gdk_pixmap_unref(pixmap); gdk_pixmap_unref(mask); gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); gtk_widget_show_all(dialog); isOk = gexMainLoop(dialog); gtk_widget_destroy(dialog); /* Switch to scrolled message window if required. */ if (msg_window) { msg_use_list_G = TRUE ; /* This allows the application to take control of the switch to the */ /* message window, in acedbs case we need this to keep user prefs. in */ /* step with message window switching. */ if (msg_prefcb_G) msg_prefcb_G(msg_window) ; } graphActivate(old); return isOk; } static BOOL gexQuery (char *text) { return modalDialog(text, 0, TRUE, FALSE); } /* Handles informational/error messages. */ static void gexOut(char *mesg_buf, void *user_pointer) { showMesg(mesg_buf, user_pointer, FALSE) ; return ; } /* Handles fatal messages. */ static void gexExit(char *mesg_buf, void *user_pointer) { showMesg(mesg_buf, user_pointer, TRUE) ; return ; } /* Handles just about all messages. */ static void showMesg(char *mesg_buf, void *user_pointer, BOOL exit) { if (exit) /* exit-message from messExit, messcrash */ (void)modalDialog(mesg_buf, "Exit", FALSE, FALSE); else { /* other message is being output, user will have option to switch to a */ /* scrolled message window. */ /* this is a dreadful hack, acedb registers a crash handler that then */ /* calls messout, so it completely subverts my attempt to detect the */ /* exit/crash cases....sigh... */ if (strstr(mesg_buf, "FATAL ERROR") != NULL || strstr(mesg_buf, "EXIT") != NULL || strstr(mesg_buf, "Sorry for the crash") != NULL) (void)modalDialog(mesg_buf, "Exit", FALSE, FALSE); else (void)modalDialog(mesg_buf, "Continue", FALSE, TRUE); } return ; } static ACEIN promptAnswer_in; static STORE_HANDLE promptAnswer_handle; static char *promptFormat; static BOOL promptDoneError; static gint graphModalDialogCheck (GtkWidget *w, gpointer data) { GtkWidget *entry = (GtkWidget *)data; GtkWidget *label; char *input = gtk_entry_get_text(GTK_ENTRY(entry)); promptAnswer_in = aceInCreateFromText (input, "", promptAnswer_handle); aceInSpecial (promptAnswer_in, ""); aceInCard (promptAnswer_in); if (aceInCheck(promptAnswer_in, promptFormat)) { gexLoopReturn(TRUE); } else if (!promptDoneError) { GtkWidget *dialog = entry; while (dialog && !GTK_IS_DIALOG(dialog)) dialog = dialog->parent; label = gtk_label_new("Sorry, invalid response. Try again or cancel"); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, TRUE, TRUE, 0); gtk_widget_show(label); promptDoneError = TRUE; } return TRUE; } static ACEIN gexPrompt (char *prompt, char *dfault, char *fmt, STORE_HANDLE handle) { GtkWidget *ok = gtk_button_new_with_label("Ok"); GtkWidget *cancel = gtk_button_new_with_label("Cancel"); GtkWidget *dialog = gtk_dialog_new(); char *ourtext = split_message(prompt); GtkWidget *label = gtk_label_new(ourtext); GtkWidget *entry = gtk_entry_new(); BOOL isOk; Graph old = graphActive(); messfree(ourtext); promptAnswer_in = NULL; promptFormat = fmt; promptDoneError = FALSE; promptAnswer_handle = handle; gtk_window_set_title(GTK_WINDOW(dialog), "Please Reply"); GTK_WINDOW(dialog)->type = GTK_WINDOW_DIALOG; gtk_signal_connect(GTK_OBJECT(dialog), "delete_event", (GtkSignalFunc) gexLoopReturnEvent, (gpointer)FALSE); gtk_signal_connect(GTK_OBJECT(cancel), "clicked", (GtkSignalFunc) gexLoopReturnSig, (gpointer) FALSE); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), ok, TRUE, TRUE, 0); gtk_signal_connect(GTK_OBJECT(ok), "clicked", (GtkSignalFunc) graphModalDialogCheck, (gpointer) entry); gtk_signal_connect(GTK_OBJECT(entry), "activate", (GtkSignalFunc) graphModalDialogCheck, (gpointer) entry); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), cancel, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, TRUE, TRUE, 5); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), entry, TRUE, TRUE, 0); gtk_container_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), 10); if (dfault) gtk_entry_set_text(GTK_ENTRY(entry), dfault); gtk_widget_grab_focus(entry); GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT); gtk_widget_grab_default(ok); gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); gtk_widget_show_all(dialog); isOk = gexMainLoop(dialog); gtk_widget_destroy(dialog); if (!isOk && promptAnswer_in) aceInDestroy (promptAnswer_in); graphActivate(old); return promptAnswer_in; } /* gexPrompt */ /***********************************************************************/ /* Text editor window: This is just the GtkText widget, wrapped up for acedb ease-of-use */ typedef void (*CBRoutine)(void *editor, void *arg1); struct GexEditor { GtkWidget *window; GtkWidget *text; GtkWidget *ok, *cancel; void *data; CBRoutine okCB, cancelCB; }; static int editorWidth = 500; static int editorHeight = 200; static void editorFinalise(void *p) { struct GexEditor *editor = (struct GexEditor *)p; gdk_window_get_size(editor->window->window, &editorWidth, &editorHeight); gtk_widget_destroy(editor->window); } static gint editorDeleteEvent(GtkWidget *w, GdkEventAny *event, gpointer data) { struct GexEditor *editor = (struct GexEditor *)data; CBRoutine CB = editor->cancelCB; void *dataArg = editor->data; (*CB)(editor, dataArg); return TRUE; } static gint editorOK(GtkWidget *w, gpointer data) { struct GexEditor *editor = (struct GexEditor *)data; CBRoutine CB = editor->okCB; void *dataArg = editor->data; (*CB)(editor, dataArg); return TRUE; } static gint editorCancel(GtkWidget *w, gpointer data) { struct GexEditor *editor = (struct GexEditor *)data; CBRoutine CB = editor->cancelCB; void *dataArg = editor->data; (*CB)(editor, dataArg); return TRUE; } void *gexTextEditorNew(char *title, char *initText, STORE_HANDLE handle, void *data, CBRoutine okCB, CBRoutine cancelCB) { struct GexEditor *editor = (struct GexEditor *) handleAlloc(editorFinalise, handle, sizeof(struct GexEditor)); GtkWidget *table; GtkWidget *vscrollbar; GtkWidget *bbox; editor->data = data; editor->okCB = okCB; editor->cancelCB = cancelCB; editor->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(editor->window), title); gtk_window_set_default_size(GTK_WINDOW(editor->window), editorWidth, editorHeight); gtk_widget_set_name(editor->window, "acedb editor window"); table = gtk_table_new(2, 2, FALSE); gtk_table_set_row_spacing(GTK_TABLE(table), 0, 2); gtk_table_set_col_spacing(GTK_TABLE(table), 0, 2); editor->text = gtk_text_new(NULL, NULL); gtk_text_set_editable(GTK_TEXT(editor->text), TRUE); gtk_text_set_word_wrap(GTK_TEXT(editor->text), TRUE); if (initText) gtk_text_insert(GTK_TEXT(editor->text), NULL, NULL, NULL, initText, strlen(initText)); editor->ok = gtk_button_new_with_label("OK"); editor->cancel = gtk_button_new_with_label("Cancel"); bbox = gtk_hbutton_box_new(); gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); gtk_button_box_set_spacing(GTK_BUTTON_BOX(bbox), 20); gtk_box_pack_start(GTK_BOX(bbox), editor->ok, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(bbox), editor->cancel, TRUE, TRUE, 0); vscrollbar = gtk_vscrollbar_new(GTK_TEXT(editor->text)->vadj); gtk_container_add(GTK_CONTAINER(editor->window), table); gtk_table_attach(GTK_TABLE(table), editor->text, 0, 1, 0, 1, GTK_EXPAND | GTK_SHRINK | GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); gtk_table_attach(GTK_TABLE(table), vscrollbar, 1, 2, 0, 1, 0, GTK_EXPAND | GTK_FILL | GTK_SHRINK, 0, 0); gtk_table_attach(GTK_TABLE(table), bbox, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 5, 4); gtk_signal_connect(GTK_OBJECT(editor->cancel), "clicked", GTK_SIGNAL_FUNC(editorCancel), (gpointer)editor); gtk_signal_connect(GTK_OBJECT(editor->window), "delete_event", GTK_SIGNAL_FUNC(editorDeleteEvent), (gpointer)editor); gtk_signal_connect(GTK_OBJECT(editor->ok), "clicked", GTK_SIGNAL_FUNC(editorOK), (gpointer)editor); gtk_widget_grab_focus(editor->text); gtk_widget_show_all(editor->window); return editor; } char *gexEditorGetText(void *ep, STORE_HANDLE handle) { struct GexEditor *editor = (struct GexEditor *)ep; char *val = gtk_editable_get_chars(GTK_EDITABLE(editor->text), (gint) 0, (gint) -1); char *ret = strnew(val, handle); g_free(val); return ret; } /* blocking interface is below here */ static void editorBlockCancel(void *ep, void *arg) { gexLoopReturn(FALSE); } static void editorBlockOK(void *ep, void *arg) { gexLoopReturn(TRUE); } char *gexTextEditor(char *title, char *initText, STORE_HANDLE handle) { char *ret; Graph old = graphActive(); struct GexEditor *editor = gexTextEditorNew(title, initText, handle, 0, editorBlockOK, editorBlockCancel); if (gexMainLoop(editor->window)) ret = gexEditorGetText(editor, handle); else ret = NULL; messfree(editor); /* destroys all via callback */ graphActivate(old); return ret; } /************************************************************/ /* signal call back func to call up help pages from buttons/menuitems */ void gexHelpOnSig (GtkWidget *widget, gpointer subject) { helpOn((char*)subject); } /************************* eof ******************************/