/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* If you add code to this file, make sure it still builds on win16 debug - its * code segment is too big. */ #include "bkmks.h" #include "net.h" #include "xp_mcom.h" #include "client.h" #include "msgcom.h" #include "undo.h" #include "xp_hash.h" #include "xpgetstr.h" #include "glhist.h" #include "libi18n.h" #include "xp_qsort.h" #include "intl_csi.h" #ifdef XP_MAC #pragma warn_unusedarg off #pragma warn_possunwant off #endif #define INTL_SORT 1 #ifdef INTL_SORT /* Added by ftang */ #include "xplocale.h" #endif extern int MK_OUT_OF_MEMORY; extern int XP_BKMKS_BOOKMARKS_CHANGED; extern int XP_BKMKS_ADDRESSBOOK_CHANGED; extern int XP_BKMKS_BOOKMARKS_CONFLICT; extern int XP_BKMKS_ADDRESSBOOK_CONFLICT; extern int XP_BKMKS_CANT_WRITE_ADDRESSBOOK; extern int XP_BKMKS_CANT_WRITE_BOOKMARKS; /* added by L10N team */ extern int XP_BKMKS_HOURS_AGO; extern int XP_BKMKS_DAYS_AGO; extern int XP_BKMKS_COUNTALIASES_MANY; extern int XP_BKMKS_COUNTALIASES_ONE; extern int XP_BKMKS_COUNTALIASES_NONE; extern int XP_BKMKS_INVALID_NICKNAME; extern int XP_BKMKS_NICKNAME_ALREADY_EXISTS; extern int XP_BKMKS_REMOVE_THIS_ITEMS_ALIASES; extern int XP_BKMKS_REMOVE_SOME_ITEMS_ALIASES; extern int XP_BKMKS_AUTOGENERATED_FILE; extern int XP_BKMKS_READ_AND_OVERWRITE; extern int XP_BKMKS_DO_NOT_EDIT; extern int XP_BKMKS_NEW_HEADER; extern int XP_BKMKS_NEW_BOOKMARK; extern int XP_BKMKS_NOT_FOUND; extern int XP_BKMKS_OPEN_BKMKS_FILE; extern int XP_BKMKS_IMPORT_BKMKS_FILE; extern int XP_BKMKS_IMPORT_ADDRBOOK; extern int XP_BKMKS_SAVE_BKMKS_FILE; extern int XP_BKMKS_SAVE_ADDRBOOK; extern int XP_BKMKS_LESS_THAN_ONE_HOUR_AGO; extern int XP_BKMKS_SOMEONE_S_BOOKMARKS; /* "%s%s's Bookmarks%s" */ extern int XP_BKMKS_PERSONAL_BOOKMARKS; /* "%sPersonal Bookmarks%s" */ extern int XP_BKMKS_SOMEONE_S_ADDRESSBOOK; /* "%s%s's Addressbook%s" */ extern int XP_BKMKS_PERSONAL_ADDRESSBOOK; /* "%sPersonal Addressbook%s" */ extern int XP_BKMKS_BOOKMARK; extern int XP_BKMKS_ENTRY; extern int XP_BKMKS_SECONDS; extern int XP_BKMKS_MINUTES; extern int XP_BKMKS_HOURS_MINUTES; extern int XP_BKMKS_HEADER; extern int XP_ADDRBOOK_HEADER; #define SECONDS_PER_DAY 86400L #define BMLIST_COOKIE "" #define BM_ADDR_LIST_COOKIE "" #define READ_BUFFER_SIZE 2048 #define WRITE_BUFFER_SIZE 1848 /* Make this a few hundred less than read buffer so we can be sure to read the bookmarks back properly */ #define DEF_NAME "Personal Bookmarks" /* L10N? This doesn't seem to be used. */ #ifdef FREEIF #undef FREEIF #endif #define FREEIF(obj) do { if (obj) { XP_FREE (obj); obj = 0; }} while (0) #define BM_ATTR_FOLDED 0x0001 #define BM_ATTR_SELECTED 0x0002 #define BM_ATTR_ISNEW 0x0004 #define BM_ATTR_FINDAFF 0x0008 /* This entry is temporarily unfolded only as a result of the last find operation. */ #define BM_ATTR_HASALIASES 0x0010 /* Actually, being set only means that this entry *might* have aliases. But if an entry has aliases, this bit is definitely set. */ #define BM_ATTR_MARKED 0x0020 /* Random bit to temporarily mark a bunch of items. */ #define BM_ATTR_CHECKING 0x0040 /* In the midst of checking whether this entry has changed. */ #define BM_ISHEADER(bmStruct) \ ((bmStruct) && ((bmStruct)->type == BM_TYPE_HEADER)) #define BM_ISURL(bmStruct) \ ((bmStruct) && ((bmStruct)->type == BM_TYPE_URL)) #define BM_ISADDRESS(bmStruct) \ ((bmStruct) && ((bmStruct)->type == BM_TYPE_ADDRESS)) #define BM_ISSEPARATOR(bmStruct) \ ((bmStruct) && ((bmStruct)->type == BM_TYPE_SEPARATOR)) #define BM_ISALIAS(bmStruct) \ ((bmStruct) && ((bmStruct)->type == BM_TYPE_ALIAS)) #define BM_ISFOLDED(bmStruct) \ ((bmStruct) && ((bmStruct)->flags & BM_ATTR_FOLDED)) #define BM_ISSELECTED(bmStruct) \ ((bmStruct) && ((bmStruct)->flags & BM_ATTR_SELECTED)) #define BM_SETFLAG(bmStruct, flag) \ ((bmStruct) ? ((bmStruct)->flags |= (flag)) : 0) #define BM_CLEARFLAG(bmStruct, flag) \ ((bmStruct) ? ((bmStruct)->flags &= ~(flag)) : 0) static int32 g_iNaturalIndexPool = 0; struct BM_Entry_struct { BM_Type type; uint16 flags; BM_Entry* next; BM_Entry* parent; char* name; char* description; BM_Date addition_date; int32 iNaturalIndex; /* Index for user arranged "sort" */ char* nickname; /* Used only by address book, alas. */ union { struct BM_Header { char* target; /* target */ BM_Entry* children; /* a linked list of my children */ uint32 childCount; /* the number of "children" */ BM_Entry* lastChild; /* the last child in "children" */ } header; struct BM_Url { char* address; /* address */ char* target; /* target */ char* content_type; /* content-type */ BM_Date last_visit; /* when last visited */ BM_Date last_modified; /* when the contents of the URL was last modified. */ } url; struct BM_Address { char* address; /* e-mail address */ } address; struct BM_Alias { BM_Entry* original; /* the original bm */ } alias; } d; }; typedef struct BM_Frame { BM_Entry* gBookmarks; /* root of the tree */ XP_Bool gBookmarksModified; /* TRUE if the tree is modified */ int32 gCount; /* number of entries in tree. If negative, then need to be recalculated. */ int32 gVisCount; /* number of visible entries. If negative, then need to be recalculated. */ int32 gSelectionCount; /* number of selected items. If negative, then we're out of sync, call bm_SyncSelection to synchronize. */ uint32 gSelectionMask; /* what types of items are selected (invalid if gSelectionCount is negative.)*/ BM_FindInfo* gFindInfo; /* information for the find dialog */ char* gFile; /* the file this data is from */ void* gTemporary; void* feData; int32 max_depth; /* The number of levels in the heirarchy that we currently display. (If zero, then we don't know. */ UndoState* undo; MWContext* next; /* Next bookmarks context in master linked list. */ XP_Bool unfoldedForFind; /* TRUE if some headers have been unfolded as the result of a find operation. The headers in question will have the BM_ATTR_FINDAFF flag set.*/ BM_SortType enSortType; /* the sort field (a column or natural) */ XP_Bool bSorting; XP_HashTable aliasTable; XP_HashTable nicknameTable; int aliasID; XP_Bool errorSavingBookmarks; int32 batch_depth; int32 first_update_line; int32 last_update_line; void* savetimer; BM_Entry* lastSelectedItem; BM_Entry* menuheader; /* Which header to use as the menu bar. */ BM_Entry* addheader; /* Which entry to add new items into. */ XP_StatStruct laststat; /* Stat of the file when we loaded it in. */ struct BM_WhatsChangedInfo { int32 total; int32 numreached; int32 numchanged; time_t starttime; } whatschanged; } BM_Frame; #define CHKCONTEXT(context) \ XP_ASSERT(context != NULL && (context->type == MWContextAddressBook || \ context->type == MWContextBookmarks) && \ context->bmframe != NULL); \ if (!context || (context->type != MWContextAddressBook && \ context->type != MWContextBookmarks) || \ !context->bmframe) return 0; #define CHKCONTEXTVOID(context) \ XP_ASSERT(context != NULL && (context->type == MWContextAddressBook || \ context->type == MWContextBookmarks) && \ context->bmframe != NULL); \ if (!context || (context->type != MWContextAddressBook && \ context->type != MWContextBookmarks) || \ !context->bmframe) return; /* Should probably be a macro, but putting the assert in the macro and making this still be easy to use is just too painful. */ static BM_Frame* GETFRAME(MWContext* context) { XP_ASSERT(context && (context->type == MWContextBookmarks || context->type == MWContextAddressBook) && context->bmframe != NULL); return (context && (context->type == MWContextBookmarks || context->type == MWContextAddressBook)) ? context->bmframe : NULL; } static void bm_CancelLastFind(MWContext* context); static void bm_InsertItemAfter(MWContext* context, BM_Entry* insert_after, BM_Entry* insertee, XP_Bool sync); static void bm_SortSelected(MWContext* context, BM_SortType enSortType); static void bm_SyncCount(MWContext* context); static void bm_AddChildToHeaderSorted(MWContext* context, BM_Entry* parent, BM_Entry* child); static void bm_SortSilent(MWContext* context, BM_SortType enSortType ); XP_Bool BM_IsHeader(BM_Entry* entry) { return BM_ISHEADER(entry); } XP_Bool BM_IsUrl(BM_Entry* entry) { return BM_ISURL(entry); } XP_Bool BM_IsAddress(BM_Entry* entry) { return BM_ISADDRESS(entry); } XP_Bool BM_IsSeparator(BM_Entry* entry) { return BM_ISSEPARATOR(entry); } XP_Bool BM_IsAlias(BM_Entry* entry) { return BM_ISALIAS(entry); } XP_Bool BM_IsFolded(BM_Entry* entry) { return BM_ISFOLDED(entry); } XP_Bool BM_IsSelected(BM_Entry* entry) { return BM_ISSELECTED(entry); } int32 BM_GetChangedState(BM_Entry* entry) { if (BM_ISALIAS(entry)) { entry = entry->d.alias.original; } if (BM_ISURL(entry)) { if (entry->d.url.last_modified == 0 || entry->flags & BM_ATTR_CHECKING) { return BM_CHANGED_UNKNOWN; } if (entry->d.url.last_visit < entry->d.url.last_modified) { return BM_CHANGED_YES; } } return BM_CHANGED_NO; } /* globals */ static MWContext* ContextList = NULL; void BM_SetFEData(MWContext* context, void* data) { BM_Frame* f = GETFRAME(context); CHKCONTEXTVOID(context); f->feData = data; } void* BM_GetFEData(MWContext* context) { BM_Frame* f = GETFRAME(context); CHKCONTEXT(context); return f->feData; } /* creates a new empty bookmark entry */ static BM_Entry* bm_NewEntry(int16 type) { BM_Entry* new_entry = XP_NEW_ZAP(BM_Entry); if (!new_entry) return NULL; XP_MEMSET(new_entry, 0, sizeof(BM_Entry)); new_entry->type = type; return new_entry; } /* creates a new header bookmarks entry */ BM_Entry* BM_NewHeader(const char* name) { BM_Entry* header; header = bm_NewEntry(BM_TYPE_HEADER); if (!header) return NULL; StrAllocCopy(header->name, name); return header; } /* creates a new URL bookmarks entry */ BM_Entry* BM_NewUrl(const char* name, const char* address, const char* content_type, BM_Date last_visit) { BM_Entry* url; url = bm_NewEntry(BM_TYPE_URL); if (!url) return NULL; StrAllocCopy(url->name, name); url->description = NULL; StrAllocCopy(url->d.url.address, address); StrAllocCopy(url->d.url.content_type, content_type); url->d.url.last_visit = last_visit; url->d.url.last_modified = last_visit; /* ### Is this right? */ url->addition_date = 0; return url; } /* creates a new URL bookmarks entry */ static BM_Entry* bm_NewAddress(const char* name, const char* address) { BM_Entry* add_struct; add_struct = bm_NewEntry(BM_TYPE_ADDRESS); if (!add_struct) return NULL; StrAllocCopy(add_struct->name, name ? name : ""); StrAllocCopy(add_struct->d.address.address, address ? address : ""); return add_struct; } /* creates a new separator bookmarks entry */ static BM_Entry* bm_NewSeparator(void) { return bm_NewEntry(BM_TYPE_SEPARATOR); } /* creates a new alias bookmarks entry */ static BM_Entry* bm_NewAlias(BM_Entry* original) { BM_Entry* alias; alias = bm_NewEntry(BM_TYPE_ALIAS); alias->d.alias.original = original; BM_SETFLAG(original, BM_ATTR_HASALIASES); return alias; } extern BM_Entry* BM_CopyBookmark(MWContext* context, BM_Entry* original) { BM_Entry* copy; BM_Entry* child; BM_Entry* childCopy; copy = NULL; if(!original) return NULL; switch (original->type) { case BM_TYPE_URL: copy = BM_NewUrl(original->name, original->d.url.address, original->d.url.content_type, original->d.url.last_visit); break; case BM_TYPE_ADDRESS: copy = bm_NewAddress(original->name, original->d.address.address); break; case BM_TYPE_SEPARATOR: copy = bm_NewSeparator(); break; case BM_TYPE_ALIAS: copy = bm_NewAlias(original->d.alias.original); break; case BM_TYPE_HEADER: copy = BM_NewHeader(original->name); child = original->d.header.children; while (child) { childCopy = BM_CopyBookmark(context, child); if(childCopy) BM_AppendToHeader(context, copy, childCopy); child = child->next; } break; } if(copy) { StrAllocCopy(copy->description, original->description); StrAllocCopy(copy->nickname, original->nickname); } return copy; } /* gets the top node of the bmlist */ BM_Entry* BM_GetRoot(MWContext* context) { BM_Frame* f = GETFRAME(context); CHKCONTEXT(context); if (!f) return NULL; if (!f->gBookmarks) { f->gBookmarks = BM_NewHeader(context->type == MWContextBookmarks ? XP_GetString(XP_BKMKS_HEADER) : XP_GetString(XP_ADDRBOOK_HEADER)); if (context->type == MWContextBookmarks) { f->menuheader = f->addheader = f->gBookmarks; } bm_SyncCount(context); } else { if (f->gBookmarks->parent != NULL) XP_ASSERT(f->gBookmarks->next == NULL); /* Stupid consistancy test; dunno what action to take if this isn't so. */ } return f->gBookmarks; } static void bm_EachEntryDo_1(MWContext* context, BM_Entry* at, EntryFunc func, void* closure) { BM_Entry* nextChild; BM_Entry* children; while (at) { nextChild = at->next; if (BM_ISHEADER(at)) { children = at->d.header.children; } else { children = NULL; } (*func)(context, at, closure); if (children) { bm_EachEntryDo_1(context, children, func, closure); } at = nextChild; } } static void bm_EachSelectedEntryDo_1(MWContext* context, BM_Entry* at, EntryFunc func, void* closure) { BM_Entry* nextChild; BM_Entry* children; while (at) { nextChild = at->next; if (BM_ISHEADER(at)) { children = at->d.header.children; } else { children = NULL; } if (BM_ISSELECTED(at)) (*func)(context, at, closure); if (children) { bm_EachSelectedEntryDo_1(context, children, func, closure); } at = nextChild; } } static void bm_EachProperSelectedEntryDo_1(MWContext* context, BM_Entry* at, EntryFunc func, void* closure, struct BM_Entry_Focus* bmFocus) { BM_Entry* child; BM_Entry* nextChild; XP_ASSERT(at); if (!at) return; XP_ASSERT(BM_ISHEADER(at)); child = at->d.header.children; while (child) { nextChild = child->next; switch (child->type) { case BM_TYPE_URL: case BM_TYPE_ADDRESS: case BM_TYPE_SEPARATOR: case BM_TYPE_ALIAS: if (BM_ISSELECTED(child)) { if ((bmFocus != NULL) && !bmFocus->foundSelection) bmFocus->foundSelection = TRUE; (*func)(context, child, closure); } else if (bmFocus && !bmFocus->foundSelection) { bmFocus->saveFocus = child; } break; case BM_TYPE_HEADER: if (BM_ISSELECTED(child)) { if ((bmFocus != NULL) && !bmFocus->foundSelection) bmFocus->foundSelection = TRUE; (*func)(context, child, closure); } else { if (bmFocus && !bmFocus->foundSelection) bmFocus->saveFocus = child; if (! BM_ISFOLDED(child)) { bm_EachProperSelectedEntryDo_1(context, child, func, closure, bmFocus); } } break; } child = nextChild; } } void BM_EachEntryDo(MWContext* context, EntryFunc func, void* closure) { CHKCONTEXTVOID(context); bm_EachEntryDo_1(context, BM_GetRoot(context), func, closure); } void BM_EachSelectedEntryDo(MWContext* context, EntryFunc func, void* closure) { CHKCONTEXTVOID(context); bm_EachSelectedEntryDo_1(context, BM_GetRoot(context), func, closure); } void BM_EachProperSelectedEntryDo(MWContext* context, EntryFunc func, void* closure, struct BM_Entry_Focus* bmFocus) { CHKCONTEXTVOID(context); bm_EachProperSelectedEntryDo_1(context, BM_GetRoot(context), func, closure, bmFocus); } static void bm_ClearMarkEverywhere_1(MWContext* context, BM_Entry* entry, void* closure) { BM_CLEARFLAG(entry, BM_ATTR_MARKED); } static void bm_ClearMarkEverywhere(MWContext* context) { BM_EachEntryDo(context, bm_ClearMarkEverywhere_1, NULL); } static MWContext* bm_GetContextForEntry(BM_Entry* entry) { MWContext* result; BM_Frame* f; while (entry->parent) entry = entry->parent; for (result = ContextList ; result ; result = f->next) { f = GETFRAME(result); if (f->gBookmarks == entry) return result; } return NULL; } BM_Type BM_GetType(BM_Entry* entry) { XP_ASSERT(entry); return entry ? entry->type : 0; } /* return the "name" for item -- may vary depending on its type */ char* BM_GetName(BM_Entry* entry) { XP_ASSERT(entry); if (!entry) return NULL; switch (entry->type) { case BM_TYPE_URL: case BM_TYPE_HEADER: case BM_TYPE_ADDRESS: return entry->name; case BM_TYPE_ALIAS: if (entry->d.alias.original) return BM_GetName(entry->d.alias.original); else return entry->name; default: return NULL; } } /* return the "address" for item -- may vary depending on its type */ char* BM_GetAddress(BM_Entry* entry) { XP_ASSERT(entry); if (!entry) return NULL; switch (entry->type) { case BM_TYPE_URL: return entry->d.url.address; case BM_TYPE_ADDRESS: return entry->d.address.address; case BM_TYPE_HEADER: case BM_TYPE_SEPARATOR: case BM_TYPE_ALIAS: if (entry->d.alias.original) return BM_GetAddress(entry->d.alias.original); return NULL; default: return NULL; } } /* return the "target" for item -- may vary depending on its type */ char* BM_GetTarget(BM_Entry* entry, XP_Bool recurse) { XP_ASSERT(entry); if (!entry) return NULL; switch (entry->type) { case BM_TYPE_URL: if ((recurse)&&(entry->d.url.target == NULL)&&(entry->parent != NULL)) { return BM_GetTarget(entry->parent, recurse); } else { return entry->d.url.target; } case BM_TYPE_HEADER: if ((recurse)&&(entry->d.header.target == NULL)&&(entry->parent != NULL)) { return BM_GetTarget(entry->parent, recurse); } else { return entry->d.header.target; } case BM_TYPE_ADDRESS: case BM_TYPE_SEPARATOR: return NULL; case BM_TYPE_ALIAS: if (entry->d.alias.original) return BM_GetTarget(entry->d.alias.original, recurse); return NULL; default: return NULL; } } /* return the "description" for item -- may vary depending on its type */ char* BM_GetDescription(BM_Entry* entry) { XP_ASSERT(entry); if (!entry) return NULL; switch (entry->type) { case BM_TYPE_URL: case BM_TYPE_HEADER: case BM_TYPE_ADDRESS: return entry->description; default: return NULL; } } char* BM_GetNickName(BM_Entry* entry) { XP_ASSERT(bm_GetContextForEntry(entry)->type == MWContextAddressBook); if (BM_ISALIAS(entry)) return BM_GetNickName(entry->d.alias.original); else return entry->nickname; } PRIVATE int32 bm_CountAliases_1(BM_Entry* at, BM_Entry* forEntry) { int32 count = 0; for ( ; at ; at = at->next) { if (BM_ISHEADER(at)) { count += bm_CountAliases_1(at->d.header.children, forEntry); } else if (BM_ISALIAS(at)) { if (at->d.alias.original == forEntry) count++; } } return count; } PUBLIC int32 BM_CountAliases(MWContext* context, BM_Entry* entry) { int32 result; CHKCONTEXT(context); result = bm_CountAliases_1(BM_GetRoot(context), entry); if (result) { XP_ASSERT(entry->flags & BM_ATTR_HASALIASES); BM_SETFLAG(entry, BM_ATTR_HASALIASES); } else { BM_CLEARFLAG(entry, BM_ATTR_HASALIASES); } return result; } BM_Date BM_GetLastVisited(BM_Entry *entry) { XP_ASSERT(entry); if (!entry || (entry->type != BM_TYPE_URL)) return 0; return entry->d.url.last_visit; } BM_Date BM_GetAdditionDate(BM_Entry *entry) { XP_ASSERT(entry); if (!entry) return 0; return entry->addition_date; } /* pretty print the last visited date ### fix i18n */ char* BM_PrettyLastVisitedDate(BM_Entry* entry) { static char buffer[200]; buffer[0] = 0; XP_ASSERT(entry); if (!entry) return NULL; if (entry->type == BM_TYPE_URL) { time_t lastVisited; time_t today; time_t elapsed; lastVisited = entry->d.url.last_visit; if (lastVisited == 0) return ""; today = XP_TIME(); elapsed = today - lastVisited; if (elapsed < SECONDS_PER_DAY) { int32 hours = (elapsed + 1800L) / 3600L; if (hours < 1) { return XP_GetString(XP_BKMKS_LESS_THAN_ONE_HOUR_AGO); } sprintf(buffer, XP_GetString(XP_BKMKS_HOURS_AGO), hours); } else if (elapsed < (SECONDS_PER_DAY * 31)) { sprintf(buffer, XP_GetString(XP_BKMKS_DAYS_AGO), (elapsed + (SECONDS_PER_DAY / 2)) / SECONDS_PER_DAY); } else { struct tm* tmp; tmp = localtime(&lastVisited); sprintf(buffer, asctime(tmp)); } return buffer; } return NULL; } /* pretty print the added on date */ char* BM_PrettyAddedOnDate(BM_Entry* entry) { static char buffer[200]; struct tm* tmp; XP_ASSERT(entry); if (entry && entry->addition_date != 0) { tmp = localtime(&(entry->addition_date)); sprintf(buffer, asctime(tmp)); return buffer; } return NULL; } char* BM_PrettyAliasCount(MWContext* context, BM_Entry* entry) { static char buffer[100]; int32 count; char* name = context->type == MWContextBookmarks ? XP_GetString(XP_BKMKS_BOOKMARK) : XP_GetString(XP_BKMKS_ENTRY); CHKCONTEXT(context); XP_ASSERT(entry); if (!entry) return NULL; count = BM_CountAliases(context, entry); buffer[0] = 0; if (count > 1) { sprintf(buffer, XP_GetString(XP_BKMKS_COUNTALIASES_MANY), count, name); } else if (count == 1) { sprintf(buffer, XP_GetString(XP_BKMKS_COUNTALIASES_ONE), name); } else { sprintf(buffer, XP_GetString(XP_BKMKS_COUNTALIASES_NONE), name); } return buffer; } BM_Entry* BM_GetChildren(BM_Entry* entry) { if (BM_ISHEADER(entry)) return entry->d.header.children; return NULL; } BM_Entry* BM_GetNext(BM_Entry* entry) { XP_ASSERT(entry); return entry ? entry->next : NULL; } BM_Entry* BM_GetParent(BM_Entry* entry) { XP_ASSERT(entry); return entry ? entry->parent : NULL; } XP_Bool BM_HasNext(BM_Entry* entry) { XP_ASSERT(entry); return entry ? (entry->next != NULL) : FALSE; } XP_Bool BM_HasPrev(BM_Entry* entry) { BM_Entry* parent = entry->parent; if (parent) { XP_ASSERT(BM_ISHEADER(parent)); return parent->d.header.children != entry; } else { return GETFRAME(bm_GetContextForEntry(entry))->gBookmarks != entry; } } static void bm_flush_updates(MWContext* context) { BM_Frame* f = GETFRAME(context); CHKCONTEXTVOID(context); if (f->first_update_line > 0) { BMFE_RefreshCells(context, f->first_update_line, f->last_update_line, FALSE); f->first_update_line = 0; } } static void bm_start_batch(MWContext* context) { BM_Frame* f = GETFRAME(context); #ifdef XP_UNIX BMFE_StartBatch(context); #endif CHKCONTEXTVOID(context); if (f->undo) UNDO_StartBatch(f->undo); f->batch_depth++; } static void bm_end_batch(MWContext* context) { BM_Frame* f = GETFRAME(context); CHKCONTEXTVOID(context); f->batch_depth--; XP_ASSERT(f->batch_depth >= 0); if (f->batch_depth == 0) { bm_flush_updates(context); } if (f->undo) UNDO_EndBatch(f->undo, NULL, NULL); #ifdef XP_UNIX BMFE_EndBatch(context); #endif } static void bm_refresh(MWContext* context, int32 first, int32 last) { BM_Frame* f = GETFRAME(context); CHKCONTEXTVOID(context); XP_ASSERT(first >= 1 && first <= last); if (first < 1 || first > last) { /* Something bogus got passed in; just repaint everything to be safe. */ first = 1; last = BM_LAST_CELL; } if (f->first_update_line <= 0 || first > f->last_update_line + 1 || last + 1 < f->first_update_line) { bm_flush_updates(context); f->first_update_line = first; f->last_update_line = last; } else { if (f->first_update_line > first) f->first_update_line = first; if (f->last_update_line < last) f->last_update_line = last; } if (f->batch_depth == 0) bm_flush_updates(context); } /* Handy routine to detect if we're already going to refresh everything. If so, then the caller knows there's no need to refresh any more... */ static XP_Bool bm_refreshing_all(MWContext* context) { BM_Frame* f = GETFRAME(context); return (f != NULL && f->first_update_line == 1 && f->last_update_line == BM_LAST_CELL); } static void bm_entry_changed_2(MWContext* context, BM_Entry* entry) { int32 index = BM_GetIndex(context, entry); if (index < 1) return; if (context->type == MWContextBookmarks || entry->parent == NULL) { bm_refresh(context, index, index); } else { /* Changing the entry might have messed up the sorting order. Better go resort it. What a hack...*/ BM_Entry* parent = entry->parent; BM_RemoveChildFromHeader(context, parent, entry); bm_AddChildToHeaderSorted(context, parent, entry); } } static void bm_entry_changed_1(MWContext* context, BM_Entry* entry, BM_Entry* find) { for (; entry ; entry = entry->next) { if (BM_ISALIAS(entry) && entry->d.alias.original == find) { bm_entry_changed_2(context, entry); } else if (BM_ISHEADER(entry)) { bm_entry_changed_1(context, entry->d.header.children, find); } } } static void bm_entry_changed(MWContext* context, BM_Entry* entry) { XP_ASSERT(!BM_ISALIAS(entry)); if (entry->flags & BM_ATTR_HASALIASES) { bm_entry_changed_1(context, BM_GetRoot(context), entry); } bm_entry_changed_2(context, entry); } static void bm_save_timer(void* closure) { MWContext* context = (MWContext*) closure; BM_Frame* f = GETFRAME(context); f->savetimer = NULL; BM_SaveBookmarks(context, NULL); } /* The bookmarks have been modified somehow. Set or reset a timer to cause them to be saved.*/ static void bm_SetModified(MWContext* context, XP_Bool mod) { BM_Frame* f = GETFRAME(context); f->gBookmarksModified = mod; f->max_depth = 0; if (f->savetimer) { FE_ClearTimeout(f->savetimer); f->savetimer = NULL; } if (mod) { f->savetimer = FE_SetTimeout(bm_save_timer, context, 60000L); /* ### hard-coding... */ if (!f->savetimer) BM_SaveBookmarks(context, NULL); } } /* give LI the ability to set the modified to false */ void BM_SetModified(MWContext* context, XP_Bool mod) { bm_SetModified(context, mod); } typedef struct bm_setheader_info { MWContext* context; BM_Entry* entry; XP_Bool isadd; } bm_setheader_info; static void bm_setheader_freeit(void* closure) { XP_FREE((bm_setheader_info*) closure); } static int bm_setheader_undo(void* closure); static void bm_SetMenuOrAddHeader(MWContext* context, BM_Entry* entry, XP_Bool isadd) { BM_Frame* f = GETFRAME(context); XP_ASSERT(context->type == MWContextBookmarks); XP_ASSERT(BM_ISHEADER(entry)); if (context->type == MWContextBookmarks && f && BM_ISHEADER(entry)) { if (f->undo) { bm_setheader_info* info = XP_NEW_ZAP(bm_setheader_info); if (!info) { UNDO_DiscardAll(f->undo); } else { info->context = context; info->entry = isadd ? f->addheader : f->menuheader; info->isadd = isadd; UNDO_LogEvent(f->undo, bm_setheader_undo, bm_setheader_freeit, info, NULL, NULL); } } bm_start_batch(context); bm_entry_changed(context, isadd ? f->addheader : f->menuheader); if (isadd) f->addheader = entry; else f->menuheader = entry; bm_entry_changed(context, entry); if (!isadd) BMFE_BookmarkMenuInvalid(context); bm_SetModified(context, TRUE); bm_end_batch(context); } } int bm_setheader_undo(void* closure) { bm_setheader_info* info = (bm_setheader_info*) closure; bm_SetMenuOrAddHeader(info->context, info->entry, info->isadd); return 0; } BM_Entry* BM_GetMenuHeader(MWContext* context) { BM_Frame* f = GETFRAME(context); XP_ASSERT(context->type == MWContextBookmarks); return f ? f->menuheader : NULL; } void BM_SetMenuHeader(MWContext* context, BM_Entry* entry) { bm_SetMenuOrAddHeader(context, entry, FALSE); } BM_Entry* BM_GetAddHeader(MWContext* context) { BM_Frame* f = GETFRAME(context); XP_ASSERT(context->type == MWContextBookmarks); return f ? f->addheader : NULL; } void BM_SetAddHeader(MWContext* context, BM_Entry* entry) { bm_SetMenuOrAddHeader(context, entry, TRUE); } typedef struct bm_copy_string_info { MWContext* context; BM_Entry* entry; char** string; char* value; } bm_copy_string_info; static void bm_copy_string_freeit(void* closure) { bm_copy_string_info* info = (bm_copy_string_info*) closure; FREEIF(info->value); XP_FREE(info); } static int bm_copy_string_undo(void* closure); static int bm_CopyStringWithUndo(MWContext* context, BM_Entry* entry, char** string, const char* value) { BM_Frame* f = GETFRAME(context); int status = 0; bm_SetModified(context, TRUE); if (f->undo) { bm_copy_string_info* info = XP_NEW_ZAP(bm_copy_string_info); if (!info) { UNDO_DiscardAll(f->undo); status = MK_OUT_OF_MEMORY; } else { info->context = context; info->entry = entry; info->string = string; info->value = *string ? XP_STRDUP(*string) : NULL; UNDO_LogEvent(f->undo, bm_copy_string_undo, bm_copy_string_freeit, info, NULL, NULL); } } if (*string) XP_FREE(*string); *string = value ? XP_STRDUP(value) : NULL; bm_entry_changed(context, entry); return 0; } static int bm_copy_string_undo(void* closure) { bm_copy_string_info* info = (bm_copy_string_info*) closure; if (info->string == &(info->entry->nickname)) { /* Have to use BM_SetNickName to get side effect of changing hashtable. */ BM_SetNickName(info->context, info->entry, info->value); } else { bm_CopyStringWithUndo(info->context, info->entry, info->string, info->value); } return 0; } /* sets the name for a bm entry */ void BM_SetName(MWContext* context, BM_Entry* entry, const char* newName) { CHKCONTEXTVOID(context); XP_ASSERT(entry); if (!entry) return; BM_CLEARFLAG(entry, BM_ATTR_ISNEW); switch (entry->type) { case BM_TYPE_URL: case BM_TYPE_HEADER: case BM_TYPE_ADDRESS: if (entry->name == NULL || XP_STRCMP(entry->name, newName) != 0) { bm_CopyStringWithUndo(context, entry, &entry->name, newName); BMFE_BookmarkMenuInvalid(context); } break; case BM_TYPE_ALIAS: BM_SetName(context, entry->d.alias.original, newName); break; } } /* sets the location field of a bm_url bookmarks entry */ void BM_SetAddress(MWContext* context, BM_Entry* entry, const char* newAddress) { CHKCONTEXTVOID(context); XP_ASSERT(entry); if (!entry) return; BM_CLEARFLAG(entry, BM_ATTR_ISNEW); switch (entry->type) { case BM_TYPE_URL: if (entry->d.url.address == NULL || XP_STRCMP(entry->d.url.address, newAddress) != 0) { bm_CopyStringWithUndo(context, entry, &entry->d.url.address, newAddress); } break; case BM_TYPE_ADDRESS: if (entry->d.address.address == NULL || XP_STRCMP(entry->d.address.address, newAddress) != 0) { bm_CopyStringWithUndo(context, entry, &entry->d.address.address, newAddress); } break; case BM_TYPE_ALIAS: BM_SetAddress(context, entry->d.alias.original, newAddress); break; } } /* sets the target field of a bm_url bookmarks entry */ void BM_SetTarget(MWContext* context, BM_Entry* entry, const char* newTarget) { CHKCONTEXTVOID(context); XP_ASSERT(entry); if (!entry) return; BM_CLEARFLAG(entry, BM_ATTR_ISNEW); switch (entry->type) { case BM_TYPE_URL: if (entry->d.url.target == NULL || XP_STRCMP(entry->d.url.target, newTarget) != 0) { bm_CopyStringWithUndo(context, entry, &entry->d.url.target, newTarget); if (entry->d.url.target[0] == '\0') { entry->d.url.target = NULL; } } break; case BM_TYPE_HEADER: if (entry->d.header.target == NULL || XP_STRCMP(entry->d.header.target, newTarget) != 0) { bm_CopyStringWithUndo(context, entry, &entry->d.header.target, newTarget); if (entry->d.header.target[0] == '\0') { entry->d.header.target = NULL; } } break; case BM_TYPE_ADDRESS: break; case BM_TYPE_ALIAS: BM_SetAddress(context, entry->d.alias.original, newTarget); break; } } /* sets the description field of an entry */ PUBLIC void BM_SetDescription(MWContext* context, BM_Entry* entry, const char* newDesc) { CHKCONTEXTVOID(context); XP_ASSERT(entry); if (!entry) return; BM_CLEARFLAG(entry, BM_ATTR_ISNEW); switch (entry->type) { case BM_TYPE_URL: case BM_TYPE_HEADER: case BM_TYPE_ADDRESS: if (entry->description == NULL || XP_STRCMP(entry->description, newDesc)) { bm_CopyStringWithUndo(context, entry, &entry->description, newDesc); } break; case BM_TYPE_ALIAS: BM_SetDescription(context, entry->d.alias.original, newDesc); break; } } /* BM_SetNickName returns FALSE if it reported an error to the user . * It returns TRUE if everything worked fine. * 5-16-95 jefft * Passing in NULL value removes the entry from the hash table. */ XP_Bool BM_SetNickName(MWContext* context, BM_Entry* entry, const char* value) { BM_Frame* f = GETFRAME(context); char* pName; CHKCONTEXT(context); XP_ASSERT(context->type == MWContextAddressBook); if (!entry) return(TRUE); if (!value) { /* 5-16-95 jefft -- bug#: 20808, remove entry from the hash table */ if (entry->nickname && *entry->nickname) { XP_Remhash(f->nicknameTable, entry->nickname); FREEIF(entry->nickname); } return(TRUE); } /* allocate a copy of the string so we can modify it to be a legal alias */ /* But only if value is non-null */ pName = (value) ? XP_STRDUP(value) : NULL; if (value && !pName) return(FALSE); BM_CLEARFLAG(entry, BM_ATTR_ISNEW); if (BM_ISALIAS(entry)) { XP_Bool retVal; retVal = BM_SetNickName(context, entry->d.alias.original, value); XP_FREE(pName); return(retVal); } else { if (entry->nickname == NULL || value == NULL || XP_STRCMP(entry->nickname, value)) { if (pName != NULL) { char* ptr; for (ptr = pName ; *ptr ; ptr++) { if (!isalnum(*ptr) && (*ptr != '-') && (*ptr != '_')) { FE_Alert(context, XP_GetString(XP_BKMKS_INVALID_NICKNAME)); XP_FREE(pName); return(FALSE); } /* convert to lowercase */ if (isupper(*ptr)) { *ptr = (char)tolower(*ptr); } } if (XP_Gethash(f->nicknameTable, pName, NULL)) { FE_Alert(context, XP_GetString(XP_BKMKS_NICKNAME_ALREADY_EXISTS)); FREEIF(pName); return(FALSE); } } } if (entry->nickname && *entry->nickname) { XP_Remhash(f->nicknameTable, entry->nickname); } bm_CopyStringWithUndo(context, entry, &entry->nickname, pName); if (entry->nickname && *entry->nickname) { XP_Puthash(f->nicknameTable, entry->nickname, entry); } } FREEIF(pName); return(TRUE); } void BM_CancelEdit(MWContext* context, BM_Entry* entry) { CHKCONTEXTVOID(context); bm_start_batch(context); if (entry && (entry->flags & BM_ATTR_ISNEW) && !(entry->flags & BM_ATTR_HASALIASES) && entry->parent) { BM_RemoveChildFromHeader(context, entry->parent, entry); BM_FreeEntry(context, entry); bm_refresh(context, 1, BM_LAST_CELL); } bm_end_batch(context); } /* returns the number of children parent has if visible is TRUE, only visible children are counted, otherwise all children are counted */ static int32 bm_CountChildren(BM_Entry* parent, XP_Bool visible) { BM_Entry* child; int32 count = 1; XP_ASSERT(parent); XP_ASSERT(BM_ISHEADER(parent)); if (!parent || !BM_ISHEADER(parent)) return 0; if (!visible || !(parent->flags & BM_ATTR_FOLDED)) { child = parent->d.header.children; while (child) { if (BM_ISHEADER(child)) { count += bm_CountChildren(child, visible); } else { count++; } child = child->next; } } return count; } static void bm_WidestEntry_1(MWContext* context, BM_Entry* parent, BM_Entry** widest, uint32* widestWidth) { BM_Entry* child; uint32 width; uint32 height; XP_ASSERT(parent); XP_ASSERT(BM_ISHEADER(parent)); XP_ASSERT(widestWidth); XP_ASSERT(widest); BMFE_MeasureEntry(context, parent, &width, &height); if (width > *widestWidth) { *widestWidth = width; *widest = parent; } if (!(BM_ISFOLDED(parent))) { child = parent->d.header.children; while (child) { if (BM_ISHEADER(child)) { bm_WidestEntry_1(context, child, widest, widestWidth); } else { BMFE_MeasureEntry(context, child, &width, &height); if (width > *widestWidth) { *widestWidth = width; *widest = child; } } child = child->next; } } } /* returns the widest visible entry in the tree (this uses a FE function to measure the width) */ PUBLIC BM_Entry* BM_WidestEntry(MWContext* context) { BM_Entry* widest = NULL; uint32 widestWidth = 0; CHKCONTEXT(context); bm_WidestEntry_1(context, BM_GetRoot(context), &widest, &widestWidth); return widest; } static void bm_SyncCount(MWContext* context) { BM_Frame* f = GETFRAME(context); if (f) { f->gCount = -1; f->gVisCount = -1; } BMFE_SyncDisplay(context); } PRIVATE void bm_SyncSelection_1(BM_Entry* parent, int32* count, uint32* selectionMask) { BM_Entry* child; XP_ASSERT(parent); XP_ASSERT(BM_ISHEADER(parent)); if (parent->flags & BM_ATTR_SELECTED) { *selectionMask |= BM_TYPE_HEADER; (*count)++; } child = parent->d.header.children; while (child) { if (BM_ISHEADER(child)) { bm_SyncSelection_1(child, count, selectionMask); } else { if (BM_ISSELECTED(child)) { *selectionMask |= child->type; (*count)++; } } child = child->next; } } /* synchronizes the selection mask and the selection count with what is actually selected this is necessary when items become deselected because we don't have a global selection list, only a count and mask */ static void bm_SyncSelection(MWContext* context) { BM_Frame* f = GETFRAME(context); f->gSelectionCount = 0; f->gSelectionMask = 0; bm_SyncSelection_1(BM_GetRoot(context), &(f->gSelectionCount), &(f->gSelectionMask)); } /* return the index number of item in cur_count with regards to the BM_ATTR_FOLDED flag */ PRIVATE int32 bm_GetIndexNum(BM_Entry* parent, BM_Entry* item, int32* cur_count) { BM_Entry* child; int32 rv = 0; XP_ASSERT(parent); XP_ASSERT(BM_ISHEADER(parent)); child = parent->d.header.children; if (parent == item) return *cur_count; while (child) { (*cur_count)++; if (child == item) { return *cur_count; } /* if it's a header and it's unfolded, traverse it's children */ if (child->type == BM_TYPE_HEADER && !BM_ISFOLDED(child)) { rv = bm_GetIndexNum(child, item, cur_count); if (rv) return rv; } child = child->next; } return 0; } /* return the index number of item in cur_count without regards to the BM_ATTR_FOLDED flag */ PRIVATE int32 bm_GetUnfoldedIndexNum(BM_Entry* parent, BM_Entry* item, int32* cur_count) { BM_Entry* child; int32 rv = 0; XP_ASSERT(parent); XP_ASSERT(parent->type == BM_TYPE_HEADER); if (parent == item) return *cur_count; for (child = parent->d.header.children; child; child = child->next) { (*cur_count)++; if (child == item) return *cur_count; if (child->type == BM_TYPE_HEADER) { rv = bm_GetUnfoldedIndexNum(child, item, cur_count); if (rv) return rv; } } return 0; } /* returns the child url entry of parent whose address is the same as url_address */ PRIVATE void bm_FindItemStub(MWContext *context, BM_Entry* parent, char* url_address, EntryFunc pf, void *pClosure) { BM_Entry* child; if (!parent) { /* Eric made me do it */ return; } for (child = parent->d.header.children; child; child = child->next) { if (child->type == BM_TYPE_URL && child->d.url.address && !XP_STRCMP(child->d.url.address, url_address)) { (*pf)(context, child, pClosure); } if (child->type == BM_TYPE_HEADER) { bm_FindItemStub(context, child, url_address, pf, pClosure); } } return; } PRIVATE int32 bm_GetDepth(BM_Entry* parent, BM_Entry* item) { int32 rv = 0; BM_Entry* next; if (!item) return -1; next = item; while (next && next->parent) { /* I think extra "next &&" is necessary for Win16 busted optimizer... */ rv++; next = next->parent; } return rv; } static void bm_simple_freeit(void* closure) { XP_FREE(closure); } typedef struct bm_delete_child_info { MWContext* context; BM_Entry* parent; BM_Entry* child; } bm_delete_child_info; static int bm_delete_child_doit(void* closure) { bm_delete_child_info* info = (bm_delete_child_info*) closure; BM_RemoveChildFromHeader(info->context, info->parent, info->child); return 0; } static void bm_LogDeleteChild(MWContext* context, BM_Entry* parent, BM_Entry* child) { BM_Frame* f = GETFRAME(context); bm_delete_child_info* info; /* Magic side effect -- if a child has just been added, and it doesn't have an addition date set, set it to be now. */ if (child->addition_date == 0) { child->addition_date = XP_TIME(); } if (!f || !f->undo) return; bm_SetModified(context, TRUE); info = XP_NEW_ZAP(bm_delete_child_info); if (!info) { UNDO_DiscardAll(f->undo); } else { info->context = context; info->parent = parent; info->child = child; UNDO_LogEvent(f->undo, bm_delete_child_doit, bm_simple_freeit, info, NULL, NULL); } } /* appends a child item to a parent at the end of the parents child list */ static void bm_AppendChildToHeader(MWContext* context, BM_Entry* parent, BM_Entry* child) { BM_Frame* f = GETFRAME(context); BM_Entry* lastChild; XP_ASSERT(parent); XP_ASSERT(BM_ISHEADER(parent)); XP_ASSERT(child); XP_ASSERT(child != parent); f->gCount = -1; f->gVisCount = -1; lastChild = parent->d.header.lastChild; if (lastChild) { lastChild->next = child; parent->d.header.lastChild = child; } else { parent->d.header.children = child; parent->d.header.lastChild = child; } parent->d.header.childCount++; child->parent = parent; if( !f->bSorting ) child->iNaturalIndex = g_iNaturalIndexPool++; if (context) { BMFE_BookmarkMenuInvalid(context); bm_LogDeleteChild(context, parent, child); } } void BM_AppendToHeader(MWContext* context, BM_Entry* parent, BM_Entry* child) { int index; bm_start_batch(context); bm_AppendChildToHeader(context, parent, child); index = BM_GetIndex(context, child); if (index > 0) bm_refresh(context, index, BM_LAST_CELL); bm_end_batch(context); } /* Add a child item to a parent at the beginning of the parents child list */ void BM_PrependChildToHeader(MWContext* context, BM_Entry* parent, BM_Entry* child) { BM_Frame* f = GETFRAME(context); BM_Entry* firstChild; XP_ASSERT(parent); XP_ASSERT(parent->type == BM_TYPE_HEADER); XP_ASSERT(child); XP_ASSERT(child != parent); f->gCount = -1; f->gVisCount = -1; firstChild = parent->d.header.children; if (!firstChild) { bm_AppendChildToHeader(context, parent, child); } else { child->next = firstChild; parent->d.header.children = child; parent->d.header.childCount++; child->parent = parent; if( !f->bSorting ) child->iNaturalIndex = g_iNaturalIndexPool++; if (context) { BMFE_BookmarkMenuInvalid(context); bm_LogDeleteChild(context, parent, child); } } } static int bm_SortAddressBook(const void* obj1, const void* obj2) { const BM_Entry* entry1 = (const BM_Entry*) obj1; const BM_Entry* entry2 = (const BM_Entry*) obj2; if (BM_ISALIAS(entry1)) { entry1 = entry1->d.alias.original; XP_ASSERT(!BM_ISALIAS(entry1)); } if (BM_ISALIAS(entry2)) { entry2 = entry2->d.alias.original; XP_ASSERT(!BM_ISALIAS(entry2)); } XP_ASSERT(BM_ISHEADER(entry1) || BM_ISADDRESS(entry1)); XP_ASSERT(BM_ISHEADER(entry2) || BM_ISADDRESS(entry2)); if (entry1 == entry2) return 0; /* Can happen with two aliases to the same thing... */ if (BM_ISHEADER(entry1)) { if (BM_ISHEADER(entry2)) { #ifdef INTL_SORT return XP_StrColl(entry1->name, entry2->name); #else return XP_STRCMP(entry1->name, entry2->name); #endif } else { return 1; } } else { if (BM_ISHEADER(entry2)) { return -1; } else { #ifdef INTL_SORT return XP_StrColl(entry1->name, entry2->name); #else return XP_STRCMP(entry1->name, entry2->name); #endif } } } static BM_Entry* bm_RealEntry(BM_Entry* entry) { if (BM_ISALIAS(entry)) return entry->d.alias.original; else return entry; } /* Adds a child item to a parent, sorting it according to address book sorting rules. */ static void bm_AddChildToHeaderSorted(MWContext* context, BM_Entry* parent, BM_Entry* child) { BM_Frame* f = GETFRAME(context); BM_Entry* entry; BM_Entry* previous = NULL; XP_ASSERT(context->type == MWContextAddressBook); XP_ASSERT(BM_ISHEADER(parent)); if (!BM_ISALIAS(child)) parent = BM_GetRoot(context); if (parent->d.header.lastChild && bm_SortAddressBook(parent->d.header.lastChild, child) < 0) { /* Ah, the most common case (especially when loading from a file). This kid goes last. */ previous = parent->d.header.lastChild; bm_AppendChildToHeader(context, parent, child); } else { for (entry = parent->d.header.children ; entry ; entry = entry->next) { int value = bm_SortAddressBook(entry, child); if (value > 0) break; if (value == 0) { /* Hmm. Let's not allow any duplicate aliases to the same thing in the same header. */ if (bm_RealEntry(entry) == bm_RealEntry(child)) { if (BM_ISALIAS(child)) { BM_FreeEntry(context, child); } else { XP_ASSERT(BM_ISALIAS(entry)); BM_RemoveChildFromHeader(context, parent, entry); bm_AddChildToHeaderSorted(context, parent, child); } return; } } previous = entry; } if (previous == NULL) { BM_PrependChildToHeader(context, parent, child); previous = parent; } else { bm_InsertItemAfter(context, previous, child, FALSE); } } f->gCount = -1; f->gVisCount = -1; if (!BM_ISFOLDED(parent) && !bm_refreshing_all(context)) { int index = BM_GetIndex(context, previous); if (index > 0) { f->gVisCount++; bm_refresh(context, index + 1, BM_LAST_CELL); } } } static BM_Entry* bm_get_previous(BM_Entry* entry) { BM_Entry* child; BM_Entry* previous = NULL; if (entry && entry->parent) { child = entry->parent->d.header.children; previous = NULL; while (child && child != entry) { previous = child; child = child->next; } } if (child == NULL) previous = NULL; return previous; } typedef struct bm_add_child_info { MWContext* context; BM_Entry* parent; BM_Entry* previous; BM_Entry* child; } bm_add_child_info; static int bm_add_child_doit(void* closure) { bm_add_child_info* info = (bm_add_child_info*) closure; XP_ASSERT(info->previous == NULL || info->previous->parent == info->parent); if (info->previous) { bm_InsertItemAfter(info->context, info->previous, info->child, FALSE); } else { BM_PrependChildToHeader(info->context, info->parent, info->child); } BM_ClearAllSelection(info->context, FALSE); return 0; } void BM_RemoveChildFromHeader(MWContext* context, BM_Entry* parent, BM_Entry* child) { BM_Frame* f = GETFRAME(context); BM_Entry* previous; XP_ASSERT(BM_ISHEADER(parent)); if (!BM_ISHEADER(parent)) return; XP_ASSERT(child); if (!child) return; XP_ASSERT(child != parent); if (child == parent) return; XP_ASSERT(child->parent == parent); if (child->parent != parent) return; if (context && (child->flags & BM_ATTR_SELECTED)) { BM_SelectItem(context, child, TRUE, TRUE, FALSE); } previous = bm_get_previous(child); if (previous) previous->next = child->next; if (parent->d.header.children == child) { parent->d.header.children = child->next; } if (parent->d.header.lastChild == child) { parent->d.header.lastChild = previous; } f->gCount = -1; f->gVisCount = -1; parent->d.header.childCount--; if (context) { BM_Frame* f = GETFRAME(context); bm_add_child_info* info; BMFE_BookmarkMenuInvalid(context); bm_SetModified(context, TRUE); if (f->undo) { info = XP_NEW_ZAP(bm_add_child_info); if (!info) { UNDO_DiscardAll(f->undo); } else { info->context = context; info->parent = parent; info->previous = previous; info->child = child; UNDO_LogEvent(f->undo, bm_add_child_doit, bm_simple_freeit, info, NULL, NULL); } } } child->parent = NULL; child->next = NULL; } #define BM_HEADER_BEGIN 0xD000 #define BM_HEADER_END 0xE000 #define BM_UNKNOWN 0xF000 static uint16 bm_tokenize_line(MWContext* context, char* buffer, char** ptr) { if ((*ptr = strcasestr(buffer, "HREF=\""))) { return context->type == MWContextBookmarks ? BM_TYPE_URL : BM_TYPE_ADDRESS; } else if ((*ptr = strcasestr(buffer, ""))) { return BM_TYPE_SEPARATOR; } else if (strcasestr(buffer, "") || strcasestr(buffer, "") || strcasestr(buffer, "")) { return BM_HEADER_END; } else if (strcasestr(buffer, "