/* File: graphmesglist.c * Author: Ed Griffiths (edgrif@sanger.ac.uk) * Copyright (c) J Thierry-Mieg and R Durbin, 2001 *------------------------------------------------------------------- * 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 (Sanger Centre, UK) rd@sanger.ac.uk, and * Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.crbm.cnrs-mop.fr * * Description: Displays any normal or error messages in a rolling * list rather than in individual popup dialogs. The * glib single linked-list stuff is used to hold the * messages. * * IMPORTANT to note is that although this is currently * done via graphs, it's controlled from gex.c and could * simply be redone in gex/GTK type stuff which would * give cut/paste. * * KNOWN PROBLEMS: * if the code outputs several error * messages and then a crash message, the error messages * don't get seen because the code kills the message * window before the messages can be seen. * * Exported functions: See mesglist.h * HISTORY: * Last edited: Feb 6 11:52 2003 (edgrif) * * Jul 5 13:29 2001 (edgrif): Fix resizing bug, crass stuff, this * now seems to be a pretty solid working window. * Created: Mon Jun 4 17:06:40 2001 (edgrif) * CVS info: $Id$ *------------------------------------------------------------------- */ #include #include #include #include #include /************************************************************/ /* Holds all the state for a message list, currently the message list is */ /* actually static because of deficiencies in the graph package but it */ /* doesn't have to be static. */ typedef struct _MesgListStateStruct { GSList *messages ; int max_messages ; Graph graph ; MENUOPT *menu ; STORE_HANDLE handle ; GraphFunc app_destroy_cb ; } MesgListStateStruct, *MesgListState ; /************************************************************/ static void initMsgList(void) ; static MesgListState getMsgList(char *calling_func) ; static void resetMsgList(void) ; static void freeMsgList(MesgListState msg_list) ; static void destroyMsgList(void) ; static void drawWindow(void) ; static void saveMessages(void) ; static char *mangleMessage(char *msg) ; static void mesgDestroy(void) ; /************************************************************/ static MENUOPT msg_list_menu_G[] = { {drawWindow, "Refresh"}, {mesgListReset, "Reset"}, {graphDestroy, "Close" }, {mesgListDestroy, "Quit" }, {menuSpacer, ""}, {graphPrint, "Print"}, {saveMessages, "Save"}, {menuSpacer, ""}, {help, "Help" }, {NULL, NULL} } ; /* By default this is the most messages that can be displayed. */ enum {MESGLIST_MIN_MESSAGES = 1, MESGLIST_DEFAULT_MESSAGES = 50, MESGLIST_MAX_MESSAGES = 2000} ; /* Defaults for the message list graph. */ static float MESGLIST_X = 0.0, MESGLIST_Y = 0.0, MESGLIST_WIDTH = 0.6, MESGLIST_HEIGHT = 0.1 ; static int MESGLIST_MAX_LINE_LENGTH = 1024 ; enum {MESGLIST_TYPE = TEXT_FULL_SCROLL} ; static int MESGLIST_BORDER = 1 ; static char *MESGLIST_TITLE = "AceDB Normal/Error Messages" ; static char *MESGLIST_NO_MESSAGES = " < No messages currently > " ; /* I hate this, really this should be gex state somewhere, not all splodged*/ /* in here....maybe we can set something up with user_data etc. */ /* */ /* This should only be looked at by get/resetMsgList(). */ static MesgListState msg_list_G = NULL ; /************************************************************/ /* Given a list size value, check it and if invalid, return a default value. */ /* This routine is a bit like a C++ static member function, it can be called */ /* before the MesgList is initialised, all functions other cannot. */ /* */ int mesgListGetDefaultListSize(int curr_value) { int new_value = curr_value ; if (curr_value < MESGLIST_MIN_MESSAGES) new_value = MESGLIST_DEFAULT_MESSAGES ; else if (curr_value > MESGLIST_MAX_MESSAGES) new_value = MESGLIST_MAX_MESSAGES ; return new_value ; } /* Like the above but return TRUE if valid, FALSE otherwise. This is to */ /* support the graphNNNEditor() check callback facility. */ /* This routine is a bit like a C++ static member function, it can be called */ /* before the MesgList is initialised, all other functions cannot. */ /* */ BOOL mesgListCheckListSize(int curr_value) { BOOL result = FALSE ; if (curr_value >= MESGLIST_MIN_MESSAGES && curr_value <= MESGLIST_MAX_MESSAGES) result = TRUE ; return result ; } /* Should only be called once to initialise, otherwise behaviour is undefined*/ /* */ void mesgListCreate(GraphFunc destroy_cb) { MesgListState msg_list = NULL ; Graph curr ; curr = graphActive() ; initMsgList() ; msg_list = getMsgList("mesgListCreate") ; msg_list->app_destroy_cb = destroy_cb ; drawWindow() ; graphActivate(curr) ; return; } /********************************************/ /* Set the list size and adjust the message window accordingly. */ void mesgListSetSize(int size) { MesgListState msg_list = getMsgList("mesgListSetSize") ; Graph curr ; curr = graphActive() ; if (size < MESGLIST_MIN_MESSAGES) size = MESGLIST_MIN_MESSAGES ; else if (size > MESGLIST_MAX_MESSAGES) size = MESGLIST_MAX_MESSAGES ; msg_list->max_messages = size ; /* If the list has shrunk then remove some of the list and redisplay. */ if (g_slist_length(msg_list->messages) > msg_list->max_messages) { int i = 0 ; char *old_msg = NULL ; i = g_slist_length(msg_list->messages) - msg_list->max_messages ; while (i > 0) { old_msg = msg_list->messages->data ; msg_list->messages = g_slist_remove(msg_list->messages, msg_list->messages->data) ; messfree(old_msg) ; i-- ; } drawWindow() ; } graphActivate(curr) ; return ; } /********************************************/ /* Add a new message. */ /* */ /* All messages are copied as we need to hang on to them for potentially a */ /* long time. */ /* */ void mesgListAdd(char *msg, void *user_pointer_unused) { MesgListState msg_list = getMsgList("mesgListAdd") ; char *msg_copy = NULL ; Graph curr ; curr = graphActive() ; /* Take a copy of the message and prepend a time stamp. */ msg_copy = hprintf(msg_list->handle, "%s - %s", timeShow(timeNow()), msg) ; /* Some messages are multiline so hack them into a single line. */ msg_copy = mangleMessage(msg_copy) ; /* If we've reached the maximum, remove the oldest message. */ if (g_slist_length(msg_list->messages) == msg_list->max_messages) { char *old_msg = NULL ; old_msg = msg_list->messages->data ; msg_list->messages = g_slist_remove(msg_list->messages, msg_list->messages->data) ; messfree(old_msg) ; } /* Stick new message onto the end of the list. */ msg_list->messages = g_slist_append(msg_list->messages, msg_copy) ; /* Refresh the message window. */ drawWindow() ; graphActivate(curr) ; return ; } /* Free the message list and redraw an empty message window. */ void mesgListReset(void) { MesgListState msg_list = getMsgList("mesgListReset") ; Graph curr ; curr = graphActive() ; freeMsgList(msg_list) ; drawWindow() ; graphActivate(curr) ; return ; } /* Close the message window but keep the message stuff around. We need this */ /* because user may wish to just close the window. */ void mesgListClose(void) { MesgListState msg_list = getMsgList("mesgListClose") ; Graph curr ; curr = graphActive() ; /* not much to do here because graph package does the destroy of window. */ msg_list->graph = GRAPH_NULL ; graphActivate(curr) ; return ; } /* Really destroy everything, graph, list, the lot. After this a call must */ /* be made to mesgListCreate() to use the message list again. */ /* */ void mesgListDestroy(void) { MesgListState msg_list = getMsgList("mesgListDestroy") ; Graph curr ; GraphFunc destroy_cb ; curr = graphActive() ; /* Its possible for user to "quit" message list window and then use */ /* preferences to switch to popups and hence call here to destroi */ if (msg_list->graph) { Graph curr_g, mesg_g ; curr_g = graphActive() ; mesg_g = msg_list->graph ; graphActivate(mesg_g) ; graphDestroy() ; if (curr_g != mesg_g) graphActivate(curr_g) ; } if (msg_list->app_destroy_cb) destroy_cb = msg_list->app_destroy_cb ; destroyMsgList() ; graphActivate(curr) ; if (destroy_cb) (*destroy_cb)() ; return ; } /* */ /* INTERNAL ROUTINES */ /* */ /********************************************/ /* Initialise our control block, sadly static at the moment. Note that this */ /* is the pathological case where we have to access the our global directly */ /* so we can set it. */ /* */ static void initMsgList(void) { MesgListState msg_list = NULL ; msg_list = (MesgListState)messalloc(sizeof(MesgListStateStruct)) ; msg_list->messages = NULL ; msg_list->max_messages = MESGLIST_DEFAULT_MESSAGES ; msg_list->handle = handleCreate() ; msg_list->graph = GRAPH_NULL ; msg_list->menu = &msg_list_menu_G[0] ; msg_list_G = msg_list ; return ; } /********************************************/ /* These two routines control access to the only global in this package, */ /* it would be nice if this wasn't necessary but the graph interface does */ /* not allow passing around of user_data which condemns us more or less to */ /* using some sort of global. */ /* */ static MesgListState getMsgList(char *calling_func) { if (msg_list_G == NULL) messcrash("Internal Error: %s called getMsgList() but" "MesgList has not been initialised.", calling_func) ; return msg_list_G ; } static void resetMsgList(void) { msg_list_G = NULL ; return ; } /********************************************/ static void freeMsgList(MesgListState msg_list) { handleDestroy(msg_list->handle) ; /* free text of messages. */ g_slist_free(msg_list->messages) ; /* free message list. */ msg_list->messages = NULL ; return ; } /********************************************/ /* Frees all the message list memory etc. */ static void destroyMsgList(void) { MesgListState msg_list = getMsgList("destroyMsgList") ; freeMsgList(msg_list) ; /* Free text/messages. */ messfree(msg_list) ; /* free msg struct. */ resetMsgList() ; /* reset to NULL. */ return ; } /* Show the message window, if there is no message window then make it and */ /* then show it. */ /* Note that current behaviour is to make sure window is shown to user, */ /* either because its newly created or because we pop it to the top. */ /* */ static void drawWindow(void) { MesgListState msg_list = getMsgList("drawWindow") ; int start, lines, line_length ; if(graphActivate(msg_list->graph)) { graphPop() ; graphClear(); } else { msg_list->graph = graphCreate(MESGLIST_TYPE, MESGLIST_TITLE, MESGLIST_X, MESGLIST_Y, MESGLIST_WIDTH, MESGLIST_HEIGHT) ; graphHelp("MesgList") ; graphMenu(msg_list->menu) ; graphRegister(DESTROY, mesgListClose) ; graphRegister(RESIZE, drawWindow) ; } /* Display all current messages (in black), with most recent one in red. */ /* Various fiddlings are done with MESGLIST_BORDER to give a decent width */ /* around the text, this hopefully gives the user a warm feeling about */ /* being able to see all of a message. */ start = MESGLIST_BORDER ; /* start a bit over and a bit down. */ lines = MESGLIST_BORDER ; line_length = 0 ; graphColor(BLACK) ; if (msg_list->messages == NULL) { /* Display default message if there are no messages yet. */ graphText(MESGLIST_NO_MESSAGES, start, lines) ; line_length = strlen(MESGLIST_NO_MESSAGES) ; } else { GSList* next ; int i, list_length, line_len = 0 ; list_length = g_slist_length(msg_list->messages) ; next = msg_list->messages ; for (i = 0 ; i < list_length ; i++) { line_len = strlen((char *)next->data) ; if (line_len > line_length) line_length = line_len ; if (i == (list_length - 1)) /* Highlight last item in red. */ graphColor(RED) ; graphText((char *)next->data, start, lines) ; lines++ ; next = g_slist_next(next) ; } } /* Set size of underlying message window up to a maximum (number of lines */ /* is constrained by max. no. of messages which is set to be showable. */ /* Again fiddle around to get a nice border. */ if (line_length > MESGLIST_MAX_LINE_LENGTH) line_length = MESGLIST_MAX_LINE_LENGTH ; graphTextBounds((MESGLIST_BORDER + line_length + MESGLIST_BORDER), lines + MESGLIST_BORDER) ; /* Scroll to bottom of window so latest message is visible. */ graphGoto(0, lines + MESGLIST_BORDER); graphRedraw() ; return ; } /* Save the messages to a file chosen by user. */ /* */ static void saveMessages(void) { MesgListState msg_list = getMsgList("saveMessages") ; static char fname[FIL_BUFFER_SIZE]="", dname[DIR_BUFFER_SIZE]="" ; ACEOUT save_file ; GSList* next ; save_file = aceOutCreateToChooser ("File to save messages in", dname, fname, "msg", "w", 0) ; if (save_file) { next = msg_list->messages ; do { aceOutPrint(save_file, "%s\n", (char *)next->data); } while ((next = g_slist_next(next))) ; aceOutDestroy(save_file) ; } return ; } /* Some messages will have tabs/newlines in them, I'm going to change them */ /* to a set number of blanks so there is feedback about where they are. */ /* The function gets handed a pointer the message, if the message needs no */ /* changes then the pointer is simply returned, otherwise the original */ /* message is freed and a new one returned. */ /* */ static char *mangleMessage(char *msg) { char *result ; enum {BLANKS = 3} ; int msg_len = strlen(msg) ; int i, count ; char *new_msg = NULL ; char *tmp ; for (i = 0, count = 0, tmp = msg ; i < msg_len ; i++, tmp++) { if (*tmp == '\n' || *tmp == '\t') count++ ; } if (count == 0) result = msg ; else { result = new_msg = messalloc(msg_len + (count * BLANKS) + 1) ; tmp = msg ; while (*tmp) { if (*tmp == '\n' || *tmp == '\t') { memset(new_msg, (int)' ', BLANKS) ; new_msg+=3 ; } else { *new_msg = *tmp ; new_msg++ ; } tmp++ ; } *new_msg = '\0' ; messfree(msg) ; } return result ; } /********************** eof *********************************/