--- svtools/util/makefile.mk.orig 2003-06-05 22:18:51.000000000 -0500 +++ svtools/util/makefile.mk 2003-06-05 22:19:38.000000000 -0500 @@ -233,6 +233,10 @@ $(VOSLIB) \ $(SALLIB) +.IF "$(GUI)"=="UNX" +SHL2STDLIBS+= $(EXPATASCII3RDLIB) `pkg-config --libs glib-2.0` +.ENDIF + .IF "$(GUI)"=="WNT" SHL2STDLIBS+= \ uwinapi.lib \ --- svtools/source/config/makefile.mk.orig 2003-06-05 22:19:59.000000000 -0500 +++ svtools/source/config/makefile.mk 2003-06-05 22:21:02.000000000 -0500 @@ -107,7 +107,13 @@ $(SLO)$/accessibilityoptions.obj \ $(SLO)$/cmdoptions.obj \ $(SLO)$/extendedsecurityoptions.obj \ - $(SLO)$/sourceviewconfig.obj + $(SLO)$/sourceviewconfig.obj \ + $(SLO)$/recent-files.obj + +.IF "$(GUI)"=="UNX" +CFLAGS+=-Wall +CFLAGS+=`pkg-config --cflags glib-2.0` +.ENDIF EXCEPTIONSFILES = \ $(SLO)$/accelcfg.obj \ --- svtools/prj/build.lst.orig 2003-06-05 22:23:34.000000000 -0500 +++ svtools/prj/build.lst 2003-06-05 22:23:42.000000000 -0500 @@ -1,4 +1,4 @@ -st svtools : offuh toolkit ucbhelper unotools jpeg NULL +st svtools : offuh toolkit ucbhelper unotools jpeg expat NULL st svtools usr1 - all st_mkout NULL st svtools\inc get - all st_inc NULL st svtools\inc\sane get - all st_incsa NULL --- svtools/inc/historyoptions.hxx.orig 2003-06-05 22:25:11.000000000 -0500 +++ svtools/inc/historyoptions.hxx 2003-06-05 22:26:05.000000000 -0500 @@ -239,6 +239,7 @@ @param "eHistory" select right history. @param "sURL" URL to save in history @param "sFilter" filter name to save in history + @param "sMimeType" MIME type of the URL; used only for ePICKLIST. @param "sTitle" document title to save in history @param "sPassword" password to save in history @return - @@ -249,6 +250,7 @@ void AppendItem( EHistoryType eHistory , const ::rtl::OUString& sURL , const ::rtl::OUString& sFilter , + const ::rtl::OUString& sMimeType , const ::rtl::OUString& sTitle , const ::rtl::OUString& sPassword ); --- svtools/source/config/historyoptions.cxx.orig 2003-06-05 22:26:20.000000000 -0500 +++ svtools/source/config/historyoptions.cxx 2003-06-05 23:12:47.000000000 -0500 @@ -95,6 +95,8 @@ #include #endif +#include "recent-files.hxx" + //_________________________________________________________________________________________________________________ // namespaces //_________________________________________________________________________________________________________________ @@ -403,8 +405,10 @@ sal_uInt32 nPosition = FIXPROPERTYCOUNT; // step over first three readed size values! but count begins at 0! // Get names/values for picklist. // 4 subkeys for every item! + sal_uInt32 nItem; +#if 0 OUString sName; - for( sal_uInt32 nItem=0; nItem>= aItem.sURL ; ++nPosition; @@ -416,6 +420,26 @@ ++nPosition; m_aPicklist.push_back( aItem ); } +#endif + /* The user may already have a pick list from OOo, so we have to skip over it --- we now fetch the list from GNOME */ + nPosition += nPicklistCount * 4; + + ::svt::RecentFileItem *items; + int nItems; + int i; + + ::svt::recentFilesGetList (&items, &nItems); + + aItem.sFilter = OUString ("", 0, RTL_TEXTENCODING_ASCII_US); + aItem.sPassword = OUString ("", 0, RTL_TEXTENCODING_ASCII_US); + + for (i = 0; i < nItems; i++) { + aItem.sURL = OUString(items[i].uri); + aItem.sTitle = OUString (items[i].uri); /* We don't have a title, so just use the filename */ + m_aPicklist.push_back (aItem); + } + + ::svt::recentFilesFree (items, nItems); // Attention: Don't reset nPosition here! @@ -506,9 +530,11 @@ OUString sNode ; Sequence< PropertyValue > seqPropertyValues( 4 ) ; + sal_uInt32 nItem; +#if 0 // Copy picklist entries to save-list! sal_uInt32 nPicklistCount = m_aPicklist.size(); - for( sal_uInt32 nItem=0; nItemAppendItem( eHistory, sURL, sFilter, sTitle, sPassword ); + + if (eHistory == ePICKLIST) + ::svt::recentFilesAddItem (sURL.pData, sMimeType.pData); + else + m_pDataContainer->AppendItem( eHistory, sURL, sFilter, sTitle, sPassword ); } //***************************************************************************************************************** --- sfx2/source/appl/newhelp.cxx.orig 2003-06-05 22:34:06.000000000 -0500 +++ sfx2/source/appl/newhelp.cxx 2003-06-05 22:35:03.000000000 -0500 @@ -1383,7 +1383,7 @@ { String aTitle = GetEntry(i); String* pURL = (String*)(ULONG)GetEntryData(i); - aHistOpt.AppendItem( eHELPBOOKMARKS, rtl::OUString( *pURL ), sEmpty, rtl::OUString( aTitle ), sEmpty ); + aHistOpt.AppendItem( eHELPBOOKMARKS, rtl::OUString( *pURL ), sEmpty, sEmpty, rtl::OUString( aTitle ), sEmpty ); delete pURL; } } --- sfx2/source/appl/sfxpicklist.cxx.orig 2003-04-11 10:54:18.000000000 -0500 +++ sfx2/source/appl/sfxpicklist.cxx 2003-06-05 22:46:10.000000000 -0500 @@ -461,6 +461,7 @@ SvtHistoryOptions().AppendItem( eHISTORY, aURL.GetURLNoPass( INetURLObject::NO_DECODE ), aFilter, + ::rtl::OUString(), aTitle, SfxStringEncode( aURL.GetPass() ) ); } @@ -497,17 +498,21 @@ ::rtl::OUString aTitle = pDocSh->GetTitle(SFX_TITLE_PICKLIST); ::rtl::OUString aFilter; + ::rtl::OUString aMimeType; INetURLObject aURL( pMed->GetOrigURL() ); const SfxFilter* pFilter = pMed->GetOrigFilter(); - if ( pFilter ) + if ( pFilter ) { aFilter = pFilter->GetFilterName(); + aMimeType = pFilter->GetMimeType(); + } // add to svtool history options SvtHistoryOptions().AppendItem( ePICKLIST, aURL.GetURLNoPass( INetURLObject::NO_DECODE ), aFilter, + aMimeType, aTitle, SfxStringEncode( aURL.GetPass() ) ); --- sd/source/ui/dlg/dlgass.cxx.orig 2003-06-05 23:00:04.000000000 -0500 +++ sd/source/ui/dlg/dlgass.cxx 2003-06-05 23:05:19.000000000 -0500 @@ -186,6 +186,12 @@ #include #endif +#include +#include +#include +#include +#include + #include "sdpage.hxx" #include "helpids.h" #include "assclass.hxx" @@ -198,6 +204,8 @@ using namespace ::com::sun::star; using namespace ::sd; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::system; void InterpolateFixedBitmap( FixedBitmap * pBitmap ) @@ -774,7 +782,38 @@ m_pWindow = NULL; } +/* Queries the name of the filter that we can use to open a file */ +static const String & +getFilterNameForFile (rtl_uString *uri) +{ + Reference< ::com::sun::star::document::XTypeDetection > type_detection ( + ::comphelper::getProcessServiceFactory ()->createInstance ( + ::rtl::OUString::createFromAscii ("com.sun.star.comp.framework.TypeDetection" )), + UNO_QUERY ); + if (!type_detection.is()) + return; + + ::com::sun::star::util::URL url; + ::rtl::OUString type_name; + + url.Complete = uri; + + Reference < ::com::sun::star::util::XURLTransformer > trans ( + ::comphelper::getProcessServiceFactory ()->createInstance ( + ::rtl::OUString::createFromAscii ("com.sun.star.util.URLTransformer" )), + UNO_QUERY); + + trans->parseStrict (url); + + type_name = type_detection->queryTypeByURL (url.Main); + SfxFilterMatcher &filter_matcher = SFX_APP ()->GetFilterMatcher (); + const SfxFilter *filter = filter_matcher.GetFilter4EA (type_name); + if (!filter) + return String (); + + return filter->GetFilterName (); +} void AssistentDlgImpl::ScanDocmenu (void) @@ -808,6 +847,10 @@ else if (aPropertySet[nProperty].Name == HISTORY_PROPERTYNAME_PASSWORD) aPropertySet[nProperty].Value >>= sPassword; + sFilter = rtl::OUString (getFilterNameForFile (sURL.pData).GetBuffer ()); + if (sFilter.getLength () == 0) + continue; + // If the entry is an impress file then insert it into the // history list and the list box. uno::Any aFilterPropSet = xFilterFactory->getByName( sFilter ); --- svtools/source/config/recent-files.hxx 2003-01-30 04:24:37.000000000 -0600 +++ svtools/source/config/recent-files.hxx 2003-06-05 23:10:17.000000000 -0500 @@ -0,0 +1,30 @@ +#ifndef RECENT_FILES_HXX +#define RECENT_FILES_HXX + +#ifndef _RTL_USTRING_ +#include +#endif + +#include + +namespace svt { + +/* A recent file item */ +struct RecentFileItem { + rtl_uString *uri; /* URI of the file */ + rtl_uString *mimeType; /* MIME type */ + time_t timestamp; /* Timestamp for when the item was added */ +}; + +/* Queries the list of recent file items for OpenOffice.org */ +void recentFilesGetList (RecentFileItem **items, int *nItems); + +/* Frees an array of RecentFileItem structures */ +void recentFilesFree (RecentFileItem *items, int nItems); + +/* Adds an item to the list of recent file items */ +void recentFilesAddItem (rtl_uString *uri, const rtl_uString *mimeType); + +}; + +#endif --- svtools/source/config/recent-files.cxx 2003-01-30 04:24:37.000000000 -0600 +++ svtools/source/config/recent-files.cxx 2003-06-05 23:28:52.000000000 -0500 @@ -0,0 +1,802 @@ +#include +#include +#include +#include +#include +#include +#include +#include "expat/xmlparse.h" +#include "recent-files.hxx" + +namespace svt { + +/* + example: + + + + file:///home/federico/gedit.txt + text/plain + 1046485966 + + gedit + + + + file:///home/federico/gedit-2.2.0.tar.bz2 + application/x-bzip + 1046209851 + + + + + +*/ + +/* Name of the standard ~/.recently-used file */ +#define RECENT_FILE_NAME ".recently-used" + +/* Items without a MIME type get assigned this by default */ +#define APPLICATION_OCTET_STREAM "application/octet-stream" + +/* Buffer size for reading ~/.recently-used */ +#define BUFFER_SIZE 16384 + +/* Maximum number of entries to keep before pruning */ +#define MAX_ITEMS 20 + +/* The group we use for OO.o files */ +#define GROUP_OPENOFFICE_ORG "OpenOffice.org" + +/* Tags we understand in the ~/.recently-used XML */ +#define TAG_RECENT_FILES "RecentFiles" +#define TAG_RECENT_ITEM "RecentItem" +#define TAG_URI "URI" +#define TAG_MIME_TYPE "Mime-Type" +#define TAG_TIMESTAMP "Timestamp" +#define TAG_PRIVATE "Private" +#define TAG_GROUPS "Groups" +#define TAG_GROUP "Group" + +/* Recent item as parsed from the file */ +struct Item { + char *uri; + char *mimeType; + time_t timestamp; + gboolean isPrivate; + GSList *groups; +}; + +/* Parser state */ +enum State { + STATE_BEGIN, /* No elements read yet */ + STATE_RECENT_FILES, /* Inside the toplevel RecentFiles element */ + STATE_RECENT_ITEM, /* Inside RecentItem */ + STATE_URI, /* Inside URI */ + STATE_MIME_TYPE, /* Inside Mime-Type */ + STATE_TIMESTAMP, /* Inside Timestamp */ + STATE_PRIVATE, /* Inside Private */ + STATE_GROUPS, /* Inside Groups */ + STATE_GROUP, /* Inside Group */ + STATE_END, /* Finished parsing the toplevel element */ + STATE_ERROR /* Bad XML */ +}; + +/* XML parsing context */ +struct ParseContext { + GSList *items; + + State state; +}; + +/* Computes the name of the ~/.recent-files file */ +static char * +getRecentFilename (void) +{ + return g_strdup_printf ("%s/" RECENT_FILE_NAME, g_get_home_dir ()); +} + +/* Returns whether a string is null or empty */ +static gboolean +stringIsEmpty (const char *s) +{ + return (s == NULL || strlen (s) == 0); +} + +/* Frees the contents of str, if any, and g_strdup()s the newStr into it */ +static void +replaceString (char **str, const char *newStr, int newStrLen) +{ + g_free (*str); + *str = g_strndup (newStr, newStrLen); +} + +/* Creates a new item in the context as a result of entering a RecentItem element */ +static void +startRecentItem (ParseContext *context) +{ + Item *item; + + item = g_new (Item, 1); + + item->uri = NULL; + item->mimeType = NULL; + item->timestamp = -1; + item->isPrivate = FALSE; + item->groups = NULL; + + context->items = g_slist_prepend (context->items, item); +} + +/* Frees an Item */ +static void +freeItem (Item *item) +{ + GSList *l; + + g_free (item->uri); + g_free (item->mimeType); + + for (l = item->groups; l; l = l->next) { + char *group; + + group = l->data; + g_free (group); + } + g_slist_free (item->groups); + + g_free (item); +} + +/* Terminates a RecentItem element by ensuring that the basic properties of the + * current are fulfilled. If the URI is empty, removes the item from the + * context. + */ +static void +endRecentItem (ParseContext *context) +{ + Item *item; + GSList *listItem; + + g_assert (context->items != NULL); + item = context->items->data; + + if (stringIsEmpty (item->uri)) { + freeItem (item); + context->items = g_slist_delete_link (context->items, context->items); + return; + } + + if (stringIsEmpty (item->mimeType)) + replaceString (&item->mimeType, APPLICATION_OCTET_STREAM, strlen (APPLICATION_OCTET_STREAM)); + + if (item->timestamp == -1) + item->timestamp = time (NULL); +} + +/* Handles entering a Private element */ +static void +startPrivate (ParseContext *context) +{ + Item *item; + + g_assert (context->items != NULL); + item = context->items->data; + + item->isPrivate = TRUE; +} + +/* Start element handler for the parser */ +static void +startElementCb (void *data, const XML_Char *name, const XML_Char **attributes) +{ + ParseContext *context; + + context = data; + + if (context->state == STATE_ERROR || context->state == STATE_END) + return; + + switch (context->state) { + case STATE_BEGIN: + if (strcmp (name, TAG_RECENT_FILES) == 0) + context->state = STATE_RECENT_FILES; + else + context->state = STATE_ERROR; + break; + + case STATE_RECENT_FILES: + if (strcmp (name, TAG_RECENT_ITEM) == 0) { + startRecentItem (context); + context->state = STATE_RECENT_ITEM; + } else + context->state = STATE_ERROR; + break; + + case STATE_RECENT_ITEM: + if (strcmp (name, TAG_URI) == 0) + context->state = STATE_URI; + else if (strcmp (name, TAG_MIME_TYPE) == 0) + context->state = STATE_MIME_TYPE; + else if (strcmp (name, TAG_TIMESTAMP) == 0) + context->state = STATE_TIMESTAMP; + else if (strcmp (name, TAG_PRIVATE) == 0) { + startPrivate (context); + context->state = STATE_PRIVATE; + } else if (strcmp (name, TAG_GROUPS) == 0) + context->state = STATE_GROUPS; + else + context->state = STATE_ERROR; + break; + + case STATE_URI: + case STATE_MIME_TYPE: + case STATE_TIMESTAMP: + case STATE_PRIVATE: + case STATE_GROUP: + case STATE_END: + case STATE_ERROR: + context->state = STATE_ERROR; + break; + + case STATE_GROUPS: + if (strcmp (name, TAG_GROUP) == 0) + context->state = STATE_GROUP; + else + context->state = STATE_ERROR; + break; + + default: + g_assert_not_reached (); + } +} + +/* End element handler for the parser */ +static void +endElementCb (void *data, const XML_Char *name) +{ + ParseContext *context; + + context = data; + + switch (context->state) { + case STATE_RECENT_FILES: + if (strcmp (name, TAG_RECENT_FILES) == 0) + context->state = STATE_END; + else + context->state = STATE_ERROR; + break; + + case STATE_RECENT_ITEM: + if (strcmp (name, TAG_RECENT_ITEM) == 0) { + endRecentItem (context); + context->state = STATE_RECENT_FILES; + } else + context->state = STATE_ERROR; + break; + + case STATE_URI: + if (strcmp (name, TAG_URI) == 0) + context->state = STATE_RECENT_ITEM; + else + context->state = STATE_ERROR; + break; + + case STATE_MIME_TYPE: + if (strcmp (name, TAG_MIME_TYPE) == 0) + context->state = STATE_RECENT_ITEM; + else + context->state = STATE_ERROR; + break; + + case STATE_TIMESTAMP: + if (strcmp (name, TAG_TIMESTAMP) == 0) + context->state = STATE_RECENT_ITEM; + else + context->state = STATE_ERROR; + break; + + case STATE_PRIVATE: + if (strcmp (name, TAG_PRIVATE) == 0) { + startPrivate (context); /* I don't know if expat calls the start or end element handler for */ + context->state = STATE_RECENT_ITEM; + } else + context->state = STATE_ERROR; + break; + + case STATE_GROUPS: + if (strcmp (name, TAG_GROUPS) == 0) + context->state = STATE_RECENT_ITEM; + else + context->state = STATE_ERROR; + break; + + case STATE_GROUP: + if (strcmp (name, TAG_GROUP) == 0) + context->state = STATE_GROUPS; + else + context->state = STATE_ERROR; + break; + + case STATE_BEGIN: + case STATE_END: + case STATE_ERROR: + context->state = STATE_ERROR; + break; + + default: + g_assert_not_reached (); + } +} + +/* Sets the timestamp field of an Item */ +static void +setTimestamp (Item *item, const char *s, int len) +{ + char *dup; + long t; + + dup = g_strndup (s, len); + if (sscanf (dup, "%ld", &t) != 1) + item->timestamp = -1; + else + item->timestamp = t; + + g_free (dup); +} + +/* Adds a group to an item */ +static void +addGroup (Item *item, const char *s, int len) +{ + GSList *l; + + for (l = item->groups; l; l = l->next) { + const char *group; + + group = l->data; + if (strncmp (group, s, len) == 0) + return; + } + + item->groups = g_slist_prepend (item->groups, g_strndup (s, len)); +} + +/* Handler for character data between tags */ +static void +characterDataCb (void *data, const XML_Char *s, int len) +{ + ParseContext *context; + Item *item; + + context = data; + item = context->items ? context->items->data : NULL; + + switch (context->state) { + case STATE_URI: + replaceString (&item->uri, s, len); + break; + + case STATE_MIME_TYPE: + replaceString (&item->mimeType, s, len); + break; + + case STATE_TIMESTAMP: + setTimestamp (item, s, len); + break; + + case STATE_GROUP: + addGroup (item, s, len); + break; + + case STATE_BEGIN: + case STATE_RECENT_FILES: + case STATE_RECENT_ITEM: + case STATE_PRIVATE: + case STATE_GROUPS: + case STATE_END: + case STATE_ERROR: + /* Ignore character data that may appear elsewhere */ + break; + + default: + g_assert_not_reached (); + } +} + +/* Creates an XML parser for .recent-files */ +static XML_Parser +createParser (ParseContext *context) +{ + XML_Parser parser; + + parser = XML_ParserCreate (NULL); + if (!parser) + return NULL; + + XML_SetUserData (parser, context); + XML_SetElementHandler (parser, startElementCb, endElementCb); + XML_SetCharacterDataHandler (parser, characterDataCb); + + return parser; +} + +/* Compares items by timestamps so as to sort them in DECREASING order */ +static gint +compareTimestampsCb (gconstpointer a, gconstpointer b, gpointer data) +{ + Item *ia, *ib; + time_t ta, tb; + + ia = a; + ta = ia->timestamp; + + ib = b; + tb = ib->timestamp; + + return (ta < tb) ? 1 : (ta > tb) ? -1 : 0; +} + +/* Returns whether an item has a certain group */ +static gboolean +hasGroup (Item *item, const char *group) +{ + GSList *l; + + for (l = item->groups; l; l = l->next) { + const char *g; + + g = l->data; + if (strcmp (g, group) == 0) + return TRUE; + } + + return FALSE; +} + +/* Removes the oldest items from the context */ +static void +trimOldItems (ParseContext *context) +{ + GSList *l, *next; + int i; + + i = 0; + l = context->items; + + while (l) { + Item *item; + + item = l->data; + next = l->next; + + if (hasGroup (item, GROUP_OPENOFFICE_ORG)) { + i++; + + if (i > MAX_ITEMS) { + freeItem (item); + context->items = g_slist_remove_link (context->items, l); + g_slist_free_1 (l); + } + } + + l = next; + } +} + +/* Parses the file and fills in the context */ +static void +parse (FILE *file, ParseContext *context) +{ + XML_Parser parser; + char buf[BUFFER_SIZE]; + + context->items = NULL; + context->state = STATE_BEGIN; + + parser = createParser (context); + + if (!parser) + return FALSE; + + while (1) { + int len; + gboolean eof; + + len = fread (buf, 1, sizeof (buf), file); + if (ferror (file)) + break; + + eof = feof (file); + + if (!XML_Parse (parser, buf, len, eof)) { + context->state = STATE_ERROR; + break; + } + + if (eof) + break; + } + + if (context->items) + endRecentItem (context); /* Finish it off for if we terminated prematurely on error */ + + XML_ParserFree (parser); + + context->items = g_slist_sort (context->items, compareTimestampsCb); + trimOldItems (context); +} + +/* Frees the contents of a parse context */ +static void +freeContext(ParseContext *context) +{ + GSList *l; + + for (l = context->items; l; l = l->next) { + Item *item; + + item = l->data; + freeItem (item); + } + + g_slist_free (context->items); +} + +/* Converts our items into exported structures */ +static void +convertItems (ParseContext *context, RecentFileItem **items, int *nItems) +{ + GSList *l; + int i; + + /* Count items with the OpenOffice.org group */ + + *nItems = 0; + for (l = context->items; l; l = l->next) { + Item *item; + + item = l->data; + if (hasGroup (item, GROUP_OPENOFFICE_ORG)) + (*nItems)++; + } + + if (*nItems == 0) + return; + + /* Add only the items we want */ + + *items = g_new (RecentFileItem, *nItems); + + i = 0; + + for (l = context->items; l; l = l->next) { + Item *item; + + item = l->data; + + if (hasGroup (item, GROUP_OPENOFFICE_ORG)) { + RecentFileItem *rfi; + + rfi = (*items) + i; + + rfi->uri = NULL; + rtl_string2UString (&rfi->uri, item->uri, strlen (item->uri), RTL_TEXTENCODING_UTF8, 0); + rfi->mimeType = NULL; + rtl_string2UString (&rfi->mimeType, item->mimeType, strlen (item->mimeType), RTL_TEXTENCODING_UTF8, 0); + + rfi->timestamp = item->timestamp; + + i++; + } + } +} + +/* Opens ~/.recent-files and locks it. If something fails, returns NULL. */ +static FILE * +openAndLock (void) +{ + char *filename; + FILE *file; + int fd; + ParseContext context; + + filename = getRecentFilename (); + + file = fopen (filename, "r+"); + g_free (filename); + + if (!file) + return NULL; + + fd = fileno (file); + if (lockf (fd, F_LOCK, 0) != 0) { + fclose (file); + return NULL; + } + + return file; +} + +/* Unlocks ~/.recent-files and closes it. */ +static void +unlockAndClose (FILE *file) +{ + int fd; + + fd = fileno (file); + lockf (fd, F_ULOCK, 0); + fclose (file); +} + + +/** + * recentFilesGetList: + * @items: Return value; pointer to an array of items. Should be freed with recentFilesFree(). + * @nItems: Return value; number of items returned. + * + * Queries the list of GNOME recent file items. + **/ +void +recentFilesGetList (RecentFileItem **items, int *nItems) +{ + FILE *file; + ParseContext context; + + *items = NULL; + *nItems = 0; + + file = openAndLock (); + if (!file) + return; + + parse (file, &context); + + unlockAndClose (file); + + convertItems (&context, items, nItems); + freeContext (&context); +} + +/* Writes the ~/.recent-files file */ +static void +writeXml (FILE *file, ParseContext *context) +{ + GSList *l; + + fputs ("\n" + "<" TAG_RECENT_FILES ">\n", + file); + + for (l = context->items; l; l = l->next) { + Item *item; + GSList *gl; + + item = l->data; + + fputs (" <" TAG_RECENT_ITEM ">\n", file); + fprintf (file, + " <" TAG_URI ">%s\n" + " <" TAG_MIME_TYPE ">%s\n" + " <" TAG_TIMESTAMP ">%ld\n" + "%s" + " <" TAG_GROUPS ">\n", + item->uri, + item->mimeType, + (long) item->timestamp, + item->isPrivate ? " <" TAG_PRIVATE "/>\n" : ""); + + for (gl = item->groups; gl; gl = gl->next) { + const char *group; + + group = gl->data; + fprintf (file, + " <" TAG_GROUP ">%s\n", + group); + } + + fputs (" \n" + " \n", + file); + } + + fputs ("\n", file); +} + +/** + * recentFilesFree: + * @items: Array of items. + * @nItems: Number of items in the array. + * + * Frees an array of #RecentFileItem structures as returned by recentFilesGetList(). + **/ +void +recentFilesFree (RecentFileItem *items, int nItems) +{ + int i; + + for (i = 0; i < nItems; i++) { + rtl_uString_release (items[i].uri); + rtl_uString_release (items[i].mimeType); + } + + g_free (items); +} + +/** + * recentFilesAddItem: + * @uri: URI to add. + * @mimeType: MIME type of the item. + * + * Adds an item to GNOME's recent files list. Automatically adds the "OpenOffice.org" group. + **/ +void +recentFilesAddItem (rtl_uString *uri, const rtl_uString *mimeType) +{ + FILE *file; + ParseContext context; + GSList *l; + gboolean alreadyExists; + int fd; + rtl_String *uriString; + rtl_String *mimeTypeString; + + file = openAndLock (); + if (!file) + return; + + parse (file, &context); + + alreadyExists = FALSE; + + uriString = NULL; + mimeTypeString = NULL; + rtl_uString2String (&uriString, uri->buffer, uri->length, RTL_TEXTENCODING_UTF8, 0); + rtl_uString2String (&mimeTypeString, mimeType->buffer, mimeType->length, RTL_TEXTENCODING_UTF8, 0); + + for (l = context.items; l; l = l->next) { + Item *item; + + item = l->data; + if (strcmp (item->uri, uriString->buffer) == 0) { + g_free (item->mimeType); + item->mimeType = g_strdup (mimeTypeString->buffer); + item->timestamp = time (NULL); + alreadyExists = TRUE; + break; + } + } + + if (!alreadyExists) { + Item *item; + + item = g_new (Item, 1); + item->uri = g_strdup (uriString->buffer); + item->mimeType = g_strdup (mimeTypeString->buffer); + item->timestamp = time (NULL); + item->isPrivate = FALSE; + item->groups = g_slist_prepend (NULL, g_strdup (GROUP_OPENOFFICE_ORG)); + + context.items = g_slist_prepend (context.items, item); + } + + rtl_string_release (uriString); + rtl_string_release (mimeTypeString); + + fd = fileno (file); + + /* FIXME: perhaps we should do a write to a temporary file, then do an atomic rename() */ + + rewind (file); + if (ftruncate (fd, 0) != 0) + goto out; + + writeXml (file, &context); + +out: + + unlockAndClose (file); + freeContext (&context); +} + +};