home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / lib / libmisc / bkmks.c next >
Encoding:
C/C++ Source or Header  |  1998-04-08  |  180.1 KB  |  7,416 lines

  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Netscape Public License
  4.  * Version 1.0 (the "NPL"); you may not use this file except in
  5.  * compliance with the NPL.  You may obtain a copy of the NPL at
  6.  * http://www.mozilla.org/NPL/
  7.  *
  8.  * Software distributed under the NPL is distributed on an "AS IS" basis,
  9.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
  10.  * for the specific language governing rights and limitations under the
  11.  * NPL.
  12.  *
  13.  * The Initial Developer of this code under the NPL is Netscape
  14.  * Communications Corporation.  Portions created by Netscape are
  15.  * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
  16.  * Reserved.
  17.  */
  18.  
  19. #include "bkmks.h"
  20. #include "net.h"
  21. #include "xp_mcom.h"
  22. #include "client.h"
  23. #include "msgcom.h"
  24. #include "undo.h"
  25. #include "xp_hash.h"
  26. #include "xpgetstr.h"
  27. #include "glhist.h"
  28. #include "libi18n.h"
  29. #include "xp_qsort.h"
  30. #include "intl_csi.h"
  31.  
  32. /* N.B. If you add code to this file, make sure it still builds on win16 debug - its 
  33.    code segment is too big.
  34. */
  35.  
  36. #define INTL_SORT 1
  37. #ifdef INTL_SORT    /* Added by ftang */
  38. #include "xplocale.h"
  39. #endif
  40.  
  41. extern int MK_OUT_OF_MEMORY;
  42.  
  43. extern int XP_BKMKS_BOOKMARKS_CHANGED;
  44. extern int XP_BKMKS_ADDRESSBOOK_CHANGED;
  45. extern int XP_BKMKS_BOOKMARKS_CONFLICT;
  46. extern int XP_BKMKS_ADDRESSBOOK_CONFLICT;
  47. extern int XP_BKMKS_CANT_WRITE_ADDRESSBOOK;
  48. extern int XP_BKMKS_CANT_WRITE_BOOKMARKS;
  49.  
  50. /* added by L10N team */
  51. extern int XP_BKMKS_HOURS_AGO;
  52. extern int XP_BKMKS_DAYS_AGO;
  53. extern int XP_BKMKS_COUNTALIASES_MANY;
  54. extern int XP_BKMKS_COUNTALIASES_ONE;
  55. extern int XP_BKMKS_COUNTALIASES_NONE;
  56. extern int XP_BKMKS_INVALID_NICKNAME;
  57. extern int XP_BKMKS_NICKNAME_ALREADY_EXISTS;
  58. extern int XP_BKMKS_REMOVE_THIS_ITEMS_ALIASES;
  59. extern int XP_BKMKS_REMOVE_SOME_ITEMS_ALIASES;
  60. extern int XP_BKMKS_AUTOGENERATED_FILE;
  61. extern int XP_BKMKS_READ_AND_OVERWRITE;
  62. extern int XP_BKMKS_DO_NOT_EDIT;
  63. extern int XP_BKMKS_NEW_HEADER;
  64. extern int XP_BKMKS_NEW_BOOKMARK;
  65. extern int XP_BKMKS_NOT_FOUND;
  66. extern int XP_BKMKS_OPEN_BKMKS_FILE;
  67. extern int XP_BKMKS_IMPORT_BKMKS_FILE; 
  68. extern int XP_BKMKS_IMPORT_ADDRBOOK;
  69. extern int XP_BKMKS_SAVE_BKMKS_FILE;
  70. extern int XP_BKMKS_SAVE_ADDRBOOK;
  71. extern int XP_BKMKS_LESS_THAN_ONE_HOUR_AGO;
  72.  
  73.  
  74. extern int XP_BKMKS_SOMEONE_S_BOOKMARKS;    /* "%s%s's Bookmarks%s" */
  75. extern int XP_BKMKS_PERSONAL_BOOKMARKS;        /* "%sPersonal Bookmarks%s" */
  76. extern int XP_BKMKS_SOMEONE_S_ADDRESSBOOK;    /* "%s%s's Addressbook%s" */
  77. extern int XP_BKMKS_PERSONAL_ADDRESSBOOK;    /* "%sPersonal Addressbook%s" */
  78.  
  79. extern int XP_BKMKS_BOOKMARK;    
  80. extern int XP_BKMKS_ENTRY;    
  81. extern int XP_BKMKS_SECONDS;    
  82. extern int XP_BKMKS_MINUTES;    
  83. extern int XP_BKMKS_HOURS_MINUTES;    
  84.  
  85. extern int XP_BKMKS_HEADER;
  86. extern int XP_ADDRBOOK_HEADER;
  87.  
  88.  
  89. #define SECONDS_PER_DAY        86400L
  90. #define BMLIST_COOKIE            "<!DOCTYPE NETSCAPE-Bookmark-file-1>"
  91. #define BM_ADDR_LIST_COOKIE        "<!DOCTYPE NETSCAPE-Addressbook-file-1>"
  92. #define READ_BUFFER_SIZE        2048
  93. #define DEF_NAME                "Personal Bookmarks" /* L10N? This doesn't seem to be used. */
  94.  
  95. #ifdef FREEIF
  96. #undef FREEIF
  97. #endif
  98. #define FREEIF(obj) do { if (obj) { XP_FREE (obj); obj = 0; }} while (0)
  99.  
  100. #define BM_ATTR_FOLDED        0x0001
  101. #define BM_ATTR_SELECTED    0x0002
  102. #define BM_ATTR_ISNEW        0x0004
  103. #define BM_ATTR_FINDAFF        0x0008 /* This entry is temporarily unfolded only
  104.                                       as a result of the last find
  105.                                       operation. */
  106. #define BM_ATTR_HASALIASES    0x0010 /* Actually, being set only means that
  107.                                       this entry *might* have aliases.  But
  108.                                       if an entry has aliases, this bit is
  109.                                       definitely set. */
  110. #define BM_ATTR_MARKED        0x0020 /* Random bit to temporarily mark a bunch of
  111.                                       items. */
  112. #define BM_ATTR_CHECKING    0x0040 /* In the midst of checking whether this
  113.                                       entry has changed. */
  114.  
  115.  
  116. #define BM_ISHEADER(bmStruct) \
  117.     ((bmStruct) && ((bmStruct)->type == BM_TYPE_HEADER))
  118.  
  119. #define BM_ISURL(bmStruct) \
  120.     ((bmStruct) && ((bmStruct)->type == BM_TYPE_URL))
  121.  
  122. #define BM_ISADDRESS(bmStruct) \
  123.     ((bmStruct) && ((bmStruct)->type == BM_TYPE_ADDRESS))
  124.  
  125. #define BM_ISSEPARATOR(bmStruct) \
  126.     ((bmStruct) && ((bmStruct)->type == BM_TYPE_SEPARATOR))
  127.  
  128. #define BM_ISALIAS(bmStruct) \
  129.     ((bmStruct) && ((bmStruct)->type == BM_TYPE_ALIAS))
  130.  
  131. #define BM_ISFOLDED(bmStruct) \
  132.     ((bmStruct) && ((bmStruct)->flags & BM_ATTR_FOLDED))
  133.  
  134. #define BM_ISSELECTED(bmStruct) \
  135.     ((bmStruct) && ((bmStruct)->flags & BM_ATTR_SELECTED))
  136.  
  137. #define BM_SETFLAG(bmStruct, flag) \
  138.     ((bmStruct) ? ((bmStruct)->flags |= (flag)) : 0)
  139.  
  140. #define BM_CLEARFLAG(bmStruct, flag) \
  141.     ((bmStruct) ? ((bmStruct)->flags &= ~(flag)) : 0)
  142.  
  143. static int32 g_iNaturalIndexPool = 0;
  144.  
  145. struct BM_Entry_struct
  146. {
  147.   BM_Type type;
  148.  
  149.   uint16 flags;
  150.   BM_Entry*    next;
  151.   BM_Entry*    parent;
  152.   char* name;
  153.   char* description;
  154.   BM_Date addition_date;
  155.   int32 iNaturalIndex;          /* Index for user arranged "sort" */
  156.   char* nickname;                /* Used only by address book, alas. */
  157.  
  158.   union {
  159.     struct BM_Header {
  160.       char* target;            /* target */
  161.       BM_Entry*    children;            /* a linked list of my children */
  162.       uint32 childCount;            /* the number of "children" */
  163.       BM_Entry*    lastChild;            /* the last child in "children" */
  164.     } header;
  165.     struct BM_Url {
  166.       char* address;            /* address */
  167.       char* target;            /* target */
  168.       char* content_type;        /* content-type */
  169.       BM_Date last_visit;        /* when last visited */
  170.       BM_Date last_modified;    /* when the contents of the URL was last
  171.                                    modified. */
  172.     } url;
  173.     struct BM_Address {
  174.       char* address;            /* e-mail address */
  175.     } address;
  176.     struct BM_Alias {
  177.       BM_Entry*    original;        /* the original bm */
  178.     } alias;
  179.   } d;
  180. };
  181.  
  182.  
  183. typedef struct BM_Frame {
  184.     BM_Entry*        gBookmarks;            /* root of the tree */
  185.     XP_Bool            gBookmarksModified; /* TRUE if the tree is modified */
  186.     int32            gCount;                /* number of entries in tree.  If
  187.                                            negative, then need to be
  188.                                            recalculated. */
  189.     int32            gVisCount;            /* number of visible entries.  If
  190.                                            negative, then need to be
  191.                                            recalculated. */
  192.     int32            gSelectionCount;    /* number of selected items.  If
  193.                                            negative, then we're out of sync,
  194.                                            call bm_SyncSelection to
  195.                                            synchronize.  */
  196.     uint32            gSelectionMask;        /* what types of items are selected
  197.                                            (invalid if gSelectionCount is
  198.                                            negative.)*/
  199.     BM_FindInfo*    gFindInfo;            /* information for the find dialog */
  200.     char*            gFile;                /* the file this data is from */
  201.     void*            gTemporary;
  202.     void*            feData;
  203.     int32            max_depth;    /* The number of levels in the heirarchy that
  204.                                    we currently display.  (If zero, then we
  205.                                    don't know. */
  206.     UndoState*        undo;
  207.     MWContext* next;            /* Next bookmarks context in master linked list. */
  208.     XP_Bool unfoldedForFind;    /* TRUE if some headers have been unfolded
  209.                                    as the result of a find operation.  The
  210.                                    headers in question will have the
  211.                                    BM_ATTR_FINDAFF flag set.*/
  212.  
  213.     BM_SortType     enSortType;         /* the sort field (a column or natural) */
  214.     XP_Bool         bSorting;
  215.     XP_HashTable aliasTable;
  216.     XP_HashTable nicknameTable;
  217.     int aliasID;
  218.     XP_Bool errorSavingBookmarks;
  219.  
  220.   int32 batch_depth;
  221.   int32 first_update_line;
  222.   int32 last_update_line;
  223.  
  224.   void* savetimer;
  225.  
  226.   BM_Entry* lastSelectedItem;
  227.  
  228.   BM_Entry* menuheader;            /* Which header to use as the menu bar. */
  229.   BM_Entry* addheader;            /* Which entry to add new items into. */
  230.  
  231.   XP_StatStruct laststat;        /* Stat of the file when we loaded it in. */
  232.  
  233.  
  234.  
  235.   struct BM_WhatsChangedInfo {
  236.     int32 total;
  237.     int32 numreached;
  238.     int32 numchanged;
  239.     time_t starttime;
  240.   } whatschanged;
  241.  
  242. } BM_Frame;
  243.  
  244.  
  245. #define CHKCONTEXT(context)                                               \
  246. XP_ASSERT(context != NULL && (context->type == MWContextAddressBook || \
  247.                               context->type == MWContextBookmarks) &&  \
  248.           context->bmframe != NULL);                                   \
  249. if (!context || (context->type != MWContextAddressBook &&               \
  250.                  context->type != MWContextBookmarks) ||               \
  251.     !context->bmframe) return 0;
  252.  
  253. #define CHKCONTEXTVOID(context)                                           \
  254. XP_ASSERT(context != NULL && (context->type == MWContextAddressBook || \
  255.                               context->type == MWContextBookmarks) &&  \
  256.           context->bmframe != NULL);                                   \
  257. if (!context || (context->type != MWContextAddressBook &&               \
  258.                  context->type != MWContextBookmarks) ||               \
  259.     !context->bmframe) return;
  260.  
  261.  
  262. /* Should probably be a macro, but putting the assert in the macro and
  263.    making this still be easy to use is just too painful. */
  264. static BM_Frame*
  265. GETFRAME(MWContext* context)
  266. {
  267.   XP_ASSERT(context &&
  268.             (context->type == MWContextBookmarks ||
  269.              context->type == MWContextAddressBook) &&
  270.             context->bmframe != NULL);
  271.   return (context && (context->type == MWContextBookmarks ||
  272.                       context->type == MWContextAddressBook))
  273.     ? context->bmframe : NULL;
  274. }
  275.  
  276.  
  277. static void bm_CancelLastFind(MWContext* context);
  278. static void bm_InsertItemAfter(MWContext* context, BM_Entry* insert_after,
  279.                                BM_Entry* insertee, XP_Bool sync);
  280. static void bm_SortSelected(MWContext* context, BM_SortType enSortType);
  281. static void bm_SyncCount(MWContext* context);
  282. static void bm_AddChildToHeaderSorted(MWContext* context, BM_Entry* parent,
  283.                                       BM_Entry* child);
  284. static void bm_SortSilent(MWContext* context, BM_SortType enSortType );
  285.  
  286.  
  287.  
  288. XP_Bool
  289. BM_IsHeader(BM_Entry* entry)
  290. {
  291.   return BM_ISHEADER(entry);
  292. }
  293.  
  294. XP_Bool
  295. BM_IsUrl(BM_Entry* entry)
  296. {
  297.   return BM_ISURL(entry);
  298. }
  299.  
  300. XP_Bool
  301. BM_IsAddress(BM_Entry* entry)
  302. {
  303.   return BM_ISADDRESS(entry);
  304. }
  305.  
  306. XP_Bool
  307. BM_IsSeparator(BM_Entry* entry)
  308. {
  309.   return BM_ISSEPARATOR(entry);
  310. }
  311.  
  312. XP_Bool
  313. BM_IsAlias(BM_Entry* entry)
  314. {
  315.   return BM_ISALIAS(entry);
  316. }
  317.  
  318. XP_Bool
  319. BM_IsFolded(BM_Entry* entry)
  320. {
  321.   return BM_ISFOLDED(entry);
  322. }
  323.  
  324. XP_Bool
  325. BM_IsSelected(BM_Entry* entry)
  326. {
  327.   return BM_ISSELECTED(entry);
  328. }
  329.  
  330.  
  331. int32
  332. BM_GetChangedState(BM_Entry* entry)
  333. {
  334.   if (BM_ISALIAS(entry)) {
  335.     entry = entry->d.alias.original;
  336.   }
  337.   if (BM_ISURL(entry)) {
  338.     if (entry->d.url.last_modified == 0 || entry->flags & BM_ATTR_CHECKING) {
  339.       return BM_CHANGED_UNKNOWN;
  340.     }
  341.     if (entry->d.url.last_visit < entry->d.url.last_modified) {
  342.       return BM_CHANGED_YES;
  343.     }
  344.   }
  345.   return BM_CHANGED_NO;
  346. }
  347.  
  348. /* globals */
  349. static MWContext* ContextList = NULL;
  350.  
  351.  
  352. void
  353. BM_SetFEData(MWContext* context, void* data)
  354. {
  355.   BM_Frame* f = GETFRAME(context);
  356.   CHKCONTEXTVOID(context);
  357.   f->feData = data;
  358. }
  359.  
  360. void*
  361. BM_GetFEData(MWContext* context)
  362. {
  363.   BM_Frame* f = GETFRAME(context);
  364.   CHKCONTEXT(context);
  365.   return f->feData;
  366. }
  367.  
  368.  
  369. /* creates a new empty bookmark entry */
  370. static BM_Entry*
  371. bm_NewEntry(int16 type)
  372. {
  373.   BM_Entry* new_entry = XP_NEW_ZAP(BM_Entry);
  374.   if (!new_entry) return NULL;
  375.   XP_MEMSET(new_entry, 0, sizeof(BM_Entry));
  376.   new_entry->type = type;
  377.   return new_entry;
  378. }
  379.  
  380. /* creates a new header bookmarks entry */
  381. BM_Entry*
  382. BM_NewHeader(const char* name)
  383. {
  384.   BM_Entry* header;
  385.   header = bm_NewEntry(BM_TYPE_HEADER);
  386.   if (!header) return NULL;
  387.   StrAllocCopy(header->name, name);
  388.   return header;
  389. }
  390.  
  391. /* creates a new URL bookmarks entry */
  392. BM_Entry*
  393. BM_NewUrl(const char* name, const char* address, const char* content_type,
  394.            BM_Date last_visit)
  395. {
  396.   BM_Entry* url;
  397.  
  398.   url = bm_NewEntry(BM_TYPE_URL);
  399.  
  400.   if (!url) return NULL;
  401.  
  402.   StrAllocCopy(url->name, name);
  403.   url->description = NULL;
  404.   StrAllocCopy(url->d.url.address, address);
  405.   StrAllocCopy(url->d.url.content_type, content_type);
  406.   url->d.url.last_visit = last_visit;
  407.   url->d.url.last_modified = last_visit; /* ### Is this right? */
  408.   url->addition_date = 0;
  409.  
  410.   return url;
  411. }
  412.  
  413. /* creates a new URL bookmarks entry */
  414. static BM_Entry*
  415. bm_NewAddress(const char* name, const char* address)
  416. {
  417.     BM_Entry*     add_struct;
  418.  
  419.     add_struct = bm_NewEntry(BM_TYPE_ADDRESS);
  420.  
  421.     if (!add_struct) return NULL;
  422.  
  423.     StrAllocCopy(add_struct->name, name ? name : "");
  424.     StrAllocCopy(add_struct->d.address.address, address ? address : "");
  425.  
  426.     return add_struct;
  427. }
  428.  
  429. /* creates a new separator bookmarks entry */
  430. static BM_Entry*
  431. bm_NewSeparator(void)
  432. {
  433.   return bm_NewEntry(BM_TYPE_SEPARATOR);
  434. }
  435.  
  436. /* creates a new alias bookmarks entry */
  437. static BM_Entry*
  438. bm_NewAlias(BM_Entry* original)
  439. {
  440.   BM_Entry* alias;
  441.   alias = bm_NewEntry(BM_TYPE_ALIAS);
  442.   alias->d.alias.original = original;
  443.   BM_SETFLAG(original, BM_ATTR_HASALIASES);
  444.   return alias;
  445. }
  446.  
  447. extern BM_Entry* BM_CopyBookmark(MWContext* context, BM_Entry* original)
  448. {
  449.     BM_Entry* copy;
  450.     BM_Entry* child;
  451.     BM_Entry* childCopy;
  452.  
  453.     copy = NULL;
  454.  
  455.     if(!original)
  456.         return NULL;
  457.  
  458.     switch (original->type) {
  459.         case BM_TYPE_URL:
  460.             copy = BM_NewUrl(original->name, original->d.url.address,
  461.                              original->d.url.content_type, original->d.url.last_visit);
  462.             break;
  463.         case BM_TYPE_ADDRESS:
  464.             copy = bm_NewAddress(original->name, original->d.address.address);
  465.             break;
  466.         case BM_TYPE_SEPARATOR:
  467.             copy = bm_NewSeparator();
  468.             break;
  469.         case BM_TYPE_ALIAS:
  470.             copy = bm_NewAlias(original->d.alias.original);
  471.             break;
  472.         case BM_TYPE_HEADER:
  473.             copy = BM_NewHeader(original->name);
  474.  
  475.             child = original->d.header.children;
  476.  
  477.             while (child) {
  478.                 childCopy = BM_CopyBookmark(context, child);
  479.                 if(childCopy)
  480.                     BM_AppendToHeader(context, copy, childCopy);
  481.                 child = child->next;
  482.             }
  483.             break;
  484.     }
  485.  
  486.     if(copy)
  487.     {
  488.             StrAllocCopy(copy->description, original->description);
  489.             StrAllocCopy(copy->nickname, original->nickname);
  490.     }
  491.     return copy;
  492. }
  493.  
  494. /* gets the top node of the bmlist */
  495. BM_Entry*
  496. BM_GetRoot(MWContext* context)
  497. {
  498.   BM_Frame* f = GETFRAME(context);
  499.   CHKCONTEXT(context);
  500.   if (!f) return NULL;
  501.   if (!f->gBookmarks) {
  502.     f->gBookmarks = BM_NewHeader(context->type == MWContextBookmarks ?
  503.         XP_GetString(XP_BKMKS_HEADER) : XP_GetString(XP_ADDRBOOK_HEADER));
  504.     if (context->type == MWContextBookmarks) {
  505.       f->menuheader = f->addheader = f->gBookmarks;
  506.     }
  507.     bm_SyncCount(context);
  508.   } else {
  509.     if (f->gBookmarks->parent != NULL)
  510.       XP_ASSERT(f->gBookmarks->next == NULL); /* Stupid consistancy test; 
  511.                                                  dunno what action to take if
  512.                                                  this isn't so. */
  513.   }
  514.   return f->gBookmarks;
  515. }
  516.  
  517. static void
  518. bm_EachEntryDo_1(MWContext* context, BM_Entry* at, EntryFunc func, void* closure)
  519. {
  520.   BM_Entry* nextChild;
  521.   BM_Entry* children;
  522.  
  523.   while (at) {
  524.     nextChild = at->next;
  525.     if (BM_ISHEADER(at)) {
  526.       children = at->d.header.children;
  527.     } else {
  528.       children = NULL;
  529.     }
  530.  
  531.     (*func)(context, at, closure);
  532.  
  533.     if (children) {
  534.       bm_EachEntryDo_1(context, children, func, closure);
  535.     }
  536.  
  537.     at = nextChild;
  538.   }
  539. }
  540.  
  541. static void
  542. bm_EachSelectedEntryDo_1(MWContext* context, BM_Entry* at, EntryFunc func,
  543.                          void* closure)
  544. {
  545.   BM_Entry* nextChild;
  546.   BM_Entry* children;
  547.  
  548.   while (at) {
  549.     nextChild = at->next;
  550.     if (BM_ISHEADER(at)) {
  551.       children = at->d.header.children;
  552.     } else {
  553.       children = NULL;
  554.     }
  555.  
  556.     if (BM_ISSELECTED(at)) (*func)(context, at, closure);
  557.  
  558.     if (children) {
  559.       bm_EachSelectedEntryDo_1(context, children, func, closure);
  560.     }
  561.  
  562.     at = nextChild;
  563.   }
  564. }
  565.  
  566. static void
  567. bm_EachProperSelectedEntryDo_1(MWContext* context, BM_Entry* at, EntryFunc func,
  568.                                void* closure, struct BM_Entry_Focus* bmFocus)
  569. {
  570.   BM_Entry* child;
  571.   BM_Entry* nextChild;
  572.  
  573.   XP_ASSERT(at);
  574.   if (!at) return;
  575.  
  576.   XP_ASSERT(BM_ISHEADER(at));
  577.   child = at->d.header.children;
  578.  
  579.   while (child) {
  580.     nextChild = child->next;
  581.     switch (child->type) {
  582.     case BM_TYPE_URL:
  583.     case BM_TYPE_ADDRESS:
  584.     case BM_TYPE_SEPARATOR:
  585.     case BM_TYPE_ALIAS:
  586.       if (BM_ISSELECTED(child)) {
  587.         if ((bmFocus != NULL) && !bmFocus->foundSelection)
  588.             bmFocus->foundSelection = TRUE;
  589.         (*func)(context, child, closure);
  590.       }
  591.       else if (bmFocus && !bmFocus->foundSelection) {
  592.         bmFocus->saveFocus = child;
  593.       }
  594.       break;
  595.     case BM_TYPE_HEADER:
  596.       if (BM_ISSELECTED(child)) {
  597.         if ((bmFocus != NULL) && !bmFocus->foundSelection)
  598.             bmFocus->foundSelection = TRUE;
  599.         (*func)(context, child, closure);
  600.       } 
  601.       else {
  602.         if (bmFocus && !bmFocus->foundSelection)
  603.           bmFocus->saveFocus = child;
  604.         if (! BM_ISFOLDED(child)) {
  605.           bm_EachProperSelectedEntryDo_1(context, child, func, closure, bmFocus);
  606.         }
  607.       }
  608.       break;
  609.     }
  610.     child = nextChild;
  611.   }
  612. }
  613.  
  614.  
  615.  
  616. void
  617. BM_EachEntryDo(MWContext* context, EntryFunc func, void* closure)
  618. {
  619.   CHKCONTEXTVOID(context);
  620.   bm_EachEntryDo_1(context, BM_GetRoot(context), func, closure);
  621. }
  622.  
  623. void
  624. BM_EachSelectedEntryDo(MWContext* context, EntryFunc func, void* closure)
  625. {
  626.   CHKCONTEXTVOID(context);
  627.   bm_EachSelectedEntryDo_1(context, BM_GetRoot(context), func, closure);
  628. }
  629.  
  630. void
  631. BM_EachProperSelectedEntryDo(MWContext* context, EntryFunc func, void* closure,
  632.   struct BM_Entry_Focus* bmFocus)
  633. {
  634.   CHKCONTEXTVOID(context);
  635.   bm_EachProperSelectedEntryDo_1(context, BM_GetRoot(context), func, closure,
  636.     bmFocus);
  637. }
  638.  
  639.  
  640.  
  641. static void
  642. bm_ClearMarkEverywhere_1(MWContext* context, BM_Entry* entry, void* closure)
  643. {
  644.   BM_CLEARFLAG(entry, BM_ATTR_MARKED);
  645. }
  646.  
  647.  
  648. static void 
  649. bm_ClearMarkEverywhere(MWContext* context)
  650. {
  651.   BM_EachEntryDo(context, bm_ClearMarkEverywhere_1, NULL);
  652. }
  653.  
  654.  
  655. static MWContext*
  656. bm_GetContextForEntry(BM_Entry* entry)
  657. {
  658.   MWContext* result;
  659.   BM_Frame* f;
  660.   while (entry->parent) entry = entry->parent;
  661.   for (result = ContextList ; result ; result = f->next) {
  662.     f = GETFRAME(result);
  663.     if (f->gBookmarks == entry) return result;
  664.   }
  665.   return NULL;
  666. }
  667.  
  668.  
  669. BM_Type
  670. BM_GetType(BM_Entry* entry)
  671. {
  672.   XP_ASSERT(entry);
  673.   return entry ? entry->type : 0;
  674. }
  675.  
  676.  
  677. /* return the "name" for item -- may vary depending on its type */
  678. char*
  679. BM_GetName(BM_Entry* entry)
  680. {
  681.   XP_ASSERT(entry);
  682.   if (!entry) return NULL;
  683.  
  684.   switch (entry->type) {
  685.   case BM_TYPE_URL:
  686.   case BM_TYPE_HEADER:
  687.   case BM_TYPE_ADDRESS:
  688.     return entry->name;
  689.   case BM_TYPE_ALIAS:
  690.     if (entry->d.alias.original)
  691.       return BM_GetName(entry->d.alias.original);
  692.     else
  693.       return entry->name;
  694.   default:
  695.     return NULL;
  696.   }
  697. }
  698.  
  699. /* return the "address" for item -- may vary depending
  700.     on its type */
  701. char*
  702. BM_GetAddress(BM_Entry* entry)
  703. {
  704.   XP_ASSERT(entry);
  705.   if (!entry) return NULL;
  706.  
  707.   switch (entry->type) {
  708.   case BM_TYPE_URL:
  709.     return entry->d.url.address;
  710.   case BM_TYPE_ADDRESS:
  711.     return entry->d.address.address;
  712.   case BM_TYPE_HEADER:
  713.   case BM_TYPE_SEPARATOR:
  714.   case BM_TYPE_ALIAS:
  715.     if (entry->d.alias.original) return BM_GetAddress(entry->d.alias.original);
  716.     return NULL;
  717.   default:
  718.     return NULL;
  719.   }
  720. }
  721.  
  722. /* return the "target" for item -- may vary depending
  723.     on its type */
  724. char*
  725. BM_GetTarget(BM_Entry* entry, XP_Bool recurse)
  726. {
  727.   XP_ASSERT(entry);
  728.   if (!entry) return NULL;
  729.  
  730.   switch (entry->type) {
  731.   case BM_TYPE_URL:
  732.     if ((recurse)&&(entry->d.url.target == NULL)&&(entry->parent != NULL))
  733.     {
  734.         return BM_GetTarget(entry->parent, recurse);
  735.     }
  736.     else
  737.     {
  738.         return entry->d.url.target;
  739.     }
  740.   case BM_TYPE_HEADER:
  741.     if ((recurse)&&(entry->d.header.target == NULL)&&(entry->parent != NULL))
  742.     {
  743.         return BM_GetTarget(entry->parent, recurse);
  744.     }
  745.     else
  746.     {
  747.         return entry->d.header.target;
  748.     }
  749.   case BM_TYPE_ADDRESS:
  750.   case BM_TYPE_SEPARATOR:
  751.     return NULL;
  752.   case BM_TYPE_ALIAS:
  753.     if (entry->d.alias.original) return BM_GetTarget(entry->d.alias.original, recurse);
  754.     return NULL;
  755.   default:
  756.     return NULL;
  757.   }
  758. }
  759.  
  760. /* return the "description" for item -- may vary depending on its type */
  761. char*
  762. BM_GetDescription(BM_Entry* entry)
  763. {
  764.   XP_ASSERT(entry);
  765.   if (!entry) return NULL;
  766.  
  767.   switch (entry->type) {
  768.   case BM_TYPE_URL:
  769.   case BM_TYPE_HEADER:
  770.   case BM_TYPE_ADDRESS:
  771.     return entry->description;
  772.   default:
  773.     return NULL;
  774.   }
  775. }
  776.  
  777. char*
  778. BM_GetNickName(BM_Entry* entry)
  779. {
  780.   XP_ASSERT(bm_GetContextForEntry(entry)->type == MWContextAddressBook);
  781.   if (BM_ISALIAS(entry)) return BM_GetNickName(entry->d.alias.original);
  782.   else return entry->nickname;
  783. }
  784.  
  785.  
  786. PRIVATE int32
  787. bm_CountAliases_1(BM_Entry* at, BM_Entry* forEntry)
  788. {
  789.   int32 count = 0;
  790.   for ( ; at ; at = at->next) {
  791.     if (BM_ISHEADER(at)) {
  792.       count += bm_CountAliases_1(at->d.header.children, forEntry);
  793.     } else if (BM_ISALIAS(at)) {
  794.       if (at->d.alias.original == forEntry) count++;
  795.     }
  796.   }
  797.   return count;
  798. }
  799.  
  800. PUBLIC int32
  801. BM_CountAliases(MWContext* context, BM_Entry* entry)
  802. {
  803.   int32 result;
  804.   CHKCONTEXT(context);
  805.   result = bm_CountAliases_1(BM_GetRoot(context), entry);
  806.   if (result) {
  807.     XP_ASSERT(entry->flags & BM_ATTR_HASALIASES);
  808.     BM_SETFLAG(entry, BM_ATTR_HASALIASES);
  809.   } else {
  810.     BM_CLEARFLAG(entry, BM_ATTR_HASALIASES);
  811.   }
  812.   return result;
  813. }
  814.  
  815. BM_Date 
  816. BM_GetLastVisited(BM_Entry *entry)
  817. {
  818.   XP_ASSERT(entry);
  819.   if (!entry || (entry->type != BM_TYPE_URL)) return 0;
  820.  
  821.   return entry->d.url.last_visit;
  822. }
  823.  
  824. BM_Date 
  825. BM_GetAdditionDate(BM_Entry *entry)
  826. {
  827.   XP_ASSERT(entry);
  828.  
  829.   if (!entry) return 0;
  830.   
  831.   return entry->addition_date;
  832. }
  833.  
  834.  
  835. /* pretty print the last visited date         ### fix i18n */
  836. char*
  837. BM_PrettyLastVisitedDate(BM_Entry* entry)
  838. {
  839.   static char buffer[200];
  840.  
  841.   buffer[0] = 0;
  842.  
  843.   XP_ASSERT(entry);
  844.   if (!entry) return NULL;
  845.  
  846.   if (entry->type == BM_TYPE_URL) {
  847.     time_t lastVisited;
  848.     time_t today;
  849.     time_t elapsed;
  850.  
  851.     lastVisited = entry->d.url.last_visit;
  852.     if (lastVisited == 0) return "";
  853.     today = XP_TIME();
  854.  
  855.     elapsed = today - lastVisited;
  856.  
  857.     if (elapsed < SECONDS_PER_DAY) {
  858.       int32 hours = (elapsed + 1800L) / 3600L;
  859.       if (hours < 1) {
  860.         return XP_GetString(XP_BKMKS_LESS_THAN_ONE_HOUR_AGO);
  861.       }
  862.       sprintf(buffer, XP_GetString(XP_BKMKS_HOURS_AGO), hours);
  863.     } else if (elapsed < (SECONDS_PER_DAY * 31)) {
  864.       sprintf(buffer, XP_GetString(XP_BKMKS_DAYS_AGO),
  865.               (elapsed + (SECONDS_PER_DAY / 2)) / SECONDS_PER_DAY);
  866.     } else {
  867.       struct tm* tmp;
  868.       tmp = localtime(&lastVisited);
  869.  
  870.       sprintf(buffer, asctime(tmp));
  871.     }
  872.     return buffer;
  873.   }
  874.   return NULL;
  875. }
  876.  
  877. /* pretty print the added on date */
  878. char*
  879. BM_PrettyAddedOnDate(BM_Entry* entry)
  880. {
  881.   static char buffer[200];
  882.   struct tm* tmp;
  883.  
  884.   XP_ASSERT(entry);
  885.  
  886.   if (entry && entry->addition_date != 0) {
  887.     tmp = localtime(&(entry->addition_date));
  888.     sprintf(buffer, asctime(tmp));
  889.     return buffer;
  890.   }
  891.   return NULL;
  892. }
  893.  
  894. char*
  895. BM_PrettyAliasCount(MWContext* context, BM_Entry* entry) 
  896. {
  897.   static char buffer[100];
  898.   int32 count;
  899.   char* name = context->type == MWContextBookmarks ? 
  900.     XP_GetString(XP_BKMKS_BOOKMARK) : XP_GetString(XP_BKMKS_ENTRY);        
  901.   CHKCONTEXT(context);
  902.   XP_ASSERT(entry);
  903.   if (!entry) return NULL;
  904.  
  905.   count = BM_CountAliases(context, entry);
  906.   buffer[0] = 0;
  907.  
  908.   if (count > 1) {
  909.     sprintf(buffer, XP_GetString(XP_BKMKS_COUNTALIASES_MANY), count, name);
  910.   } else if (count == 1) {
  911.     sprintf(buffer, XP_GetString(XP_BKMKS_COUNTALIASES_ONE), name);
  912.   } else {
  913.     sprintf(buffer, XP_GetString(XP_BKMKS_COUNTALIASES_NONE), name);
  914.   }
  915.   return buffer;
  916. }
  917.  
  918.  
  919. BM_Entry*
  920. BM_GetChildren(BM_Entry* entry)
  921. {
  922.   if (BM_ISHEADER(entry)) return entry->d.header.children;
  923.   return NULL;
  924. }
  925.  
  926. BM_Entry*
  927. BM_GetNext(BM_Entry* entry)
  928. {
  929.   XP_ASSERT(entry);
  930.   return entry ? entry->next : NULL;
  931. }
  932.  
  933.  
  934. BM_Entry*
  935. BM_GetParent(BM_Entry* entry)
  936. {
  937.   XP_ASSERT(entry);
  938.   return entry ? entry->parent : NULL;
  939. }
  940.  
  941.  
  942. XP_Bool
  943. BM_HasNext(BM_Entry* entry)
  944. {
  945.   XP_ASSERT(entry);
  946.   return entry ? (entry->next != NULL) : FALSE;
  947. }
  948.  
  949.  
  950. XP_Bool
  951. BM_HasPrev(BM_Entry* entry)
  952. {
  953.   BM_Entry* parent = entry->parent;
  954.   if (parent) {
  955.     XP_ASSERT(BM_ISHEADER(parent));
  956.     return parent->d.header.children != entry;
  957.   } else {
  958.     return GETFRAME(bm_GetContextForEntry(entry))->gBookmarks != entry;
  959.   }
  960. }
  961.  
  962.  
  963. static void
  964. bm_flush_updates(MWContext* context)
  965. {
  966.   BM_Frame* f = GETFRAME(context);
  967.   CHKCONTEXTVOID(context);
  968.   if (f->first_update_line > 0) {
  969.     BMFE_RefreshCells(context, f->first_update_line, f->last_update_line,
  970.                       FALSE);
  971.     f->first_update_line = 0;
  972.   }
  973. }
  974.  
  975. static void
  976. bm_start_batch(MWContext* context)
  977. {
  978.   BM_Frame* f = GETFRAME(context);
  979.  
  980. #ifdef XP_UNIX
  981.   BMFE_StartBatch(context);
  982. #endif
  983.  
  984.   CHKCONTEXTVOID(context);
  985.   if (f->undo) UNDO_StartBatch(f->undo);
  986.   f->batch_depth++;
  987. }
  988.  
  989. static void
  990. bm_end_batch(MWContext* context)
  991. {
  992.   BM_Frame* f = GETFRAME(context);
  993.   CHKCONTEXTVOID(context);
  994.   f->batch_depth--;
  995.   XP_ASSERT(f->batch_depth >= 0);
  996.   if (f->batch_depth == 0) {
  997.     bm_flush_updates(context);
  998.   }
  999.   if (f->undo) UNDO_EndBatch(f->undo, NULL, NULL);
  1000.  
  1001. #ifdef XP_UNIX
  1002.   BMFE_EndBatch(context);
  1003. #endif
  1004. }
  1005.  
  1006. static void 
  1007. bm_refresh(MWContext* context, int32 first, int32 last)
  1008. {
  1009.   BM_Frame* f = GETFRAME(context);
  1010.   CHKCONTEXTVOID(context);
  1011.   XP_ASSERT(first >= 1 && first <= last);
  1012.   if (first < 1 || first > last) {
  1013.     /* Something bogus got passed in; just repaint everything to
  1014.        be safe. */
  1015.     first = 1;
  1016.     last = BM_LAST_CELL;
  1017.   }
  1018.   if (f->first_update_line <= 0 ||
  1019.       first > f->last_update_line + 1 ||
  1020.       last + 1 < f->first_update_line) {
  1021.     bm_flush_updates(context);
  1022.     f->first_update_line = first;
  1023.     f->last_update_line = last;
  1024.   } else {
  1025.     if (f->first_update_line > first) f->first_update_line = first;
  1026.     if (f->last_update_line < last) f->last_update_line = last;
  1027.   }
  1028.   if (f->batch_depth == 0) bm_flush_updates(context);
  1029. }
  1030.  
  1031.  
  1032. /* Handy routine to detect if we're already going to refresh everything.
  1033.    If so, then the caller knows there's no need to refresh any more... */
  1034. static XP_Bool
  1035. bm_refreshing_all(MWContext* context)
  1036. {
  1037.   BM_Frame* f = GETFRAME(context);
  1038.   return (f != NULL &&
  1039.           f->first_update_line == 1 &&
  1040.           f->last_update_line == BM_LAST_CELL);
  1041. }
  1042.  
  1043.  
  1044. static void
  1045. bm_entry_changed_2(MWContext* context, BM_Entry* entry)
  1046. {
  1047.   int32 index = BM_GetIndex(context, entry);
  1048.   if (index < 1) return;
  1049.   if (context->type == MWContextBookmarks || entry->parent == NULL) {
  1050.     bm_refresh(context, index, index);
  1051.   } else {
  1052.     /* Changing the entry might have messed up the sorting order.  Better
  1053.        go resort it.  What a hack...*/
  1054.     BM_Entry* parent = entry->parent;
  1055.     BM_RemoveChildFromHeader(context, parent, entry);
  1056.     bm_AddChildToHeaderSorted(context, parent, entry);
  1057.   }
  1058. }
  1059.  
  1060. static void
  1061. bm_entry_changed_1(MWContext* context, BM_Entry* entry, BM_Entry* find)
  1062. {
  1063.   for (; entry ; entry = entry->next) {
  1064.     if (BM_ISALIAS(entry) && entry->d.alias.original == find) {
  1065.       bm_entry_changed_2(context, entry);
  1066.     } else if (BM_ISHEADER(entry)) {
  1067.       bm_entry_changed_1(context, entry->d.header.children, find);
  1068.     }
  1069.   }
  1070. }
  1071.  
  1072. static void
  1073. bm_entry_changed(MWContext* context, BM_Entry* entry)
  1074. {
  1075.   XP_ASSERT(!BM_ISALIAS(entry));
  1076.   if (entry->flags & BM_ATTR_HASALIASES) {
  1077.     bm_entry_changed_1(context, BM_GetRoot(context), entry);
  1078.   }
  1079.   bm_entry_changed_2(context, entry);
  1080. }
  1081.  
  1082.  
  1083. static void
  1084. bm_save_timer(void* closure)
  1085. {
  1086.   MWContext* context = (MWContext*) closure;
  1087.   BM_Frame* f = GETFRAME(context);
  1088.   f->savetimer = NULL;
  1089.   BM_SaveBookmarks(context, NULL);
  1090. }
  1091.  
  1092. /* The bookmarks have been modified somehow.  Set or reset a timer to cause
  1093.    them to be saved.*/
  1094. static void
  1095. bm_SetModified(MWContext* context, XP_Bool mod)
  1096. {
  1097.   BM_Frame* f = GETFRAME(context);
  1098.   f->gBookmarksModified = mod;
  1099.   f->max_depth = 0;
  1100.   if (f->savetimer) {
  1101.     FE_ClearTimeout(f->savetimer);
  1102.     f->savetimer = NULL;
  1103.   }
  1104.   if (mod) {
  1105.     f->savetimer = FE_SetTimeout(bm_save_timer, context,
  1106.                                  60000L); /* ### hard-coding... */
  1107.     if (!f->savetimer) BM_SaveBookmarks(context, NULL);
  1108.   }
  1109. }
  1110.  
  1111. /* give LI the ability to set the modified to false */
  1112. void
  1113. BM_SetModified(MWContext* context, XP_Bool mod)
  1114. {
  1115.     bm_SetModified(context, mod);
  1116. }
  1117.  
  1118. typedef struct bm_setheader_info {
  1119.   MWContext* context;
  1120.   BM_Entry* entry;
  1121.   XP_Bool isadd;
  1122. } bm_setheader_info;
  1123.  
  1124. static void
  1125. bm_setheader_freeit(void* closure)
  1126. {
  1127.   XP_FREE((bm_setheader_info*) closure);
  1128. }
  1129.  
  1130. static int bm_setheader_undo(void* closure);
  1131.  
  1132. static void
  1133. bm_SetMenuOrAddHeader(MWContext* context, BM_Entry* entry, XP_Bool isadd)
  1134. {
  1135.   BM_Frame* f = GETFRAME(context);
  1136.   XP_ASSERT(context->type == MWContextBookmarks);
  1137.   XP_ASSERT(BM_ISHEADER(entry));
  1138.   if (context->type == MWContextBookmarks && f && BM_ISHEADER(entry)) {
  1139.     if (f->undo) {
  1140.       bm_setheader_info* info = XP_NEW_ZAP(bm_setheader_info);
  1141.       if (!info) {
  1142.         UNDO_DiscardAll(f->undo);
  1143.       } else {
  1144.         info->context = context;
  1145.         info->entry = isadd ? f->addheader : f->menuheader;
  1146.         info->isadd = isadd;
  1147.         UNDO_LogEvent(f->undo, bm_setheader_undo, bm_setheader_freeit, info, NULL, NULL);
  1148.       }
  1149.     }
  1150.     bm_start_batch(context);
  1151.     bm_entry_changed(context, isadd ? f->addheader : f->menuheader);
  1152.     if (isadd) f->addheader = entry;
  1153.     else f->menuheader = entry;
  1154.     bm_entry_changed(context, entry);
  1155.     if (!isadd) BMFE_BookmarkMenuInvalid(context);
  1156.     bm_SetModified(context, TRUE);
  1157.     bm_end_batch(context);
  1158.   }
  1159. }
  1160.  
  1161. int bm_setheader_undo(void* closure)
  1162. {
  1163.   bm_setheader_info* info = (bm_setheader_info*) closure;
  1164.   bm_SetMenuOrAddHeader(info->context, info->entry, info->isadd);
  1165.   return 0;
  1166. }
  1167.  
  1168. BM_Entry*
  1169. BM_GetMenuHeader(MWContext* context)
  1170. {
  1171.   BM_Frame* f = GETFRAME(context);
  1172.   XP_ASSERT(context->type == MWContextBookmarks);
  1173.   return f ? f->menuheader : NULL;
  1174. }
  1175.  
  1176. void BM_SetMenuHeader(MWContext* context, BM_Entry* entry)
  1177. {
  1178.   bm_SetMenuOrAddHeader(context, entry, FALSE);
  1179. }
  1180.  
  1181.  
  1182. BM_Entry*
  1183. BM_GetAddHeader(MWContext* context)
  1184. {
  1185.   BM_Frame* f = GETFRAME(context);
  1186.   XP_ASSERT(context->type == MWContextBookmarks);
  1187.   return f ? f->addheader : NULL;
  1188. }
  1189.  
  1190. void BM_SetAddHeader(MWContext* context, BM_Entry* entry)
  1191. {
  1192.   bm_SetMenuOrAddHeader(context, entry, TRUE);
  1193. }
  1194.  
  1195.  
  1196. typedef struct bm_copy_string_info {
  1197.   MWContext* context;
  1198.   BM_Entry* entry;
  1199.   char** string;
  1200.   char* value;
  1201. } bm_copy_string_info;
  1202.  
  1203.  
  1204. static void
  1205. bm_copy_string_freeit(void* closure)
  1206. {
  1207.   bm_copy_string_info* info = (bm_copy_string_info*) closure;
  1208.   FREEIF(info->value);
  1209.   XP_FREE(info);
  1210. }
  1211.  
  1212. static int bm_copy_string_undo(void* closure);
  1213.  
  1214. static int
  1215. bm_CopyStringWithUndo(MWContext* context, BM_Entry* entry, char** string,
  1216.                       const char* value)
  1217. {
  1218.   BM_Frame* f = GETFRAME(context);
  1219.   int status = 0;
  1220.   bm_SetModified(context, TRUE);
  1221.   if (f->undo) {
  1222.     bm_copy_string_info* info = XP_NEW_ZAP(bm_copy_string_info);
  1223.     if (!info) {
  1224.       UNDO_DiscardAll(f->undo);
  1225.       status = MK_OUT_OF_MEMORY;
  1226.     } else {
  1227.       info->context = context;
  1228.       info->entry = entry;
  1229.       info->string = string;
  1230.       info->value = *string ? XP_STRDUP(*string) : NULL;
  1231.       UNDO_LogEvent(f->undo, bm_copy_string_undo, bm_copy_string_freeit,
  1232.                     info, NULL, NULL);
  1233.     }
  1234.   }
  1235.   if (*string) XP_FREE(*string);
  1236.   *string = value ? XP_STRDUP(value) : NULL;
  1237.   bm_entry_changed(context, entry);
  1238.   return 0;
  1239. }
  1240.  
  1241. static int
  1242. bm_copy_string_undo(void* closure)
  1243. {
  1244.   bm_copy_string_info* info = (bm_copy_string_info*) closure;
  1245.   if (info->string == &(info->entry->nickname)) {
  1246.     /* Have to use BM_SetNickName to get side effect of changing hashtable. */
  1247.     BM_SetNickName(info->context, info->entry, info->value);
  1248.   } else {
  1249.     bm_CopyStringWithUndo(info->context, info->entry, info->string,
  1250.                           info->value);
  1251.   }
  1252.   return 0;
  1253. }
  1254.  
  1255.  
  1256.  
  1257. /* sets the name for a bm entry */
  1258. void
  1259. BM_SetName(MWContext* context, BM_Entry* entry, const char* newName)
  1260. {
  1261.   CHKCONTEXTVOID(context);
  1262.   XP_ASSERT(entry);
  1263.   if (!entry) return;
  1264.   BM_CLEARFLAG(entry, BM_ATTR_ISNEW);
  1265.  
  1266.   switch (entry->type) {
  1267.   case BM_TYPE_URL:
  1268.   case BM_TYPE_HEADER:
  1269.   case BM_TYPE_ADDRESS:
  1270.     if (entry->name == NULL || XP_STRCMP(entry->name, newName) != 0) {
  1271.       bm_CopyStringWithUndo(context, entry, &entry->name, newName);
  1272.       BMFE_BookmarkMenuInvalid(context);
  1273.     }
  1274.     break;
  1275.   case BM_TYPE_ALIAS:
  1276.     BM_SetName(context, entry->d.alias.original, newName);
  1277.     break;
  1278.   }
  1279. }
  1280.  
  1281.  
  1282.  
  1283. /* sets the location field of a bm_url bookmarks entry */
  1284. void
  1285. BM_SetAddress(MWContext* context, BM_Entry* entry, const char* newAddress)
  1286. {
  1287.   CHKCONTEXTVOID(context);
  1288.   XP_ASSERT(entry);
  1289.   if (!entry) return;
  1290.   BM_CLEARFLAG(entry, BM_ATTR_ISNEW);
  1291.  
  1292.   switch (entry->type) {
  1293.   case BM_TYPE_URL:
  1294.     if (entry->d.url.address == NULL ||
  1295.         XP_STRCMP(entry->d.url.address, newAddress) != 0) {
  1296.       bm_CopyStringWithUndo(context, entry, &entry->d.url.address, newAddress);
  1297.     }
  1298.     break;
  1299.   case BM_TYPE_ADDRESS:
  1300.     if (entry->d.address.address == NULL ||
  1301.         XP_STRCMP(entry->d.address.address, newAddress) != 0) {
  1302.       bm_CopyStringWithUndo(context, entry, &entry->d.address.address,
  1303.                             newAddress);
  1304.     }
  1305.     break;
  1306.   case BM_TYPE_ALIAS:
  1307.     BM_SetAddress(context, entry->d.alias.original, newAddress);
  1308.     break;
  1309.   }
  1310. }
  1311.  
  1312.  
  1313. /* sets the target field of a bm_url bookmarks entry */
  1314. void
  1315. BM_SetTarget(MWContext* context, BM_Entry* entry, const char* newTarget)
  1316. {
  1317.   CHKCONTEXTVOID(context);
  1318.   XP_ASSERT(entry);
  1319.   if (!entry) return;
  1320.   BM_CLEARFLAG(entry, BM_ATTR_ISNEW);
  1321.  
  1322.   switch (entry->type) {
  1323.   case BM_TYPE_URL:
  1324.     if (entry->d.url.target == NULL ||
  1325.         XP_STRCMP(entry->d.url.target, newTarget) != 0) {
  1326.       bm_CopyStringWithUndo(context, entry, &entry->d.url.target, newTarget);
  1327.       if (entry->d.url.target[0] == '\0') {
  1328.         entry->d.url.target = NULL;
  1329.       }
  1330.     }
  1331.     break;
  1332.   case BM_TYPE_HEADER:
  1333.     if (entry->d.header.target == NULL ||
  1334.         XP_STRCMP(entry->d.header.target, newTarget) != 0) {
  1335.       bm_CopyStringWithUndo(context, entry, &entry->d.header.target, newTarget);
  1336.       if (entry->d.header.target[0] == '\0') {
  1337.         entry->d.header.target = NULL;
  1338.       }
  1339.     }
  1340.     break;
  1341.   case BM_TYPE_ADDRESS:
  1342.     break;
  1343.   case BM_TYPE_ALIAS:
  1344.     BM_SetAddress(context, entry->d.alias.original, newTarget);
  1345.     break;
  1346.   }
  1347. }
  1348.  
  1349. /* sets the description field of an entry */
  1350. PUBLIC void
  1351. BM_SetDescription(MWContext* context, BM_Entry* entry, const char* newDesc)
  1352. {
  1353.   CHKCONTEXTVOID(context);
  1354.   XP_ASSERT(entry);
  1355.   if (!entry) return;
  1356.   BM_CLEARFLAG(entry, BM_ATTR_ISNEW);
  1357.  
  1358.   switch (entry->type) {
  1359.   case BM_TYPE_URL:
  1360.   case BM_TYPE_HEADER:
  1361.   case BM_TYPE_ADDRESS:
  1362.     if (entry->description == NULL ||
  1363.         XP_STRCMP(entry->description, newDesc)) {
  1364.       bm_CopyStringWithUndo(context, entry, &entry->description, newDesc);
  1365.     }
  1366.     break;
  1367.   case BM_TYPE_ALIAS:
  1368.     BM_SetDescription(context, entry->d.alias.original, newDesc);
  1369.     break;
  1370.   }
  1371. }
  1372.  
  1373.  
  1374. /*    BM_SetNickName returns FALSE if it reported an error to the user .
  1375.  *    It returns TRUE if everything worked fine.
  1376.  *  5-16-95 jefft
  1377.  *  Passing in NULL value removes the entry from the hash table.
  1378.  */
  1379.  
  1380. XP_Bool
  1381. BM_SetNickName(MWContext* context, BM_Entry* entry, const char* value)
  1382. {
  1383.     BM_Frame* f = GETFRAME(context);
  1384.     char* pName;
  1385.     CHKCONTEXT(context);
  1386.     XP_ASSERT(context->type == MWContextAddressBook);
  1387.     if (!entry) return(TRUE); 
  1388.     if (!value) {
  1389.       /* 5-16-95 jefft -- bug#: 20808, remove entry from the hash table */
  1390.         if (entry->nickname && *entry->nickname) {
  1391.             XP_Remhash(f->nicknameTable, entry->nickname);
  1392.             FREEIF(entry->nickname);
  1393.         }
  1394.         return(TRUE);
  1395.     }
  1396.  
  1397.     /* allocate a copy of the string so we can modify it to be a legal alias */
  1398.     /* But only if value is non-null */
  1399.     pName = (value) ? XP_STRDUP(value) : NULL;
  1400.     if (value && !pName) return(FALSE);
  1401.  
  1402.     BM_CLEARFLAG(entry, BM_ATTR_ISNEW);
  1403.  
  1404.     if (BM_ISALIAS(entry)) {
  1405.         XP_Bool retVal;
  1406.         retVal = BM_SetNickName(context, entry->d.alias.original, value);
  1407.         XP_FREE(pName);
  1408.         return(retVal);
  1409.     } else {
  1410.         if (entry->nickname == NULL || value == NULL ||    XP_STRCMP(entry->nickname, value)) {
  1411.             if (pName != NULL)
  1412.             {
  1413.                 char* ptr;
  1414.                 for (ptr = pName ; *ptr ; ptr++) {
  1415.                     if (!isalnum(*ptr) && (*ptr != '-') && (*ptr != '_')) {
  1416.                         FE_Alert(context, XP_GetString(XP_BKMKS_INVALID_NICKNAME));
  1417.                         XP_FREE(pName);
  1418.                         return(FALSE);
  1419.                     }
  1420.                     /* convert to lowercase */
  1421.                     if (isupper(*ptr)) {
  1422.                         *ptr = (char)tolower(*ptr);
  1423.                     }
  1424.                 }
  1425.                 if (XP_Gethash(f->nicknameTable, pName, NULL)) {
  1426.                     FE_Alert(context, XP_GetString(XP_BKMKS_NICKNAME_ALREADY_EXISTS));
  1427.                     FREEIF(pName);
  1428.                     return(FALSE);
  1429.                 }
  1430.             }
  1431.         }
  1432.         if (entry->nickname && *entry->nickname) {
  1433.             XP_Remhash(f->nicknameTable, entry->nickname);
  1434.         }
  1435.         bm_CopyStringWithUndo(context, entry, &entry->nickname, pName);
  1436.         if (entry->nickname && *entry->nickname) {
  1437.             XP_Puthash(f->nicknameTable, entry->nickname, entry);
  1438.         }
  1439.     }
  1440.     FREEIF(pName);
  1441.     return(TRUE);
  1442. }
  1443.  
  1444.  
  1445.  
  1446. void
  1447. BM_CancelEdit(MWContext* context, BM_Entry* entry)
  1448. {
  1449.   CHKCONTEXTVOID(context);
  1450.   bm_start_batch(context);
  1451.   if (entry && (entry->flags & BM_ATTR_ISNEW) &&
  1452.         !(entry->flags & BM_ATTR_HASALIASES) && entry->parent) {
  1453.     BM_RemoveChildFromHeader(context, entry->parent, entry);
  1454.     BM_FreeEntry(context, entry);
  1455.     bm_refresh(context, 1, BM_LAST_CELL);
  1456.   }
  1457.   bm_end_batch(context);
  1458. }
  1459.  
  1460.  
  1461.  
  1462. /* returns the number of children parent has
  1463.     if visible is TRUE, only visible children are counted,
  1464.     otherwise all children are counted */
  1465. static int32
  1466. bm_CountChildren(BM_Entry* parent, XP_Bool visible)
  1467. {
  1468.   BM_Entry*        child;
  1469.   int32            count = 1;
  1470.  
  1471.   XP_ASSERT(parent);
  1472.   XP_ASSERT(BM_ISHEADER(parent));
  1473.  
  1474.   if (!parent || !BM_ISHEADER(parent)) return 0;
  1475.  
  1476.   if (!visible || !(parent->flags & BM_ATTR_FOLDED)) {
  1477.     child = parent->d.header.children;
  1478.     while (child) {
  1479.       if (BM_ISHEADER(child)) {
  1480.         count += bm_CountChildren(child, visible);
  1481.       } else {
  1482.         count++;
  1483.       }
  1484.       child = child->next;
  1485.     }
  1486.   }
  1487.  
  1488.   return count;
  1489. }
  1490.  
  1491.  
  1492. static void
  1493. bm_WidestEntry_1(MWContext* context, BM_Entry* parent, BM_Entry** widest,
  1494.                  uint32* widestWidth)
  1495. {
  1496.   BM_Entry*        child;
  1497.   uint32            width;
  1498.   uint32            height;
  1499.  
  1500.   XP_ASSERT(parent);
  1501.   XP_ASSERT(BM_ISHEADER(parent));
  1502.   XP_ASSERT(widestWidth);
  1503.   XP_ASSERT(widest);
  1504.  
  1505.   BMFE_MeasureEntry(context, parent, &width, &height);
  1506.  
  1507.   if (width > *widestWidth) {
  1508.     *widestWidth = width;
  1509.     *widest = parent;
  1510.   }
  1511.  
  1512.   if (!(BM_ISFOLDED(parent))) {
  1513.     child = parent->d.header.children;
  1514.     while (child) {
  1515.       if (BM_ISHEADER(child)) {
  1516.         bm_WidestEntry_1(context, child, widest, widestWidth);
  1517.       } else {
  1518.         BMFE_MeasureEntry(context, child, &width, &height);
  1519.         if (width > *widestWidth) {
  1520.           *widestWidth = width;
  1521.           *widest = child;
  1522.         }
  1523.       }
  1524.       child = child->next;
  1525.     }
  1526.   }
  1527. }
  1528.  
  1529. /* returns the widest visible entry in the tree
  1530. (this uses a FE function to measure the width) */
  1531. PUBLIC BM_Entry*
  1532. BM_WidestEntry(MWContext* context)
  1533. {
  1534.   BM_Entry* widest = NULL;
  1535.   uint32 widestWidth = 0;
  1536.   CHKCONTEXT(context);
  1537.   
  1538.   bm_WidestEntry_1(context, BM_GetRoot(context), &widest, &widestWidth);
  1539.   return widest;
  1540. }
  1541.  
  1542.  
  1543. static void
  1544. bm_SyncCount(MWContext* context)
  1545. {
  1546.   BM_Frame* f = GETFRAME(context);
  1547.   if (f) {
  1548.     f->gCount = -1;
  1549.     f->gVisCount = -1;
  1550.   }
  1551.   BMFE_SyncDisplay(context);
  1552. }
  1553.  
  1554. PRIVATE void
  1555. bm_SyncSelection_1(BM_Entry* parent, int32* count, uint32* selectionMask)
  1556. {
  1557.   BM_Entry* child;
  1558.  
  1559.   XP_ASSERT(parent);
  1560.   XP_ASSERT(BM_ISHEADER(parent));
  1561.  
  1562.   if (parent->flags & BM_ATTR_SELECTED) {
  1563.     *selectionMask |= BM_TYPE_HEADER;
  1564.     (*count)++;
  1565.   }
  1566.  
  1567.   child = parent->d.header.children;
  1568.   while (child) {
  1569.     if (BM_ISHEADER(child)) {
  1570.       bm_SyncSelection_1(child, count, selectionMask);
  1571.     } else {
  1572.       if (BM_ISSELECTED(child)) {
  1573.         *selectionMask |= child->type;
  1574.         (*count)++;
  1575.       }
  1576.     }
  1577.     child = child->next;
  1578.   }
  1579. }
  1580.  
  1581. /* synchronizes the selection mask and the selection count with
  1582.     what is actually selected
  1583.     this is necessary when items become deselected because
  1584.     we don't have a global selection list, only a count and
  1585.     mask
  1586. */
  1587. static void
  1588. bm_SyncSelection(MWContext* context)
  1589. {
  1590.   BM_Frame* f = GETFRAME(context);
  1591.   f->gSelectionCount = 0;
  1592.   f->gSelectionMask = 0;
  1593.  
  1594.   bm_SyncSelection_1(BM_GetRoot(context), &(f->gSelectionCount),
  1595.                      &(f->gSelectionMask));
  1596. }
  1597.  
  1598. /* return the index number of item in cur_count with regards
  1599. to the BM_ATTR_FOLDED flag */
  1600. PRIVATE int32
  1601. bm_GetIndexNum(BM_Entry* parent, BM_Entry* item, int32* cur_count)
  1602. {
  1603.   BM_Entry* child;
  1604.   int32 rv = 0;
  1605.  
  1606.   XP_ASSERT(parent);
  1607.   XP_ASSERT(BM_ISHEADER(parent));
  1608.  
  1609.   child = parent->d.header.children;
  1610.  
  1611.   if (parent == item) return *cur_count;
  1612.  
  1613.   while (child) {
  1614.     (*cur_count)++;
  1615.  
  1616.     if (child == item) {
  1617.       return *cur_count;
  1618.     }
  1619.  
  1620.     /* if it's a header and it's unfolded, traverse it's children */
  1621.     if (child->type == BM_TYPE_HEADER && !BM_ISFOLDED(child)) {
  1622.       rv = bm_GetIndexNum(child, item, cur_count);
  1623.       if (rv)
  1624.         return rv;
  1625.     }
  1626.     child = child->next;
  1627.   }
  1628.   return 0;
  1629. }
  1630.  
  1631. /* return the index number of item in cur_count without regards to
  1632.    the BM_ATTR_FOLDED flag */
  1633. PRIVATE int32
  1634. bm_GetUnfoldedIndexNum(BM_Entry* parent, BM_Entry* item, int32* cur_count)
  1635. {
  1636.   BM_Entry* child;
  1637.   int32 rv = 0;
  1638.  
  1639.   XP_ASSERT(parent);
  1640.   XP_ASSERT(parent->type == BM_TYPE_HEADER);
  1641.  
  1642.   if (parent == item) return *cur_count;
  1643.  
  1644.   for (child = parent->d.header.children; child; child = child->next) {
  1645.     (*cur_count)++;
  1646.  
  1647.     if (child == item) return *cur_count;
  1648.  
  1649.  
  1650.     if (child->type == BM_TYPE_HEADER) {
  1651.       rv = bm_GetUnfoldedIndexNum(child, item, cur_count);
  1652.       if (rv) return rv;
  1653.     }
  1654.   }
  1655.   return 0;
  1656. }
  1657.  
  1658. /* returns the child url entry of parent whose address is the same as
  1659.    url_address */
  1660. PRIVATE void
  1661. bm_FindItemStub(MWContext *context, BM_Entry* parent, char* url_address, EntryFunc pf, void *pClosure)
  1662. {
  1663.   BM_Entry*    child;
  1664.  
  1665.   if (!parent) { /* Eric made me do it */
  1666.     return;
  1667.   }
  1668.  
  1669.   for (child = parent->d.header.children; child; child = child->next) {
  1670.     if (child->type == BM_TYPE_URL && child->d.url.address &&
  1671.         !XP_STRCMP(child->d.url.address, url_address)) {
  1672.       (*pf)(context, child, pClosure);
  1673.     }
  1674.     if (child->type == BM_TYPE_HEADER) {
  1675.       bm_FindItemStub(context, child, url_address, pf, pClosure);
  1676.     }
  1677.   }
  1678.   return;
  1679. }
  1680.  
  1681.  
  1682. PRIVATE int32
  1683. bm_GetDepth(BM_Entry* parent, BM_Entry* item)
  1684. {
  1685.   int32 rv = 0;
  1686.   BM_Entry* next;
  1687.  
  1688.   if (!item) return -1;
  1689.  
  1690.   next = item;
  1691.   while (next && next->parent) { /* I think extra "next &&" is
  1692.                                            necessary for Win16 busted
  1693.                                            optimizer... */
  1694.     rv++;
  1695.     next = next->parent;
  1696.   }
  1697.   return rv;
  1698. }
  1699.  
  1700.  
  1701.  
  1702. static void
  1703. bm_simple_freeit(void* closure)
  1704. {
  1705.   XP_FREE(closure);
  1706. }
  1707.  
  1708.  
  1709. typedef struct bm_delete_child_info {
  1710.   MWContext* context;
  1711.   BM_Entry* parent;
  1712.   BM_Entry* child;
  1713. } bm_delete_child_info;
  1714.  
  1715.  
  1716. static int
  1717. bm_delete_child_doit(void* closure)
  1718. {
  1719.   bm_delete_child_info* info = (bm_delete_child_info*) closure;
  1720.   BM_RemoveChildFromHeader(info->context, info->parent, info->child);
  1721.   return 0;
  1722. }
  1723.  
  1724.  
  1725. static void
  1726. bm_LogDeleteChild(MWContext* context, BM_Entry* parent, BM_Entry* child)
  1727. {
  1728.   BM_Frame* f = GETFRAME(context);
  1729.   bm_delete_child_info* info;
  1730.  
  1731.   /* Magic side effect -- if a child has just been added, and it doesn't have
  1732.      an addition date set, set it to be now. */
  1733.   if (child->addition_date == 0) {
  1734.     child->addition_date = XP_TIME();
  1735.   }
  1736.  
  1737.   if (!f || !f->undo) return;
  1738.   bm_SetModified(context, TRUE);
  1739.   info = XP_NEW_ZAP(bm_delete_child_info);
  1740.   if (!info) {
  1741.     UNDO_DiscardAll(f->undo);
  1742.   } else {
  1743.     info->context = context;
  1744.     info->parent = parent;
  1745.     info->child = child;
  1746.     UNDO_LogEvent(f->undo, bm_delete_child_doit, bm_simple_freeit, info, NULL, NULL);
  1747.   }
  1748. }
  1749.  
  1750. /* appends a child item to a parent at the end of the
  1751.     parents child list */
  1752. static void
  1753. bm_AppendChildToHeader(MWContext* context, BM_Entry* parent, BM_Entry* child)
  1754. {
  1755.   BM_Frame* f = GETFRAME(context);
  1756.   BM_Entry* lastChild;
  1757.  
  1758.   XP_ASSERT(parent);
  1759.   XP_ASSERT(BM_ISHEADER(parent));
  1760.   XP_ASSERT(child);
  1761.   XP_ASSERT(child != parent);
  1762.  
  1763.   f->gCount = -1;
  1764.   f->gVisCount = -1;
  1765.  
  1766.   lastChild = parent->d.header.lastChild;
  1767.   if (lastChild) {
  1768.     lastChild->next = child;
  1769.     parent->d.header.lastChild = child;
  1770.   } else {
  1771.     parent->d.header.children = child;
  1772.     parent->d.header.lastChild = child;
  1773.   }
  1774.  
  1775.   parent->d.header.childCount++;
  1776.   child->parent = parent;
  1777.   
  1778.   if( !f->bSorting )
  1779.     child->iNaturalIndex = g_iNaturalIndexPool++;
  1780.     
  1781.   if (context) {
  1782.     BMFE_BookmarkMenuInvalid(context);
  1783.     bm_LogDeleteChild(context, parent, child);
  1784.   }
  1785. }
  1786.  
  1787. void
  1788. BM_AppendToHeader(MWContext* context, BM_Entry* parent, BM_Entry* child)
  1789. {
  1790.   int index;
  1791.   bm_start_batch(context);
  1792.   bm_AppendChildToHeader(context, parent, child);
  1793.   index = BM_GetIndex(context, child);
  1794.   if (index > 0) bm_refresh(context, index, BM_LAST_CELL);
  1795.   bm_end_batch(context);
  1796. }
  1797.  
  1798.  
  1799. /* Add a child item to a parent at the beginning of the
  1800.     parents child list */
  1801. void
  1802. BM_PrependChildToHeader(MWContext* context, BM_Entry* parent, BM_Entry* child)
  1803. {
  1804.   BM_Frame* f = GETFRAME(context);
  1805.   BM_Entry* firstChild;
  1806.  
  1807.   XP_ASSERT(parent);
  1808.   XP_ASSERT(parent->type == BM_TYPE_HEADER);
  1809.   XP_ASSERT(child);
  1810.   XP_ASSERT(child != parent);
  1811.  
  1812.   f->gCount = -1;
  1813.   f->gVisCount = -1;
  1814.   firstChild = parent->d.header.children;
  1815.   if (!firstChild) {
  1816.     bm_AppendChildToHeader(context, parent, child);
  1817.   } else {
  1818.     child->next = firstChild;
  1819.     parent->d.header.children = child;
  1820.  
  1821.     parent->d.header.childCount++;
  1822.     child->parent = parent;
  1823.     
  1824.     if( !f->bSorting )    
  1825.       child->iNaturalIndex = g_iNaturalIndexPool++;
  1826.       
  1827.     if (context) {
  1828.       BMFE_BookmarkMenuInvalid(context);
  1829.       bm_LogDeleteChild(context, parent, child);
  1830.     }
  1831.   }
  1832. }
  1833.  
  1834.  
  1835. static int
  1836. bm_SortAddressBook(const void* obj1, const void* obj2)
  1837. {
  1838.   const BM_Entry* entry1 = (const BM_Entry*) obj1;
  1839.   const BM_Entry* entry2 = (const BM_Entry*) obj2;
  1840.  
  1841.   if (BM_ISALIAS(entry1)) {
  1842.     entry1 = entry1->d.alias.original;
  1843.     XP_ASSERT(!BM_ISALIAS(entry1));
  1844.   }
  1845.   if (BM_ISALIAS(entry2)) {
  1846.     entry2 = entry2->d.alias.original;
  1847.     XP_ASSERT(!BM_ISALIAS(entry2));
  1848.   }
  1849.   XP_ASSERT(BM_ISHEADER(entry1) || BM_ISADDRESS(entry1));
  1850.   XP_ASSERT(BM_ISHEADER(entry2) || BM_ISADDRESS(entry2));
  1851.   if (entry1 == entry2) return 0; /* Can happen with two aliases to the same
  1852.                                      thing... */
  1853.   if (BM_ISHEADER(entry1)) {
  1854.     if (BM_ISHEADER(entry2)) {
  1855. #ifdef INTL_SORT
  1856.       return XP_StrColl(entry1->name, entry2->name);
  1857. #else
  1858.       return XP_STRCMP(entry1->name, entry2->name);
  1859. #endif
  1860.     } else {
  1861.       return 1;
  1862.     }
  1863.   } else {
  1864.     if (BM_ISHEADER(entry2)) {
  1865.       return -1;
  1866.     } else {
  1867. #ifdef INTL_SORT
  1868.       return XP_StrColl(entry1->name, entry2->name);
  1869. #else
  1870.       return XP_STRCMP(entry1->name, entry2->name);
  1871. #endif
  1872.     }
  1873.   }
  1874. }
  1875.  
  1876.  
  1877. static BM_Entry*
  1878. bm_RealEntry(BM_Entry* entry)
  1879. {
  1880.   if (BM_ISALIAS(entry)) return entry->d.alias.original;
  1881.   else return entry;
  1882. }
  1883.  
  1884.  
  1885. /* Adds a child item to a parent, sorting it according to address book sorting
  1886.    rules. */
  1887. static void
  1888. bm_AddChildToHeaderSorted(MWContext* context, BM_Entry* parent,
  1889.                           BM_Entry* child)
  1890. {
  1891.   BM_Frame* f = GETFRAME(context);
  1892.   BM_Entry* entry;
  1893.   BM_Entry* previous = NULL;
  1894.   XP_ASSERT(context->type == MWContextAddressBook);
  1895.   XP_ASSERT(BM_ISHEADER(parent));
  1896.   if (!BM_ISALIAS(child)) parent = BM_GetRoot(context);
  1897.   if (parent->d.header.lastChild &&
  1898.       bm_SortAddressBook(parent->d.header.lastChild, child) < 0) {
  1899.     /* Ah, the most common case (especially when loading from a file).  This
  1900.        kid goes last. */
  1901.     previous = parent->d.header.lastChild;
  1902.     bm_AppendChildToHeader(context, parent, child);
  1903.   } else {
  1904.     for (entry = parent->d.header.children ; entry ; entry = entry->next) {
  1905.       int value = bm_SortAddressBook(entry, child);
  1906.       if (value > 0) break;
  1907.       if (value == 0) {
  1908.         /* Hmm.  Let's not allow any duplicate aliases to the same thing
  1909.            in the same header. */
  1910.         if (bm_RealEntry(entry) == bm_RealEntry(child)) {
  1911.           if (BM_ISALIAS(child)) {
  1912.             BM_FreeEntry(context, child);
  1913.           } else {
  1914.             XP_ASSERT(BM_ISALIAS(entry));
  1915.             BM_RemoveChildFromHeader(context, parent, entry);
  1916.             bm_AddChildToHeaderSorted(context, parent, child);
  1917.           }
  1918.           return;
  1919.         }
  1920.       }
  1921.       previous = entry;
  1922.     }
  1923.     if (previous == NULL) {
  1924.       BM_PrependChildToHeader(context, parent, child);
  1925.       previous = parent;
  1926.     } else {
  1927.       bm_InsertItemAfter(context, previous, child, FALSE);
  1928.     }
  1929.   }
  1930.   f->gCount = -1;
  1931.   f->gVisCount = -1;
  1932.   if (!BM_ISFOLDED(parent) && !bm_refreshing_all(context)) {
  1933.     int index = BM_GetIndex(context, previous);
  1934.     if (index > 0) {
  1935.       f->gVisCount++;
  1936.       bm_refresh(context, index + 1, BM_LAST_CELL);
  1937.     }
  1938.   }
  1939. }
  1940.  
  1941. static BM_Entry*
  1942. bm_get_previous(BM_Entry* entry)
  1943. {
  1944.   BM_Entry*    child;
  1945.   BM_Entry*    previous = NULL;
  1946.  
  1947.   if (entry && entry->parent) {
  1948.     child = entry->parent->d.header.children;
  1949.     previous = NULL;
  1950.     while (child && child != entry) {
  1951.       previous = child;
  1952.       child = child->next;
  1953.     }
  1954.   }
  1955.  
  1956.   if (child == NULL) previous = NULL;
  1957.  
  1958.   return previous;
  1959. }
  1960.  
  1961.  
  1962.  
  1963. typedef struct bm_add_child_info {
  1964.   MWContext* context;
  1965.   BM_Entry* parent;
  1966.   BM_Entry* previous;
  1967.   BM_Entry* child;
  1968. } bm_add_child_info;
  1969.  
  1970.  
  1971. static int
  1972. bm_add_child_doit(void* closure)
  1973. {
  1974.   bm_add_child_info* info = (bm_add_child_info*) closure;
  1975.   XP_ASSERT(info->previous == NULL || info->previous->parent == info->parent);
  1976.   if (info->previous) {
  1977.     bm_InsertItemAfter(info->context, info->previous, info->child, FALSE);
  1978.   } else {
  1979.     BM_PrependChildToHeader(info->context, info->parent, info->child);
  1980.   }
  1981.   BM_ClearAllSelection(info->context, FALSE);
  1982.   return 0;
  1983. }
  1984.  
  1985.  
  1986.  
  1987. void BM_RemoveChildFromHeader(MWContext* context, BM_Entry* parent, BM_Entry* child)
  1988. {
  1989.   BM_Frame* f = GETFRAME(context);
  1990.   BM_Entry* previous;
  1991.  
  1992.   XP_ASSERT(BM_ISHEADER(parent));
  1993.   if (!BM_ISHEADER(parent)) return;
  1994.   XP_ASSERT(child);
  1995.   if (!child) return;
  1996.   XP_ASSERT(child != parent);
  1997.   if (child == parent) return;
  1998.   XP_ASSERT(child->parent == parent);
  1999.   if (child->parent != parent) return;
  2000.  
  2001.   if (context && (child->flags & BM_ATTR_SELECTED)) {
  2002.     BM_SelectItem(context, child, TRUE, TRUE, FALSE);
  2003.   }
  2004.   previous = bm_get_previous(child);
  2005.  
  2006.   if (previous) previous->next = child->next;
  2007.  
  2008.   if (parent->d.header.children == child) {
  2009.     parent->d.header.children = child->next;
  2010.   }
  2011.  
  2012.   if (parent->d.header.lastChild == child) {
  2013.     parent->d.header.lastChild = previous;
  2014.   }
  2015.  
  2016.   f->gCount = -1;
  2017.   f->gVisCount = -1;
  2018.  
  2019.   parent->d.header.childCount--;
  2020.   
  2021.   if (context) {
  2022.     BM_Frame* f = GETFRAME(context);
  2023.     bm_add_child_info* info;
  2024.     BMFE_BookmarkMenuInvalid(context);
  2025.     bm_SetModified(context, TRUE);
  2026.     if (f->undo) {
  2027.       info = XP_NEW_ZAP(bm_add_child_info);
  2028.       if (!info) {
  2029.         UNDO_DiscardAll(f->undo);
  2030.       } else {
  2031.         info->context = context;
  2032.         info->parent = parent;
  2033.         info->previous = previous;
  2034.         info->child = child;
  2035.         UNDO_LogEvent(f->undo, bm_add_child_doit, bm_simple_freeit, info, NULL, NULL);
  2036.       }
  2037.     }
  2038.   }
  2039.   child->parent = NULL;
  2040.   child->next = NULL;
  2041. }
  2042.  
  2043.  
  2044.  
  2045. #define BM_HEADER_BEGIN        0xD000
  2046. #define BM_HEADER_END        0xE000
  2047. #define BM_UNKNOWN            0xF000
  2048.  
  2049. static uint16
  2050. bm_tokenize_line(MWContext* context, char* buffer, char** ptr)
  2051. {
  2052.   if ((*ptr = strcasestr(buffer, "HREF=\""))) {
  2053.     return context->type == MWContextBookmarks ? BM_TYPE_URL : BM_TYPE_ADDRESS;
  2054.   } else if ((*ptr = strcasestr(buffer, "<H")) && isdigit(*(*ptr + 2))) {
  2055.     return BM_TYPE_HEADER;
  2056.   } else if ((*ptr = strcasestr(buffer, "<HR>"))) {
  2057.     return BM_TYPE_SEPARATOR;
  2058.   } else if (strcasestr(buffer, "</UL>") ||
  2059.              strcasestr(buffer, "</MENU>") ||
  2060.              strcasestr(buffer, "</DL>")) {
  2061.     return BM_HEADER_END;
  2062.   } else if (strcasestr(buffer, "<UL>") ||
  2063.              strcasestr(buffer, "<MENU>") ||
  2064.              strcasestr(buffer, "<DL>")) {
  2065.     return BM_HEADER_BEGIN;
  2066.   } else {
  2067.     return BM_UNKNOWN;
  2068.   }
  2069. }
  2070.  
  2071. /* parse out the folded state in buffer */
  2072. static XP_Bool
  2073. bm_is_folded(char* buffer)
  2074. {
  2075.   XP_ASSERT(buffer);
  2076.   return strcasestr(buffer, "FOLDED") != NULL;
  2077. }
  2078.  
  2079. /* parse out the addition date in buffer */
  2080. static time_t
  2081. bm_addition_date(char* buffer)
  2082. {
  2083.     char*        ptr;
  2084.     char*        end;
  2085.     time_t        add_date = 0;
  2086.  
  2087.     XP_ASSERT(buffer);
  2088.  
  2089.     ptr = strcasestr(buffer, "ADD_DATE=\"");
  2090.     if (ptr)
  2091.     {
  2092.         /* find the end of the addition date */
  2093.         end = XP_STRCHR(ptr + 10, '"');
  2094.         if (end)
  2095.         {
  2096.             /* temporarily stick a NULL in the buffer */
  2097.             *end = '\0';
  2098.  
  2099.             add_date = (time_t)atol(ptr + 10);
  2100.  
  2101.             /* replace the quote */
  2102.             *end = '"';
  2103.         }
  2104.     }
  2105.     return add_date;
  2106. }
  2107.  
  2108. /* parse out the last visited or last modified date in buffer */
  2109. static time_t
  2110. bm_last_date(char* buffer, XP_Bool ismodified)
  2111. {
  2112.     char* ptr;
  2113.     char* start;
  2114.     char* end;
  2115.     time_t result = 0;
  2116.  
  2117.     ptr = strcasestr(buffer,
  2118.         ismodified ? "LAST_MODIFIED=\"": "LAST_VISIT=\"");
  2119.     if (ptr) {
  2120.         start = ptr + (ismodified ? 15 : 12);
  2121.         end = XP_STRCHR(start, '"');
  2122.         if (end) {
  2123.             /* temporarily stick a NULL in the buffer */
  2124.             *end = '\0';
  2125.  
  2126.             result = (time_t)atol(start);
  2127.  
  2128.             /* replace the quote */
  2129.             *end = '"';
  2130.         }
  2131.     }
  2132.     return result;
  2133. }
  2134.  
  2135.  
  2136. /* parse out the target string in buffer */
  2137. static char *
  2138. bm_target(char* buffer, XP_Bool ismodified)
  2139. {
  2140.     char* ptr;
  2141.     char* start;
  2142.     char* end;
  2143.     char *result = NULL;
  2144.  
  2145.     ptr = strcasestr(buffer, "TARGET=\"");
  2146.     if (ptr) {
  2147.         start = ptr + 8;
  2148.         end = XP_STRCHR(start, '"');
  2149.         if (end) {
  2150.             /* temporarily stick a NULL in the buffer */
  2151.             *end = '\0';
  2152.  
  2153.             result = (start) ? XP_STRDUP(start) : NULL;
  2154.  
  2155.             /* replace the quote */
  2156.             *end = '"';
  2157.         }
  2158.     }
  2159.     return result;
  2160. }
  2161.  
  2162.  
  2163. typedef struct bm_alias_info {
  2164.   char* id;                    /* String to use for this alias in the file. */
  2165.   char* key;                /* Key to use to lookup this alias in the table */
  2166.   BM_Entry* entry;
  2167. } bm_alias_info;
  2168.  
  2169.  
  2170. static bm_alias_info* 
  2171. bm_find_alias_info(MWContext* context, const char* ptr, XP_Bool create)
  2172. {
  2173.   BM_Frame* f = GETFRAME(context);
  2174.   bm_alias_info* info = NULL;
  2175.   if (f->aliasTable) {
  2176.     info = (bm_alias_info *)XP_Gethash(f->aliasTable, ptr, NULL);
  2177.     XP_ASSERT(info == NULL || XP_STRCMP(ptr, info->key) == 0);
  2178.     if (!info && create) {
  2179.       info = XP_NEW_ZAP(bm_alias_info);
  2180.       if (info) {
  2181.         info->key = XP_STRDUP(ptr);
  2182.         XP_Puthash(f->aliasTable, info->key, info);
  2183.       }
  2184.     }
  2185.   }
  2186.   return info;
  2187. }
  2188.  
  2189. static bm_alias_info*
  2190. bm_find_writealias_info(MWContext* context, BM_Entry* entry)
  2191. {
  2192.   BM_Frame* f = GETFRAME(context);
  2193.   static char key[20];
  2194.   bm_alias_info* info;
  2195.   XP_SPRINTF(key, "%ld", (long) entry);
  2196.   info = bm_find_alias_info(context, key, TRUE);
  2197.   if (info && info->id == NULL) {
  2198.     info->id = (char *)XP_ALLOC(10);
  2199.     if (info->id) {
  2200.       XP_SPRINTF(info->id, "%d", f->aliasID++);
  2201.     }
  2202.   }
  2203.   return info;
  2204. }
  2205.  
  2206. static XP_Bool 
  2207. bm_free_alias_info(XP_HashTable table, const void* key, void* value,
  2208.                    void* closure)
  2209. {
  2210.   bm_alias_info* info = (bm_alias_info*) value;
  2211.   FREEIF(info->id);
  2212.   FREEIF(info->key);
  2213.   XP_FREE(info);
  2214.   return TRUE;
  2215. }
  2216.  
  2217.  
  2218. static int
  2219. bm_string_cmp (const void *obj1, const void *obj2)
  2220. {
  2221.   XP_ASSERT (obj1 && obj2);
  2222.   return XP_STRCMP((char*) obj1, (char*) obj2);
  2223. }
  2224.  
  2225. static void
  2226. bm_clear_alias_info(MWContext* context)
  2227. {
  2228.   BM_Frame* f = GETFRAME(context);
  2229.   if (f->aliasTable) {
  2230.     XP_Maphash(f->aliasTable, bm_free_alias_info, NULL);
  2231.     XP_Clrhash(f->aliasTable);
  2232.   } else {
  2233.     f->aliasTable = XP_HashTableNew(100, XP_StringHash, bm_string_cmp);
  2234.   }
  2235.   f->aliasID = 0;
  2236. }
  2237.  
  2238.  
  2239.  
  2240. /* Checks if the given item is an alias to another item, or has aliases to it.
  2241.    Takes care of all the required tree mucking, and updating of the alias
  2242.    table.  The return value is the item for the caller to insert into the
  2243.    tree; it is usually but not always the item passed in. */
  2244. static BM_Entry*
  2245. bm_check_read_alias(MWContext* context, BM_Entry* new_item, char* parseString)
  2246. {
  2247.   char* ptr;
  2248.   char* end = NULL;
  2249.   ptr = strcasestr(parseString, "ALIASID=\"");
  2250.   if (ptr) {
  2251.     ptr += 9;
  2252.     end = XP_STRCHR(ptr, '"');
  2253.     if (end) {
  2254.       bm_alias_info* info;
  2255.       *end = '\0';
  2256.       info = bm_find_alias_info(context, ptr, TRUE);
  2257.       if (info) {
  2258.         if (info->entry) {
  2259.           /* Sigh.  We have the definition of an alias, but there
  2260.              have already been some references to this alias, and we don't
  2261.              really want to go chasing the references down.  Instead, we'll
  2262.              just copy our data into the existing record.  Yikes. */
  2263.           BM_Entry* tmp = XP_NEW(BM_Entry);
  2264.           if (tmp) {
  2265.             XP_MEMCPY(tmp, info->entry, sizeof(BM_Entry));
  2266.             XP_MEMCPY(info->entry, new_item, sizeof(BM_Entry));
  2267.             BM_FreeEntry(context, tmp);
  2268.             XP_FREE(new_item);
  2269.             new_item = info->entry;
  2270.           }
  2271.         } else {
  2272.           info->entry = new_item;
  2273.         }
  2274.       }
  2275.       BM_SETFLAG(new_item, BM_ATTR_HASALIASES);
  2276.     }
  2277.   } else {
  2278.     ptr = strcasestr(parseString, "ALIASOF=\"");
  2279.     if (ptr) {
  2280.       ptr += 9;
  2281.       end = XP_STRCHR(ptr, '"');
  2282.       if (end) {
  2283.         bm_alias_info* info;
  2284.         *end = '\0';
  2285.         info = bm_find_alias_info(context, ptr, TRUE);
  2286.         if (info) {
  2287.           if (info->entry == NULL) {
  2288.             /* Even though this entry is probably not complete, it will
  2289.                do better than nothing.  If something better comes along,
  2290.                this one will get replaced.  If not, we'll insert this
  2291.                item into the main tree at the end. */
  2292.             info->entry = new_item;
  2293.           } else {
  2294.             BM_FreeEntry(context, new_item);
  2295.           }
  2296.           new_item = bm_NewAlias(info->entry);
  2297.           if (!new_item) return NULL;
  2298.         }
  2299.       }
  2300.     }
  2301.   }
  2302.  
  2303.   /* replace the quotes */
  2304.   if (end) *end = '"';
  2305.   return new_item;
  2306. }
  2307.  
  2308. static void
  2309. bm_check_nickname(MWContext* context, BM_Entry* entry, char* str)
  2310. {
  2311.   char* ptr = strcasestr(str, "NICKNAME=\"");
  2312.   char* end;
  2313.   if (ptr) {
  2314.     ptr += 10;
  2315.     end = XP_STRCHR(ptr, '"');
  2316.     if (end) {
  2317.       *end = '\0';
  2318.       BM_SetNickName(context, entry, ptr);
  2319.       *end = '"';
  2320.     }
  2321.   }
  2322. }
  2323.  
  2324. /*
  2325. // Replace all occurances of escaped quotes (%22) with explicit quotes (").
  2326. // Do not replace beyond the end of the " delimited string.
  2327. // 
  2328. // Return a ptr to the position after the last occurance of an escaped quote.
  2329. */
  2330. static char *bm_explicit_quotes( char *pszSource )
  2331. {
  2332.     char *pszCsr  = NULL;
  2333.     char *pszLast = NULL;
  2334.     
  2335.     if( !pszSource )
  2336.     {
  2337.         return pszSource;
  2338.     }
  2339.     
  2340.     
  2341.     pszLast = XP_STRCHR( pszSource, '"' );
  2342.     
  2343.     while( (pszCsr = strstr( pszSource, "%22" )) && (pszCsr < pszLast) )
  2344.     {
  2345.         *pszCsr = '"';    
  2346.         pszSource = pszCsr + 1;
  2347.     XP_MEMMOVE( pszSource, pszSource+2, XP_STRLEN(pszSource+2)+1 );
  2348.     }    
  2349.  
  2350.     return pszSource;
  2351. }
  2352.  
  2353.  
  2354. static BM_Entry*
  2355. bm_read_url(MWContext* context, XP_File fp, char* buffer, char* ptr,
  2356.             const char* relative_url)
  2357. {
  2358.   char* endQuote;
  2359.   char* gtr_than;
  2360.   char* parseString;
  2361.   char* end;
  2362.   char* url;
  2363.   char *pszAfterLastEscapedQuote;
  2364.   BM_Entry* new_item = NULL;
  2365.  
  2366.   /* find next quote */
  2367.   parseString = ptr + 6;
  2368.  
  2369.   /* Replace escaped quotes with explicit ones */
  2370.   pszAfterLastEscapedQuote = bm_explicit_quotes( parseString );
  2371.   
  2372.   endQuote = XP_STRCHR(pszAfterLastEscapedQuote, '"');
  2373.  
  2374.   if (endQuote) {
  2375.   
  2376.     /* temporarily terminate */
  2377.     *endQuote = '\0';
  2378.  
  2379.     url = NET_MakeAbsoluteURL((char*)relative_url, parseString);
  2380.     if (url) {
  2381.       new_item = BM_NewUrl(NULL, url, NULL, 0);
  2382.       XP_FREE(url);
  2383.     }
  2384.     if (!new_item) return NULL;
  2385.  
  2386.     /* find '>' and the name will be right after it */
  2387.     gtr_than = XP_STRCHR(endQuote + 1, '>');
  2388.     if (gtr_than) {
  2389.       /* find the end of the name */
  2390.       end = strcasestr(gtr_than, "</A>");
  2391.       if (end) {
  2392.         *end = '\0';
  2393.         StrAllocCopy(new_item->name, XP_StripLine(gtr_than + 1));
  2394.         /* terminate at beginning of name since there
  2395.            is nothing interesting after that */
  2396.         *gtr_than = '\0';
  2397.       } else {
  2398.         StrAllocCopy(new_item->name,
  2399.                      XP_StripLine(gtr_than + 1));
  2400.  
  2401.         /* what happens if this breaks??  this is bogus stuff I don't
  2402.            know what to do with */
  2403.         XP_FileReadLine(buffer, READ_BUFFER_SIZE, fp);
  2404.         end = strcasestr(buffer, "</A>");
  2405.  
  2406.         if (end) *end = '\0';
  2407.  
  2408.         StrAllocCat(new_item->name, XP_StripLine(buffer));
  2409.       }
  2410.     }
  2411.  
  2412.     parseString = endQuote + 1;
  2413.  
  2414.     new_item->d.url.target = bm_target(parseString, FALSE);
  2415.  
  2416.     new_item->addition_date = bm_addition_date(parseString);
  2417.  
  2418.     new_item->d.url.last_visit = bm_last_date(parseString, FALSE);
  2419.     new_item->d.url.last_modified = bm_last_date(parseString, TRUE);
  2420.     if (new_item->d.url.last_modified == 0) {
  2421.       new_item->d.url.last_modified = new_item->d.url.last_visit;
  2422.     }
  2423.  
  2424.     new_item = bm_check_read_alias(context, new_item, parseString);
  2425.  
  2426.     /* replace the quotes */
  2427.     *endQuote = '"';
  2428.   }
  2429.  
  2430.   return new_item;
  2431. }
  2432.  
  2433.  
  2434. static BM_Entry*
  2435. bm_read_address(MWContext* context, XP_File fp, char* buffer, char* ptr)
  2436. {
  2437.   char* endQuote;
  2438.   char* gtr_than;
  2439.   char* parseString;
  2440.   char* end;
  2441.   char* url;
  2442.   BM_Entry* new_item = NULL;
  2443.  
  2444.   XP_ASSERT(context->type == MWContextAddressBook);
  2445.  
  2446.   /* find next quote */
  2447.   parseString = ptr + 6;
  2448.  
  2449.   endQuote = XP_STRCHR(parseString, '"');
  2450.   if (endQuote) {
  2451.     *endQuote = '\0';
  2452.  
  2453.     url = parseString;
  2454.  
  2455.     if (strncasecomp(url, "mailto:", 7) == 0) {
  2456.       url += 7;
  2457.     }
  2458.  
  2459.     new_item = bm_NewAddress(NULL, url);
  2460.     if (!new_item) return NULL;
  2461.  
  2462.     /* find '>' and the name will be right after it */
  2463.     gtr_than = XP_STRCHR(endQuote + 1, '>');
  2464.     if (gtr_than) {
  2465.       *gtr_than++ = '\0';
  2466.       /* find the end of the name */
  2467.       end = strcasestr(gtr_than, "</A>");
  2468.       if (end) {
  2469.         *end = '\0';
  2470.         StrAllocCopy(new_item->name, XP_StripLine(gtr_than));
  2471.       }
  2472.     }
  2473.  
  2474.     parseString = endQuote + 1;
  2475.  
  2476.     new_item = bm_check_read_alias(context, new_item, parseString);
  2477.  
  2478.     if (!BM_ISALIAS(new_item)) {
  2479.       bm_check_nickname(context, new_item, parseString);
  2480.     }
  2481.   }
  2482.  
  2483.   return new_item;
  2484. }
  2485.  
  2486.  
  2487. /* ptr should point to the chars "<Hx" */
  2488. static BM_Entry*
  2489. bm_read_header(MWContext* context, char* buffer, char* ptr)
  2490. {
  2491.   BM_Frame* f = GETFRAME(context);
  2492.   char* gtr_than;
  2493.   char* end;
  2494.   BM_Entry* new_item = NULL;
  2495.  
  2496.  
  2497.   /* find the beginning of the name */
  2498.   gtr_than = XP_STRCHR(ptr + 3, '>');
  2499.  
  2500.   /* find the end of the name */
  2501.   if (gtr_than) {
  2502.     end = strcasestr(gtr_than, "</H");
  2503.   }
  2504.  
  2505.   if (gtr_than && end) {
  2506.     /* temporarily NULL the name string */
  2507.     *end = '\0';
  2508.  
  2509.     new_item = BM_NewHeader(gtr_than + 1);
  2510.  
  2511.     if (!new_item)
  2512.       return NULL;
  2513.  
  2514.     *gtr_than = '\0';
  2515.  
  2516.     new_item->d.header.target = bm_target(buffer, FALSE);
  2517.  
  2518.     new_item->addition_date = bm_addition_date(buffer);
  2519.  
  2520.     if (bm_is_folded(buffer))
  2521.       BM_SETFLAG(new_item, BM_ATTR_FOLDED);
  2522.     else
  2523.       BM_CLEARFLAG(new_item, BM_ATTR_FOLDED);
  2524.  
  2525.     new_item = bm_check_read_alias(context, new_item, buffer);
  2526.  
  2527.     if (!BM_ISALIAS(new_item)) {
  2528.       if (context->type == MWContextAddressBook) {
  2529.         bm_check_nickname(context, new_item, buffer);
  2530.       } else {
  2531.         if (strcasestr(buffer, "MENUHEADER")) f->menuheader = new_item;
  2532.         if (strcasestr(buffer, "NEWITEMHEADER")) f->addheader = new_item;
  2533.       }
  2534.     }
  2535.  
  2536.   }
  2537.  
  2538.   return new_item;
  2539. }
  2540.  
  2541.  
  2542.  
  2543. static void
  2544. bm_read_description(BM_Entry* new_item, char* buffer )
  2545. {
  2546.   char* ptr;
  2547.   char* end;
  2548.   int length;
  2549.  
  2550.   /* assume the rest is descriptions; ignore if item is not a reasonable
  2551.      type */
  2552.  
  2553.   if (!new_item || !buffer) return;
  2554.  
  2555.   switch (new_item->type) {
  2556.   case BM_TYPE_HEADER:
  2557.   case BM_TYPE_URL:
  2558.   case BM_TYPE_ADDRESS:
  2559.  
  2560.     length = XP_STRLEN(buffer);
  2561.  
  2562.     /* skip <DL> if present */
  2563.     if (*buffer == '<') {
  2564.       buffer += 4;
  2565.       length -= 4;
  2566.     }
  2567.  
  2568.     if (length <= 0) return;
  2569.  
  2570.     end = buffer + length - 1;
  2571.  
  2572.     /* check for <BR> on the end and remove it also add a return */
  2573.     if (*end == '>') {
  2574.       end -= 3;
  2575.       XP_STRCPY(end, LINEBREAK);
  2576.       end += LINEBREAK_LEN;
  2577.       *end = '\0';
  2578.     } else {
  2579.       end++;
  2580.       XP_STRCPY(end, LINEBREAK);
  2581.       end += LINEBREAK_LEN;
  2582.       *end = '\0';
  2583.     }
  2584.  
  2585.     /* go through and turn < into '<' */
  2586.     for (ptr = buffer, end = buffer; *end != '\0'; end++) {
  2587.       if (!strncasecomp(end, "<", 4)) {
  2588.         end += 3;
  2589.         *ptr++ = '<';
  2590.       } else {
  2591.         *ptr++ = *end;
  2592.       }
  2593.     }
  2594.     *ptr = '\0'; /* terminate */
  2595.     StrAllocCat(new_item->description, buffer);
  2596.   }
  2597. }
  2598.  
  2599.  
  2600.     
  2601.  
  2602. /* Find the next entry after this one, where "next" means "the one that would
  2603.    show up on the next line if we didn't fold any headers". Also, this will
  2604.    wrap around from the end back to the beginning.  In other words, it will
  2605.    never return NULL. */
  2606. static BM_Entry*
  2607. bm_GetNextSpanningWrapping(MWContext* context, BM_Entry* at)
  2608. {
  2609.   if (BM_ISHEADER(at) && at->d.header.children) return at->d.header.children;
  2610.   if (at->next) return at->next;
  2611.   do {
  2612.     at = at->parent;
  2613.     if (at && at->next) {
  2614.       return at->next;
  2615.     }
  2616.   } while (at);
  2617.   return BM_GetRoot(context);
  2618. }
  2619.  
  2620.  
  2621.  
  2622. static XP_Bool
  2623. bm_StringMatches(MWContext* context, BM_FindInfo* findInfo, const char* str) {
  2624.   char* ptr;
  2625.   INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(context);
  2626.   if (!str) return FALSE;
  2627.   if (findInfo->matchCase) {
  2628.     ptr = INTL_Strstr(INTL_GetCSIWinCSID(c), str, findInfo->textToFind);
  2629.   } else {
  2630.     ptr = INTL_Strcasestr(INTL_GetCSIWinCSID(c), str, findInfo->textToFind);
  2631.   }
  2632.   if (!ptr) return FALSE;
  2633.   if (findInfo->matchWholeWord) {
  2634.     XP_ASSERT(ptr >= str);
  2635.     XP_ASSERT(ptr + XP_STRLEN(findInfo->textToFind) <= str + XP_STRLEN(str));
  2636.     if (ptr != str && !isspace(ptr[-1]) && !ispunct(ptr[-1])) return FALSE;
  2637.     ptr += XP_STRLEN(findInfo->textToFind);
  2638.     if (*ptr != '\0' && !isspace(*ptr) && !ispunct(*ptr)) return FALSE;
  2639.   }
  2640.   return TRUE;
  2641. }
  2642.  
  2643.  
  2644. static XP_Bool
  2645. bm_IsMatch(MWContext* context, BM_Entry* entry, BM_FindInfo* findInfo)
  2646. {
  2647.  
  2648.   if(!context)
  2649.       return FALSE;
  2650.  
  2651.   if (BM_ISALIAS(entry)) entry = entry->d.alias.original;
  2652.   if (findInfo->checkNickname) {
  2653.     if (bm_StringMatches(context, findInfo, entry->nickname)) return TRUE;
  2654.   }
  2655.   if (findInfo->checkName) {
  2656.     if (bm_StringMatches(context, findInfo, BM_GetName(entry))) return TRUE;
  2657.   }
  2658.   if (findInfo->checkLocation && BM_ISURL(entry)) {
  2659.     if (bm_StringMatches(context, findInfo, entry->d.url.address)) return TRUE;
  2660.   }
  2661.   if (findInfo->checkDescription) {
  2662.     if (bm_StringMatches(context, findInfo, entry->description)) return TRUE;
  2663.   }
  2664.   return FALSE;
  2665. }
  2666.  
  2667. static BM_Entry*
  2668. bm_DoFindBookmark_1(MWContext* context, BM_Entry* at, BM_FindInfo* findInfo) {
  2669.   BM_Entry* start = at;
  2670.   if (!at) return NULL;
  2671.   do {
  2672.     if (bm_IsMatch(context, at, findInfo)) return at;
  2673.     at = bm_GetNextSpanningWrapping(context, at);
  2674.   } while (at != start);
  2675.   return NULL;
  2676. }
  2677.  
  2678. /* reads an item from fp using the specified buffer and relative_url */
  2679. static void
  2680. bm_ReadFromHTML(MWContext* context,
  2681.                 XP_File fp,
  2682.                 BM_Entry* item,
  2683.                 char* buffer,
  2684.                 const char* relative_url)
  2685. {
  2686.   BM_Frame* f = GETFRAME(context);
  2687.   BM_Entry*    new_item = NULL;
  2688.   char* buffer_ptr;
  2689.   char* ptr;
  2690.   uint16 type;
  2691.  
  2692.   /* read loop */
  2693.   while (XP_FileReadLine(buffer, READ_BUFFER_SIZE, fp)) {
  2694.     buffer_ptr = XP_StripLine(buffer);
  2695.  
  2696.     type = bm_tokenize_line(context, buffer_ptr, &ptr);
  2697.  
  2698.     switch (type) {
  2699.     case BM_TYPE_URL:
  2700.       new_item = bm_read_url(context, fp, buffer_ptr, ptr, relative_url);
  2701.       break;
  2702.  
  2703.     case BM_TYPE_ADDRESS:
  2704.       new_item = bm_read_address(context, fp, buffer_ptr, ptr);
  2705.       break;
  2706.  
  2707.     case BM_TYPE_HEADER:
  2708.       new_item = bm_read_header(context, buffer_ptr, ptr);
  2709.       break;
  2710.  
  2711.     case BM_TYPE_SEPARATOR:
  2712.       if (context->type == MWContextBookmarks) {
  2713.         new_item = bm_NewSeparator();
  2714.       }
  2715.       break;
  2716.  
  2717.     case BM_HEADER_END:
  2718.       if (item != f->gBookmarks) return;
  2719.       break;
  2720.  
  2721.     case BM_UNKNOWN:
  2722.       if (new_item)
  2723.         bm_read_description(new_item, buffer_ptr);
  2724.       else if (item)
  2725.         bm_read_description(item, buffer_ptr);
  2726.       break;
  2727.     }
  2728.  
  2729.     /* test for insertable item -- nb you'll need
  2730.        to update this if you add new types/items */
  2731.     if (new_item && (type == BM_TYPE_URL ||
  2732.                      type == BM_TYPE_HEADER ||
  2733.                      type == BM_TYPE_SEPARATOR ||
  2734.                      type == BM_TYPE_ADDRESS)) {
  2735.       if (!item) {
  2736.         if (!f->gBookmarks) {
  2737.           if (new_item->type == BM_TYPE_HEADER) {
  2738.             f->gBookmarks = new_item;
  2739.             if (context->type == MWContextBookmarks) {
  2740.               f->menuheader = f->addheader = new_item;
  2741.             }
  2742.             goto SKIP;
  2743.           } else {
  2744.             (void) BM_GetRoot(context);    /* Has side effect of creating
  2745.                                            root header. */
  2746.             if (!f->gBookmarks) return;
  2747.           }
  2748.         }
  2749.         item = f->gBookmarks;
  2750.       }
  2751.  
  2752.       if (context->type == MWContextBookmarks) {
  2753.         bm_AppendChildToHeader(context, item, new_item);
  2754.       } else {
  2755.         bm_AddChildToHeaderSorted(context, item, new_item);
  2756.       }
  2757.  
  2758. SKIP:
  2759.       /* if it's a header, recurse */
  2760.       if (new_item->type == BM_TYPE_HEADER) {
  2761.         bm_ReadFromHTML(context, fp, new_item, buffer, relative_url);
  2762.       }
  2763.     }
  2764.   }
  2765. }
  2766.  
  2767. static int
  2768. bm_WriteAsHTML(MWContext* context, XP_File fp, BM_Entry* item, int32 level,
  2769.                XP_Bool isalias);
  2770.  
  2771.  
  2772. static int
  2773. bm_write_ok(const char* str, int length, XP_File fp)
  2774. {
  2775.   if (length < 0) length = XP_STRLEN(str);
  2776.   if ((int) XP_FileWrite(str, length, fp) < length) return -1;
  2777.   return 0;
  2778. }
  2779.  
  2780.  
  2781. #define WRITE(str, length, fp) \
  2782. if (bm_write_ok((str), (length), (fp)) < 0) return -1
  2783.  
  2784. static int
  2785. bm_write_alias_info(MWContext* context, XP_File fp, BM_Entry* entry,
  2786.                     XP_Bool isalias)
  2787. {
  2788.   bm_alias_info* info;
  2789.   XP_ASSERT(!isalias || (entry->flags & BM_ATTR_HASALIASES));
  2790.   if (entry->flags & BM_ATTR_HASALIASES) {
  2791.     if (!isalias) {
  2792.       /* Well, we think we have some aliases, but we can't be sure.  Let's
  2793.          make sure. */
  2794.       if (BM_CountAliases(context, entry) == 0) return 0;
  2795.     }
  2796.     info = bm_find_writealias_info(context, entry);
  2797.     WRITE(isalias ? " ALIASOF=\"" : " ALIASID=\"", -1, fp);
  2798.     WRITE(info->id, -1, fp);
  2799.     WRITE("\"", -1, fp);
  2800.   }
  2801.   return 0;
  2802. }
  2803.  
  2804.  
  2805. static int
  2806. bm_write_nickname(MWContext* context, XP_File fp, BM_Entry* entry,
  2807.                   XP_Bool isalias)
  2808. {
  2809.   if (context->type == MWContextAddressBook && !isalias &&
  2810.       entry->nickname && *entry->nickname) {
  2811.     WRITE(" NICKNAME=\"", -1, fp);
  2812.     WRITE(entry->nickname, -1, fp);
  2813.     WRITE("\"", -1, fp);
  2814.   }
  2815.   return 0;        /* XXX This was left out. Is is 0 right? */
  2816. }
  2817.     
  2818.  
  2819.  
  2820. static int
  2821. bm_write_html_header(MWContext* context, XP_File fp, BM_Entry* item,
  2822.                      int32 level, XP_Bool isalias)
  2823. {
  2824.   BM_Frame* f = GETFRAME(context);
  2825.   char buffer[16];
  2826.   int32 i;
  2827.   BM_Entry*    child;
  2828.   char* target;
  2829.   int status;
  2830.  
  2831.   XP_ASSERT(BM_ISHEADER(item));
  2832.  
  2833.   target = BM_GetTarget(item, FALSE);
  2834.  
  2835.   if (level != 0) {
  2836.     if (item->name) {
  2837.       WRITE("<DT><H3", -1, fp);
  2838.       /* write folded state */
  2839.       if (item->flags & BM_ATTR_FOLDED) {
  2840.         WRITE(" FOLDED", -1, fp);
  2841.       }
  2842.  
  2843.       if (item == f->menuheader) {
  2844.         WRITE(" MENUHEADER", -1, fp);
  2845.       }
  2846.       if (item == f->addheader) {
  2847.         WRITE(" NEWITEMHEADER", -1, fp);
  2848.       }
  2849.  
  2850.       status = bm_write_alias_info(context, fp, item, isalias);
  2851.       if (status < 0) return status;
  2852.  
  2853.       status = bm_write_nickname(context, fp, item, isalias);
  2854.       if (status < 0) return status;
  2855.  
  2856.       /* write target */
  2857.       if ((target)&&(target[0] != '\0'))
  2858.       {
  2859.         WRITE(" TARGET=\"", -1, fp);
  2860.         WRITE(target, -1, fp);
  2861.         WRITE("\"", -1, fp);
  2862.       }
  2863.  
  2864.       if (context->type == MWContextBookmarks) {
  2865.         /* write addition date */
  2866.         WRITE(" ADD_DATE=\"", -1, fp);
  2867.         XP_SPRINTF(buffer, "%ld\"", item->addition_date);
  2868.         WRITE(buffer, XP_STRLEN(buffer), fp);
  2869.       }
  2870.       WRITE(">", -1, fp);
  2871.  
  2872.       /* write name */
  2873.       WRITE(item->name, XP_STRLEN(item->name), fp);
  2874.       WRITE("</H3>", -1, fp);
  2875.       WRITE(LINEBREAK, LINEBREAK_LEN, fp);
  2876.     }
  2877.   }
  2878.  
  2879.   /* write description if there is one */
  2880.   if (item->description) {
  2881.     char *ptr = XP_StripLine(item->description);
  2882.  
  2883.     WRITE("<DD>", -1, fp);
  2884.  
  2885.     for (; *ptr != '\0'; ptr++) {
  2886.       if (*ptr == '<') {
  2887.         WRITE("<", -1, fp);
  2888.       } else if (*ptr == '\n') {
  2889.         WRITE("<BR>", -1, fp);
  2890.         WRITE(LINEBREAK, LINEBREAK_LEN, fp);
  2891.       } else {
  2892.         WRITE(ptr, 1, fp);
  2893.       }
  2894.     }
  2895.     WRITE(LINEBREAK, LINEBREAK_LEN, fp);
  2896.   }
  2897.  
  2898.   if (!isalias) {
  2899.     /* write children out */
  2900.     for (i = 0; i < level; i++) {
  2901.       WRITE("    ", -1, fp); /* indent */
  2902.     }
  2903.     WRITE("<DL><p>" LINEBREAK, -1, fp);
  2904.     
  2905.     for (child = item->d.header.children; child ; child = child->next) {
  2906.       bm_WriteAsHTML(context, fp, child, level + 1, FALSE);
  2907.     }
  2908.     
  2909.     for (i = 0; i < level; i++) {
  2910.       WRITE("    ", -1, fp);
  2911.     }
  2912.     
  2913.     WRITE("</DL><p>" LINEBREAK, -1, fp);
  2914.   }
  2915.   return 0;
  2916. }
  2917.  
  2918. static int
  2919. bm_write_separator(XP_File fp)
  2920. {
  2921.   WRITE("<HR>", -1, fp);
  2922.   WRITE(LINEBREAK, LINEBREAK_LEN, fp);
  2923.   return 0;
  2924. }
  2925.  
  2926. static int
  2927. bm_write_address( char *pszAddress, XP_File fp )
  2928. {
  2929.     /*
  2930.     // Replace explicit quotes with escaped quotes before writing the address.
  2931.     // For example:
  2932.     //    javascript:netscape.plugin.composer.Document.editDocument("http://myserver.com/docs/schedule.html")
  2933.     // is converted to:
  2934.     //    javascript:netscape.plugin.composer.Document.editDocument(%20http://myserver.com/docs/schedule.html%20)
  2935.     */
  2936.     
  2937.     int     iBufPos, iLen = 0;
  2938.     char *  pszCsr = pszAddress;
  2939.     char *  pszBuf = NULL;
  2940.     
  2941.     if( !pszAddress || !fp )
  2942.     {
  2943.         return 0;
  2944.     }
  2945.  
  2946.     if( !XP_STRCHR( pszAddress, '"' ) )
  2947.     {
  2948.         /* No quotes to convert, so just write it and return. */
  2949.         
  2950.         WRITE( pszAddress, -1, fp );
  2951.         return 0;
  2952.     }
  2953.         
  2954.     /*
  2955.     // Calculate the size of the new string.
  2956.     */
  2957.     iLen = XP_STRLEN( pszAddress );
  2958.     while( *pszCsr )
  2959.     {
  2960.         if( *pszCsr == '"' )
  2961.         {
  2962.             iLen += 2;
  2963.         }
  2964.         pszCsr++;
  2965.     }
  2966.  
  2967.     pszBuf = (char *)XP_ALLOC( iLen+1 );
  2968.     if( !pszBuf )
  2969.     {
  2970.         return 0;
  2971.     }
  2972.  
  2973.     /*
  2974.     // Copy the url while converting explicit quotes to escaped quotes.
  2975.     */
  2976.     iBufPos = 0;    
  2977.     pszCsr  = pszAddress;
  2978.     while( *pszCsr )
  2979.     {
  2980.         if( *pszCsr == '"' )
  2981.         {
  2982.             pszBuf[iBufPos]   = '%';
  2983.             pszBuf[++iBufPos] = '2';
  2984.             pszBuf[++iBufPos] = '2';
  2985.         }
  2986.         else
  2987.         {
  2988.             pszBuf[iBufPos] = *pszCsr;
  2989.         }
  2990.         
  2991.         iBufPos++;
  2992.         pszCsr++;
  2993.     }
  2994.     pszBuf[iBufPos] = 0;
  2995.     
  2996.     /* Finally write out the converted address */
  2997.     
  2998.     WRITE( pszBuf, -1, fp );
  2999.     
  3000.     XP_FREE( pszBuf );
  3001. }
  3002.  
  3003. /* writes out a URL entry to look like:
  3004.  *
  3005.  * <DT><A HREF="http://www.ncsa.uiuc.edu/radio/radio.html" \
  3006.  * ADD_DATE="777240414" LAST_VISIT="802992591">Internet Talk Radio</A>
  3007.  *
  3008.  */
  3009. static int
  3010. bm_write_url_or_address(MWContext* context, XP_File fp, BM_Entry* item,
  3011.                         XP_Bool isalias)
  3012. {
  3013.   char buffer[16];
  3014.   char* address;
  3015.   char* target;
  3016.   int status;
  3017.  
  3018.   address = BM_GetAddress(item);
  3019.   target = BM_GetTarget(item, FALSE);
  3020.  
  3021.   if (address) {
  3022.     WRITE("<DT>", -1, fp);
  3023.  
  3024.     /* write address */
  3025.     WRITE("<A HREF=\"", -1, fp);
  3026.     if (context->type == MWContextAddressBook) {
  3027.       WRITE("mailto:", -1, fp);
  3028.     }
  3029.     bm_write_address(address, fp);
  3030.     WRITE("\"", -1, fp);
  3031.  
  3032.     /* write target */
  3033.     if ((target)&&(target[0] != '\0'))
  3034.     {
  3035.       WRITE(" TARGET=\"", -1, fp);
  3036.       WRITE(target, -1, fp);
  3037.       WRITE("\"", -1, fp);
  3038.     }
  3039.  
  3040.     status = bm_write_alias_info(context, fp, item, isalias);
  3041.     if (status < 0) return status;
  3042.  
  3043.     status = bm_write_nickname(context, fp, item, isalias);
  3044.     if (status < 0) return status;
  3045.  
  3046.     if (BM_ISURL(item)) {
  3047.       /* write the addition date  */
  3048.       WRITE(" ADD_DATE=\"", -1, fp);
  3049.       XP_SPRINTF(buffer, "%ld", item->addition_date);
  3050.       WRITE(buffer, -1, fp);
  3051.       WRITE("\"", -1, fp);
  3052.  
  3053.       /* write the last visited date */
  3054.       WRITE(" LAST_VISIT=\"", -1, fp);
  3055.       XP_SPRINTF(buffer, "%ld\"", item->d.url.last_visit);
  3056.       WRITE(buffer, -1, fp);
  3057.  
  3058.       /* write the last modified date */
  3059.       WRITE(" LAST_MODIFIED=\"", -1, fp);
  3060.       XP_SPRINTF(buffer, "%ld\"", item->d.url.last_modified);
  3061.       WRITE(buffer, -1, fp);
  3062.     }
  3063.     WRITE(">", -1, fp);
  3064.  
  3065.     /* write the name */
  3066.  
  3067.     if (item->name) {
  3068.       WRITE(item->name, -1, fp);
  3069.     } else {
  3070.       if (BM_ISURL(item)) {
  3071.         WRITE(item->d.url.address, -1, fp);
  3072.       } else {
  3073.         XP_ASSERT(BM_ISADDRESS(item));
  3074.         WRITE(item->d.address.address, -1, fp);
  3075.       }
  3076.     }
  3077.  
  3078.     WRITE("</A>", -1, fp);
  3079.     WRITE(LINEBREAK, LINEBREAK_LEN, fp);
  3080.  
  3081.     /* write description if there is one */
  3082.     if (item->description) {
  3083.       char *ptr = XP_StripLine(item->description);
  3084.  
  3085.       WRITE("<DD>", -1, fp);
  3086.  
  3087.       for (; *ptr != '\0'; ptr++) {
  3088.         if (*ptr == '<') {
  3089.           WRITE("<", -1, fp);
  3090.         } else if (*ptr == '\n') {
  3091.           WRITE("<BR>", -1, fp);
  3092.           WRITE(LINEBREAK, LINEBREAK_LEN, fp);
  3093.         } else {
  3094.           WRITE(ptr, 1, fp);
  3095.         }
  3096.       }
  3097.       WRITE(LINEBREAK, LINEBREAK_LEN, fp);
  3098.     }
  3099.   }
  3100.   return 0;
  3101. }
  3102.  
  3103.  
  3104. /* writes an item into fp at the specified indentation level */
  3105. static int
  3106. bm_WriteAsHTML(MWContext* context, XP_File fp, BM_Entry* item, int32 level,
  3107.                XP_Bool isalias)
  3108. {
  3109.   int32 i;
  3110.   int status = 0;
  3111.  
  3112.   /* indent */
  3113.   if (!isalias) {
  3114.     for (i = 0; i < level; i++) {
  3115.       WRITE("    ", -1, fp);
  3116.     }
  3117.   }
  3118.  
  3119.   switch (item->type) {
  3120.   case BM_TYPE_HEADER:
  3121.     status = bm_write_html_header(context, fp, item, level, isalias);
  3122.     break;
  3123.  
  3124.   case BM_TYPE_SEPARATOR:
  3125.     status = bm_write_separator(fp);
  3126.     break;
  3127.  
  3128.   case BM_TYPE_URL:
  3129.   case BM_TYPE_ADDRESS:
  3130.     status = bm_write_url_or_address(context, fp, item, isalias);
  3131.     break;
  3132.  
  3133.   case BM_TYPE_ALIAS:
  3134.     XP_ASSERT(!isalias);
  3135.     status = bm_WriteAsHTML(context, fp, item->d.alias.original, level, TRUE);
  3136.     break;
  3137.   }
  3138.   return status;
  3139. }
  3140.  
  3141. /* clears the selected state of parent and all of it's children
  3142.     if refresh is TRUE, the FE is called to redraw necessary items
  3143.     count should match the index of parent in the visible tree.  count will
  3144.     be NULL if a parent is folded. */
  3145. PRIVATE void
  3146. bm_ClearSelection(MWContext* context, BM_Entry* parent, XP_Bool refresh, int32* count)
  3147. {
  3148.   BM_Frame* f = GETFRAME(context);
  3149.   BM_Entry* child;
  3150.   XP_ASSERT(parent);
  3151.   XP_ASSERT(BM_ISHEADER(parent));
  3152.  
  3153.   if (parent->flags & BM_ATTR_SELECTED) {
  3154.     BM_CLEARFLAG(parent, BM_ATTR_SELECTED);
  3155.     if (refresh && count) bm_refresh(context, *count, *count);
  3156.     f->gSelectionCount = -9999;
  3157.   }
  3158.  
  3159.   if (count) (*count)++;
  3160.  
  3161.   if (BM_ISFOLDED(parent)) count = NULL;
  3162.  
  3163.   for (child = parent->d.header.children ; child ; child = child->next) {
  3164.     if (child->type != BM_TYPE_HEADER) {
  3165.       if (child->flags & BM_ATTR_SELECTED) {
  3166.         BM_CLEARFLAG(child, BM_ATTR_SELECTED);
  3167.         if (refresh && count) bm_refresh(context, *count, *count);
  3168.         f->gSelectionCount = -9999;
  3169.       }
  3170.       if (count) (*count)++;
  3171.     } else {
  3172.       bm_ClearSelection(context, child, refresh, count);
  3173.     }
  3174.   }
  3175. }
  3176.  
  3177. PRIVATE void
  3178. bm_SelectAll(MWContext* context, BM_Entry* parent)
  3179. {
  3180.   BM_Frame* f = GETFRAME(context);
  3181.   BM_Entry* child;
  3182.  
  3183.     XP_ASSERT(parent);
  3184.     XP_ASSERT(parent->type == BM_TYPE_HEADER);
  3185.  
  3186.     child = parent->d.header.children;
  3187.     if (!BM_ISSELECTED(parent))
  3188.     {
  3189.         BM_SETFLAG(parent, BM_ATTR_SELECTED);
  3190.         f->gSelectionCount++;
  3191.         f->gSelectionMask |= BM_TYPE_HEADER;
  3192.     }
  3193.  
  3194.     while (child)
  3195.     {
  3196.         if (BM_ISHEADER(child))
  3197.             bm_SelectAll(context, child);
  3198.         else
  3199.         {
  3200.             if (!(child->flags & BM_ATTR_SELECTED))
  3201.             {
  3202.                 BM_SETFLAG(child, BM_ATTR_SELECTED);
  3203.                 f->gSelectionCount++;
  3204.                 f->gSelectionMask |= child->type;
  3205.             }
  3206.         }
  3207.         child = child->next;
  3208.     }
  3209. }
  3210.  
  3211. PUBLIC void
  3212. BM_SelectAll(MWContext* context, XP_Bool refresh)
  3213. {
  3214.     BM_Entry*        root;
  3215.     CHKCONTEXTVOID(context);
  3216.  
  3217.     root = BM_GetRoot(context);
  3218.  
  3219.     if (root)
  3220.     {
  3221.         bm_SelectAll(context, root);
  3222.         if (refresh)
  3223.             bm_refresh(context, 1, BM_LAST_CELL);
  3224.     }
  3225. }
  3226.  
  3227. static void
  3228. bm_TellGoingAway(MWContext* context, BM_Entry* entry, void* closure)
  3229. {
  3230.   BM_Frame* f = GETFRAME(context);
  3231.   if (entry == f->menuheader) {
  3232.     BM_SetMenuHeader(context, BM_GetRoot(context));
  3233.   }
  3234.   if (entry == f->addheader) {
  3235.     BM_SetAddHeader(context, BM_GetRoot(context));
  3236.   }
  3237.   BMFE_EntryGoingAway(context, entry);
  3238.   if (entry->nickname) {
  3239.     BM_SetNickName(context, entry, NULL);    /* Causes the nickname hash to be
  3240.                                                cleared, adding undo stuff to
  3241.                                                bring it back if this entry
  3242.                                                somehow gets brought back. */
  3243.     XP_ASSERT(entry->nickname == NULL);
  3244.   }
  3245. #ifdef DEBUG
  3246.   /* Confirm that we are not deleting any dangling aliases. */
  3247.   if (entry->flags & BM_ATTR_HASALIASES) {
  3248.     int32 count = BM_CountAliases(context, entry);
  3249.     if (count) {
  3250.       BM_Entry* deleteroot = (BM_Entry*) closure;
  3251.       if (BM_ISHEADER(deleteroot)) {
  3252.         /* Reduce the count by the number of aliases that we're going to
  3253.            delete. */
  3254.         count -= bm_CountAliases_1(deleteroot, entry);
  3255.       }
  3256.       XP_ASSERT(count == 0);
  3257.     }
  3258.   }
  3259. #endif /* DEBUG */
  3260. }
  3261.  
  3262. /* free's a bmlist entry */
  3263. PRIVATE void
  3264. bm_ShallowFreeEntry(BM_Entry* entry)
  3265. {
  3266.   if (entry) {
  3267.     XP_ASSERT(entry->next == NULL);
  3268.     XP_ASSERT(entry->nickname == NULL); 
  3269.  
  3270.     FREEIF(entry->name);
  3271.     FREEIF(entry->description);
  3272.     switch (entry->type) {
  3273.     case BM_TYPE_HEADER:
  3274.       XP_ASSERT(entry->d.header.children == NULL);
  3275.       break;
  3276.  
  3277.     case BM_TYPE_URL:
  3278.       FREEIF(entry->d.url.address);
  3279.       FREEIF(entry->d.url.content_type);
  3280.       break;
  3281.  
  3282.     case BM_TYPE_ADDRESS:
  3283.       FREEIF(entry->d.address.address);
  3284.       break;
  3285.     }
  3286.     XP_FREE(entry);
  3287.   }
  3288. }
  3289.  
  3290. PRIVATE void
  3291. bm_ReallyFreeEntry(void* data)
  3292. {
  3293.   BM_Entry* entry = (BM_Entry*) data;
  3294.   while (entry) {
  3295.     BM_Entry* next = entry->next;
  3296.     entry->next = NULL;
  3297.     if (BM_ISHEADER(entry)) {
  3298.       /* free all the children */
  3299.       bm_ReallyFreeEntry(entry->d.header.children);
  3300.       entry->d.header.children = NULL;
  3301.     }
  3302.     bm_ShallowFreeEntry(entry);
  3303.     entry = next;
  3304.   }
  3305. }
  3306.  
  3307.  
  3308. typedef struct bm_free_info {
  3309.   BM_Entry* entry;
  3310.   XP_Bool usedFromUndo;
  3311. } bm_free_info;
  3312.  
  3313. static int
  3314. bm_cancel_free(void* closure)
  3315. {
  3316.   bm_free_info* info = (bm_free_info*) closure;
  3317.   info->usedFromUndo = TRUE;
  3318.   return 0;
  3319. }
  3320.  
  3321.  
  3322. static void
  3323. bm_free_freeit(void* closure)
  3324. {
  3325.   bm_free_info* info = (bm_free_info*) closure;
  3326.   if (!info->usedFromUndo) bm_ReallyFreeEntry(info->entry);
  3327.   XP_FREE(info);
  3328. }
  3329.  
  3330.  
  3331. /* free's a BM_Entry and all of its succeeding siblings... if it's a
  3332.     header, it frees all of its children.  However, actually this
  3333.     does nothing, but logs an event in the undo queue.  When the event gets
  3334.     freed, then we know that nothing in the undo chain needs this thing,
  3335.     so *then* we can free it.*/
  3336. void
  3337. BM_FreeEntry(MWContext* context, BM_Entry* entry)
  3338. {
  3339.   BM_Frame* f = GETFRAME(context);
  3340.   if (!entry) return;
  3341.  
  3342.   bm_start_batch(context);
  3343.   bm_EachEntryDo_1(context, entry, bm_TellGoingAway, entry);
  3344.  
  3345.   if (f->undo) {
  3346.     bm_free_info* info = XP_NEW_ZAP(bm_free_info);
  3347.     if (!info) {
  3348.       UNDO_DiscardAll(f->undo);
  3349.       bm_ReallyFreeEntry(entry);
  3350.     } else {
  3351.       info->entry = entry;
  3352.       UNDO_LogEvent(f->undo, bm_cancel_free, bm_free_freeit, info, NULL, NULL);
  3353.     }
  3354.   } else {
  3355.     bm_ReallyFreeEntry(entry);
  3356.   }
  3357.   bm_end_batch(context);
  3358. }
  3359.  
  3360.  
  3361.  
  3362. int 
  3363. BM_InitializeBookmarksContext(MWContext* context)
  3364. {
  3365.   BM_Frame* f;
  3366.   XP_ASSERT(context);
  3367.   if (!context) return -1;        /* ### Need better error code? */
  3368.   f = XP_NEW_ZAP(BM_Frame);
  3369.   if (!f) return MK_OUT_OF_MEMORY;
  3370.   XP_ASSERT(context->bmframe == NULL);
  3371.   f->undo = UNDO_Create(10);
  3372.   if (!f->undo) goto FAIL;
  3373.   f->nicknameTable = XP_HashTableNew(100, XP_StringHash, bm_string_cmp);
  3374.   if (!f->nicknameTable) goto FAIL;
  3375.   f->errorSavingBookmarks = FALSE;
  3376.   f->enSortType = BM_Sort_Natural;
  3377.   f->bSorting = FALSE;
  3378.   context->bmframe = f;
  3379.   f->next = ContextList;
  3380.   ContextList = context;
  3381.   (void) BM_GetRoot(context);    /* Has side effect of creating root header. */
  3382.   bm_refresh(context, 1, BM_LAST_CELL);
  3383.   return 0;
  3384. FAIL:
  3385.   if (f->undo) UNDO_Destroy(f->undo);
  3386.   if (f->nicknameTable) XP_HashTableDestroy(f->nicknameTable);
  3387.   XP_FREE(f);
  3388.   return MK_OUT_OF_MEMORY;
  3389. }
  3390.  
  3391. void
  3392. BM_CleanupBookmarksContext(MWContext* context)
  3393. {
  3394.   BM_Frame* f;
  3395.   MWContext** tmp;
  3396.   CHKCONTEXTVOID(context);
  3397.   BM_SaveBookmarks(context, NULL);
  3398.  
  3399.                                     /* This cleanup code can be slow and
  3400.                                    inefficient.  Since we're gonna exit soon
  3401.                                    anyway, let's not bother doing this stuff.
  3402.                                    ### - DMB - Let's, at least for debug
  3403.                                     detection of meory leaks. How slow could it
  3404.                                     be?    */
  3405.   f = GETFRAME(context);
  3406.   UNDO_Destroy(f->undo);
  3407.   f->undo = NULL;
  3408.   BM_FreeEntry(context, f->gBookmarks);
  3409.   if (f->savetimer) {
  3410.     FE_ClearTimeout(f->savetimer);
  3411.     f->savetimer = NULL;
  3412.   }
  3413.  
  3414.   f->gBookmarks = NULL;
  3415.   if (f->aliasTable) {
  3416.     bm_clear_alias_info(context);
  3417.     XP_HashTableDestroy(f->aliasTable);
  3418.   }
  3419.   if (f->nicknameTable) {
  3420.     XP_HashTableDestroy(f->nicknameTable);
  3421.   }
  3422.        
  3423.   for (tmp = &ContextList ; *tmp ; tmp = &(f->next)) {
  3424.     f = GETFRAME(*tmp);
  3425.     if (*tmp == context) {
  3426.       (*tmp)->bmframe = NULL;
  3427.       *tmp = f->next;
  3428.       XP_FREE(f);
  3429.       return;
  3430.     }
  3431.   }
  3432.   XP_ASSERT(0);
  3433. }
  3434.  
  3435.  
  3436. /* returns TRUE if the bookmarks have been modified since
  3437. the file was read, FALSE otherwise */
  3438. PUBLIC XP_Bool
  3439. BM_Modified(MWContext* context)
  3440. {
  3441.   BM_Frame* f = GETFRAME(context);
  3442.   CHKCONTEXT(context);
  3443.   return f ? f->gBookmarksModified : FALSE;
  3444. }
  3445.  
  3446. static void
  3447. bm_UpdateTimeStamp(MWContext* context, BM_Entry* entry, void* closure)
  3448. {
  3449.   BM_Date cur_time = *(BM_Date *)closure;
  3450.   BM_Frame* f = GETFRAME(context);
  3451.   int32 oldstate;
  3452.   
  3453.   if (!entry) return;
  3454.   
  3455.   oldstate = BM_GetChangedState(entry);
  3456.   entry->d.url.last_visit = cur_time;
  3457.   if (entry->d.url.last_modified == 0) {
  3458.     /* Well, this current visitation is a good enough estimate for the modification time. */
  3459.     entry->d.url.last_modified = cur_time;
  3460.   }
  3461.  
  3462.   /* Deliberately *don't* call bm_SetModified here; we don't want to cause
  3463.      the file to be saved soon for this trivial change.  Just turn on the
  3464.      modified bit so that we know things will be saved eventually. */
  3465.   f->gBookmarksModified = TRUE;
  3466.  
  3467.   if (BM_GetChangedState(entry) != oldstate) {
  3468.     bm_entry_changed(context, entry);
  3469.   }
  3470. }
  3471.  
  3472. /* checks the bmlist for a url and updates the last accessed time */
  3473. PUBLIC void
  3474. BM_UpdateBookmarksTime(URL_Struct* URL_s, BM_Date cur_time)
  3475. {
  3476.   MWContext* context;
  3477.   BM_Frame* f;
  3478.  
  3479.   if (!URL_s) return;
  3480.  
  3481.   for (context = ContextList ; context ; context = f->next) {
  3482.     f = GETFRAME(context);
  3483.     if (context->type != MWContextBookmarks) continue;
  3484.     bm_FindItemStub(context, BM_GetRoot(context), URL_s->address, bm_UpdateTimeStamp, (void *)&cur_time);
  3485.   }
  3486. }
  3487.  
  3488.  
  3489. /* returns the total number of items in the tree */
  3490. PUBLIC int32
  3491. BM_GetCount(MWContext* context)
  3492. {
  3493.   BM_Frame* f = GETFRAME(context);
  3494.   CHKCONTEXT(context);
  3495.   if (!f) return 0;
  3496.   if (f->gCount <= 0) {
  3497.     f->gCount = bm_CountChildren(BM_GetRoot(context), FALSE);
  3498.   }
  3499.   return f->gCount;
  3500. }
  3501.  
  3502. /* returns the number of items in the tree that are presently
  3503.     visible */
  3504. PUBLIC int32
  3505. BM_GetVisibleCount(MWContext* context)
  3506. {
  3507.   BM_Frame* f = GETFRAME(context);
  3508.   CHKCONTEXT(context);
  3509.   if (!f) return 0;
  3510.   if (f->gVisCount <= 0) {
  3511.     f->gVisCount = bm_CountChildren(BM_GetRoot(context), TRUE);
  3512.   }
  3513.   return f->gVisCount;
  3514. }
  3515.  
  3516.  
  3517.  
  3518.  
  3519.  
  3520. static void
  3521. bm_InsertItemAfter(MWContext* context, BM_Entry* insert_after,
  3522.                    BM_Entry* insertee, XP_Bool sync)
  3523. {
  3524.     BM_Frame* f = GETFRAME(context);
  3525.     CHKCONTEXTVOID(context);
  3526.  
  3527.     XP_ASSERT(insertee);
  3528.     if (!insertee) return;
  3529.  
  3530.     /* insert after the item if specified */
  3531.     if (insert_after)
  3532.     {
  3533.         BM_Entry* tmp;
  3534.         BM_Entry* parent;
  3535.  
  3536.         if (insert_after->parent == NULL)
  3537.         {
  3538.             /* insert as first child, displayed below header */
  3539.             tmp = insert_after->d.header.children;
  3540.             parent = insert_after;
  3541.             parent->d.header.childCount++;
  3542.             parent->d.header.children = insertee;
  3543.         }
  3544.         else
  3545.         {
  3546.             tmp = insert_after->next;
  3547.             parent = insert_after->parent;
  3548.  
  3549.             if (parent)
  3550.             {
  3551.                 parent->d.header.childCount++;
  3552.                 if (!tmp)
  3553.                     parent->d.header.lastChild = insertee;
  3554.             }
  3555.             insert_after->next = insertee;
  3556.         }
  3557.         insertee->next = tmp;
  3558.         insertee->parent = parent;
  3559.         
  3560.         if( !f->bSorting )        
  3561.           insertee->iNaturalIndex = g_iNaturalIndexPool++;
  3562.         
  3563.         BMFE_BookmarkMenuInvalid(context);
  3564.         bm_LogDeleteChild(context, parent, insertee);
  3565.     }
  3566.     else
  3567.         bm_AppendChildToHeader(context, BM_GetRoot(context), insertee);
  3568.  
  3569.     bm_SetModified(context, TRUE);
  3570.     if (sync)
  3571.         bm_SyncCount(context);
  3572. }
  3573.  
  3574. /* insert an item after another item in the bmlist
  3575.     if the insert_after item is NULL the item
  3576.     will be inserted at the end of the bookmarks */
  3577. PUBLIC void
  3578. BM_InsertItemAfter(MWContext* context, BM_Entry* insert_after, BM_Entry* insertee)
  3579. {
  3580.   CHKCONTEXTVOID(context);
  3581.   bm_start_batch(context);
  3582.   bm_InsertItemAfter(context, insert_after, insertee, TRUE);
  3583.   bm_end_batch(context);
  3584. }
  3585.  
  3586. /* insert an item in a header if "insert_after" is a
  3587.     Header type, or after the item if "insert after" is
  3588.     not a header type.
  3589.     if the insert_after item is NULL or not found the item
  3590.     will be inserted at the end of the bookmarks */
  3591. PUBLIC void
  3592. BM_InsertItemInHeaderOrAfterItem(    MWContext* context,
  3593.                                     BM_Entry* insert_after,
  3594.                                     BM_Entry* insertee)
  3595. {
  3596.   CHKCONTEXTVOID(context);
  3597.   XP_ASSERT(insertee);
  3598.  
  3599.   bm_start_batch(context);
  3600.   if (insert_after && insert_after->type == BM_TYPE_HEADER)
  3601.     bm_AppendChildToHeader(context, insert_after, insertee);
  3602.   else
  3603.     BM_InsertItemAfter(context, insert_after, insertee);
  3604.   bm_SyncCount(context);
  3605.   bm_end_batch(context);
  3606. }
  3607.  
  3608. void remove_to(MWContext* context, BM_Entry* entry, void* to)
  3609. {
  3610.     BM_Entry*        moveTo = (BM_Entry*)to;
  3611.     BM_Entry*        parent;
  3612.  
  3613.     XP_ASSERT(entry);
  3614.  
  3615.     parent = entry->parent;
  3616.     if (parent)
  3617.         BM_RemoveChildFromHeader(context, parent, entry);
  3618.  
  3619.     bm_AppendChildToHeader(context, moveTo, entry);
  3620. }
  3621.  
  3622.  
  3623.  
  3624. static int
  3625. bm_get_max_depth_1(BM_Entry* entry)
  3626. {
  3627.   int result = 0;
  3628.   for (; entry ; entry = entry->next) {
  3629.     if (BM_ISHEADER(entry) && !BM_ISFOLDED(entry)) {
  3630.       int value = bm_get_max_depth_1(entry->d.header.children);
  3631.       if (result < value) result = value;
  3632.     }
  3633.   }
  3634.   return result + 1;
  3635. }
  3636.  
  3637. int
  3638. BM_GetMaxDepth(MWContext* context)
  3639. {
  3640.   BM_Frame* f = GETFRAME(context);
  3641.   CHKCONTEXT(context);
  3642.   if (!f) return 0;
  3643.   if (f->max_depth == 0) {
  3644.     f->max_depth = bm_get_max_depth_1(f->gBookmarks);
  3645.   }
  3646.   return f->max_depth;
  3647. }
  3648.  
  3649.  
  3650. PUBLIC XP_Bool
  3651. BM_IsDragEffectBox(MWContext* context, int line, XP_Bool under)
  3652. {
  3653.   BM_Entry* entry;
  3654.   CHKCONTEXT(context);
  3655.   if (line <= 0) return FALSE;
  3656.   entry = BM_AtIndex(context, line);
  3657.   if (!entry) return FALSE;        /* ### */
  3658.   if (BM_ISHEADER(entry)) {
  3659.     if (under && (BM_ISFOLDED(entry) || entry->d.header.childCount == 0)) {
  3660.       return FALSE;
  3661.     }
  3662.     return TRUE;
  3663.   }
  3664.   return FALSE;
  3665. }
  3666.  
  3667.  
  3668. PUBLIC void
  3669. BM_DoDrop(MWContext* context, int line, XP_Bool under)
  3670. {
  3671.   BM_Entry* dest = BM_AtIndex(context, line);
  3672.   BM_Entry* tmp;
  3673.   BM_Entry* parent;
  3674.   BM_Entry* entry;
  3675.   CHKCONTEXTVOID(context);
  3676.   if (!dest) return;
  3677.   for (parent = dest ; parent ; parent = parent->parent) {
  3678.     if (BM_ISSELECTED(parent)) return;
  3679.   }
  3680.   tmp = BM_NewHeader("");
  3681.   if (!tmp) return;
  3682.   bm_start_batch(context);
  3683.   if (BM_ISHEADER(dest) && BM_IsDragEffectBox(context, line, under)) {
  3684.     parent = dest;
  3685.     dest = NULL;
  3686.   } else {
  3687.     parent = dest->parent;
  3688.   }
  3689.   BM_EachProperSelectedEntryDo(context, remove_to, tmp, NULL);
  3690.   entry = tmp->d.header.children;
  3691.   if (entry) {
  3692.     BM_ClearAllSelection(context, FALSE);
  3693.     while ((entry = tmp->d.header.children)) {
  3694.       BM_RemoveChildFromHeader(context, tmp, entry);
  3695.       BM_CLEARFLAG(entry, BM_ATTR_SELECTED);
  3696.       if (context->type == MWContextBookmarks) {
  3697.         if (dest) {
  3698.           bm_InsertItemAfter(context, dest, entry, FALSE);
  3699.         } else {
  3700.           BM_PrependChildToHeader(context, parent, entry);
  3701.         }
  3702.       } else {
  3703.         if (BM_ISALIAS(entry)) {
  3704.           bm_AddChildToHeaderSorted(context, parent, entry);
  3705.         } else {
  3706.           bm_AddChildToHeaderSorted(context, BM_GetRoot(context), entry);
  3707.           entry = bm_NewAlias(entry);
  3708.           bm_AddChildToHeaderSorted(context, parent, entry);
  3709.         }
  3710.       }
  3711.       if (!BM_ISHEADER(parent) || !BM_ISFOLDED(parent))
  3712.       BM_SelectItem(context, entry, FALSE, TRUE, TRUE);
  3713.       dest = entry;
  3714.       entry = tmp->d.header.children;
  3715.     }
  3716.     if (BM_ISHEADER(parent) && BM_ISFOLDED(parent))
  3717.       BM_SelectItem(context, parent, FALSE, TRUE, TRUE);
  3718.     BMFE_BookmarkMenuInvalid(context);
  3719.     bm_SyncCount(context);
  3720.     bm_refresh(context, 1, BM_LAST_CELL);
  3721.   }
  3722.   BM_FreeEntry(context, tmp);
  3723.   bm_end_batch(context);
  3724. }
  3725.  
  3726.  
  3727.  
  3728. typedef struct bm_goingaway_info {
  3729.   XP_Bool userasked;
  3730.   XP_Bool userconfirmed;
  3731.   BM_Entry* entry;
  3732.   int count;
  3733. } bm_goingaway_info;
  3734.  
  3735.  
  3736. static void
  3737. bm_subtract_alias_for(MWContext* context, BM_Entry* entry, void* closure)
  3738. {
  3739.   bm_goingaway_info* info = (bm_goingaway_info*) closure;
  3740.   if (BM_ISALIAS(entry) && entry->d.alias.original == info->entry) {
  3741.     info->count--;
  3742.   }
  3743. }
  3744.  
  3745.  
  3746. static void
  3747. bm_delete_alias_for(MWContext* context, BM_Entry* entry, void* closure)
  3748. {
  3749.   BM_Entry* base = (BM_Entry*) closure;
  3750.   if (BM_ISALIAS(entry) && entry->d.alias.original == base) {
  3751.     BM_RemoveChildFromHeader(context, entry->parent, entry);
  3752.     BM_FreeEntry(context, entry);
  3753.   }
  3754. }
  3755.  
  3756.  
  3757.  
  3758. static void
  3759. bm_check_dangling_aliases(MWContext* context, BM_Entry* entry, void* closure)
  3760. {
  3761.   BM_Frame* f = GETFRAME(context);
  3762.   bm_goingaway_info* info = (bm_goingaway_info*) closure;
  3763.   if (entry->flags & BM_ATTR_HASALIASES) {
  3764.     info->count = BM_CountAliases(context, entry);
  3765.     if (info->count) {
  3766.       /* Reduce the count by the number of aliases that we're going to
  3767.          delete. */
  3768.       info->entry = entry;
  3769.       BM_EachProperSelectedEntryDo(context, bm_subtract_alias_for, info, NULL);
  3770.       XP_ASSERT(info->count >= 0);
  3771.       if (info->count) {
  3772.         if (!info->userasked) {
  3773.           if (f->gSelectionCount < 0) bm_SyncSelection(context);
  3774.           if (f->gSelectionCount == 1) {
  3775.             char* buf = (char*) XP_ALLOC(512);
  3776.             if (buf) {
  3777.               XP_SPRINTF(buf,
  3778.                          XP_GetString(XP_BKMKS_REMOVE_THIS_ITEMS_ALIASES), info->count);
  3779.               info->userconfirmed = FE_Confirm(context, buf);
  3780.               XP_FREE(buf);
  3781.             } else {
  3782.               info->userconfirmed =
  3783.                 FE_Confirm
  3784.                 (context,
  3785.                  XP_GetString(XP_BKMKS_REMOVE_SOME_ITEMS_ALIASES) );
  3786.             }
  3787.             info->userasked = TRUE;
  3788.           }
  3789.           if (info->userconfirmed) {
  3790.             BM_EachEntryDo(context, bm_delete_alias_for, entry);
  3791.           }
  3792.         }
  3793.       }
  3794.     }
  3795.   }
  3796. }
  3797.  
  3798. static void
  3799. bm_delete(MWContext* context)
  3800. {
  3801.     BM_Entry*        tmp;
  3802.     struct BM_Entry_Focus bmFocus;
  3803.     bm_goingaway_info info;
  3804.  
  3805.     tmp = BM_NewHeader("");
  3806.     XP_ASSERT(tmp);
  3807.     if (!tmp) return;
  3808.  
  3809.     XP_MEMSET(&info, 0, sizeof(info));
  3810.     BM_EachProperSelectedEntryDo(context, bm_check_dangling_aliases, &info,
  3811.       NULL);
  3812.     if (info.userasked && !info.userconfirmed) return;
  3813.  
  3814.     bmFocus.saveFocus = (BM_Entry*) NULL;
  3815.     bmFocus.foundSelection = FALSE;
  3816.     BM_EachProperSelectedEntryDo(context, remove_to, tmp, &bmFocus);
  3817.  
  3818.     BM_FreeEntry(context, tmp);
  3819.     bm_SyncCount(context);
  3820.  
  3821.     if (bmFocus.saveFocus == NULL)
  3822.       bmFocus.saveFocus = BM_GetRoot(context);
  3823.     if (bmFocus.saveFocus)
  3824.       BM_SETFLAG(bmFocus.saveFocus, BM_ATTR_SELECTED);
  3825.     bm_refresh(context, 1, BM_LAST_CELL);
  3826. }
  3827.  
  3828.  
  3829. static void
  3830. bm_copy(MWContext* context)
  3831. {
  3832.   char*            block;
  3833.   int32            length;
  3834.  
  3835.   block = BM_ConvertSelectionsToBlock(context, TRUE, &length);
  3836.   BMFE_SetClipContents(context, (void*)block, length);
  3837.  
  3838.   XP_FREE(block);
  3839. }
  3840.  
  3841. static void
  3842. bm_cut(MWContext* context)
  3843. {
  3844.   bm_copy(context);
  3845.   bm_delete(context);
  3846. }
  3847.  
  3848.  
  3849. static void
  3850. bm_paste(MWContext* context)
  3851. {
  3852.   BM_Entry*        firstSelected;
  3853.   char*            buffer;
  3854.   int32            length;
  3855.  
  3856.   firstSelected = BM_FirstSelectedItem(context);
  3857.   buffer = (char*)BMFE_GetClipContents(context, &length);
  3858.   if (buffer)
  3859.     {
  3860.       BM_InsertBlockAt(context, buffer, firstSelected, TRUE, length);
  3861.       bm_SyncCount(context);
  3862.       bm_refresh(context, 1, BM_LAST_CELL);
  3863.     }
  3864. }
  3865.  
  3866. /* Insert a block of long-format entries */
  3867. PUBLIC void
  3868. BM_DropBlockL( MWContext *pContext, char *pData, BM_Entry *firstSelected )
  3869. {
  3870.    int32 length;
  3871.  
  3872.    if( !firstSelected )
  3873.    {
  3874.       firstSelected = BM_FirstSelectedItem( pContext );
  3875.    }
  3876.    
  3877.    if( pData )
  3878.    {
  3879.       /* Length is stored at byte 0 as int32 */
  3880.       XP_MEMCPY( &length, pData, sizeof(int32) );
  3881.       pData += sizeof(int32);
  3882.       
  3883.       BM_InsertBlockAt( pContext, pData, firstSelected, TRUE, length );
  3884.       bm_SyncCount( pContext );
  3885.       bm_refresh( pContext, 1, BM_LAST_CELL );
  3886.    }
  3887. }
  3888.  
  3889. /* returns an integer index of the item in the visible tree */
  3890. int32
  3891. BM_GetIndex(MWContext* context, BM_Entry* item)
  3892. {
  3893.     int32 count = 1;
  3894.  
  3895.     CHKCONTEXT(context);
  3896.  
  3897.     if (BM_GetRoot(context))
  3898.         return bm_GetIndexNum(BM_GetRoot(context), item, &count);
  3899.     else
  3900.         return 0;
  3901. }
  3902.  
  3903. /* returns an integer index of the item in the list and does not pay
  3904.     attention to the BM_ATTR_FOLDED value */
  3905. int32
  3906. BM_GetUnfoldedIndex(MWContext* context, BM_Entry* item)
  3907. {
  3908.     int32 count = 1;
  3909.  
  3910.     BM_Entry* root = BM_GetRoot(context);
  3911.  
  3912.     CHKCONTEXT(context);
  3913.  
  3914.     if (root)
  3915.         return bm_GetUnfoldedIndexNum(root, item, &count);
  3916.     else
  3917.         return 0;
  3918. }
  3919.  
  3920. /* returns TRUE if the second argument is a direct
  3921.     descendent of the first argument.
  3922.     returns FALSE otherwise */
  3923. PUBLIC XP_Bool
  3924. BM_IsDescendent(MWContext* context, BM_Entry* parent, BM_Entry* possible_child)
  3925. {
  3926.     int32 count = 1;
  3927.     CHKCONTEXT(context);
  3928.  
  3929.     if (    parent &&
  3930.             parent->type == BM_TYPE_HEADER &&
  3931.             bm_GetUnfoldedIndexNum(parent, possible_child, &count))
  3932.         return TRUE;
  3933.  
  3934.     return FALSE;
  3935. }
  3936.  
  3937.  
  3938. /* returns an integer depth of the item in the list starting at zero */
  3939. PUBLIC int32
  3940. BM_GetDepth(MWContext* context, BM_Entry* item)
  3941. {
  3942.     CHKCONTEXT(context);
  3943.     if (BM_GetRoot(context))
  3944.         return bm_GetDepth(BM_GetRoot(context), item);
  3945.     else
  3946.         return 0;
  3947. }
  3948.  
  3949.  
  3950. /* returns the item at "count" visible indexes below "item" */
  3951. static BM_Entry*
  3952. bm_AtIndex(BM_Entry* item, int32* count)
  3953. {
  3954.     XP_ASSERT(item);
  3955.     XP_ASSERT(item->type == BM_TYPE_HEADER);
  3956.  
  3957.     (*count)--;
  3958.  
  3959.     /* first check to see if parent is the node we are looking for */
  3960.     if (*count <= 0)
  3961.         return item;
  3962.  
  3963.     if (!BM_ISFOLDED(item))
  3964.     {
  3965.         BM_Entry*        child;
  3966.  
  3967.         child = item->d.header.children;
  3968.         while (child)
  3969.         {
  3970.             if (child->type == BM_TYPE_HEADER)
  3971.             {
  3972.                 BM_Entry*        rv = NULL;
  3973.  
  3974.                 rv = bm_AtIndex(child, count);
  3975.                 if (rv)
  3976.                     return rv;
  3977.             }
  3978.             else
  3979.             {
  3980.                 (*count)--;
  3981.                 if (*count <= 0)
  3982.                     return child;
  3983.             }
  3984.             child = child->next;
  3985.         }
  3986.     }
  3987.     return NULL;
  3988. }
  3989.  
  3990. /* returns the object associated with the index returned by BM_GetIndex() */
  3991. BM_Entry*
  3992. BM_AtIndex(MWContext* context, int32 index)
  3993. {
  3994.     BM_Frame* f = GETFRAME(context);
  3995.     static BM_Frame*        last_f = NULL;
  3996.     static BM_Entry*        last_item = NULL;
  3997.     static int32            last_index = -1;
  3998.  
  3999.     int32                    count = index;
  4000.  
  4001.     CHKCONTEXT(context);
  4002.  
  4003.     /* only used the cached items if last_item is non-NULL and the
  4004.     requested index is one more than the last requested index */
  4005.     if (last_f == f && last_item && (index == (last_index + 1)))
  4006.     {
  4007.         /* if we're not a header
  4008.             or we're a header but folded,
  4009.             or we're a header but have no children,
  4010.             just go to the next item and set the local cache */
  4011.         if (    (last_item->type != BM_TYPE_HEADER) ||
  4012.                 (last_item->flags & BM_ATTR_FOLDED) ||
  4013.                 (! last_item->d.header.children))
  4014.         {
  4015.             last_item = last_item->next;
  4016.             if (last_item)
  4017.             {
  4018.                 last_index = index;
  4019.                 return last_item;
  4020.             }
  4021.             else
  4022.             {
  4023.                 last_index = -1;
  4024.                 return BM_AtIndex(context, index);
  4025.             }
  4026.         }
  4027.         else
  4028.         /* we're a header, we're unfolded, and we have children */
  4029.         {
  4030.             last_item = last_item->d.header.children;
  4031.             last_index = index;
  4032.             return last_item;
  4033.         }
  4034.     }
  4035.  
  4036.     if (BM_GetRoot(context) && index > 0)
  4037.     {
  4038.         last_item =    bm_AtIndex(BM_GetRoot(context), &count);
  4039.         if (last_item)
  4040.         {
  4041.             last_f = f;
  4042.             last_index = index;
  4043.             return last_item;
  4044.         }
  4045.     }
  4046.     return NULL;
  4047. }
  4048.  
  4049. PRIVATE BM_Entry*
  4050. bm_GetUnfoldedIndex(BM_Entry* parent, int32* index)
  4051. {
  4052.     BM_Entry* child;
  4053.     BM_Entry* rv = 0;
  4054.  
  4055.     XP_ASSERT(parent);
  4056.     XP_ASSERT(parent->type == BM_TYPE_HEADER);
  4057.     child = parent->d.header.children;
  4058.  
  4059.     while (child)
  4060.     {
  4061.         *(index) -= 1;
  4062.  
  4063.         if (*index <= 0)
  4064.             return child;
  4065.  
  4066.         if (child->type == BM_TYPE_HEADER)
  4067.         {
  4068.             rv = bm_GetUnfoldedIndex(child, index);
  4069.  
  4070.             if (rv)
  4071.                 return rv;
  4072.         }
  4073.         child = child->next;
  4074.     }
  4075.  
  4076.     return NULL;
  4077. }
  4078.  
  4079. /* returns the object associated with the index returned by BM_GetUnfoldedIndex() */
  4080. PUBLIC BM_Entry*
  4081. BM_AtUnfoldedIndex(MWContext* context, int32 index)
  4082. {
  4083.       CHKCONTEXT(context);
  4084.     if (BM_GetRoot(context) && index > 0)
  4085.         return bm_GetUnfoldedIndex(BM_GetRoot(context), &index);
  4086.     else
  4087.         return NULL;
  4088. }
  4089.  
  4090.  
  4091. static void
  4092. bm_fold_header_all(MWContext* context, BM_Entry* entry, XP_Bool fold,
  4093.                    XP_Bool refresh)
  4094. {
  4095.   if (BM_ISHEADER(entry)) {
  4096.     BM_FoldHeader(context, entry, fold, refresh, FALSE);
  4097.     for (entry = entry->d.header.children ; entry ; entry = entry->next) {
  4098.       bm_fold_header_all(context, entry, fold, refresh);
  4099.     }
  4100.   }
  4101. }
  4102.  
  4103.  
  4104. /* folds the header bm
  4105.     if fold is TRUE, the item becomes folded
  4106.         else the item is unfolded
  4107.     if refresh is TRUE, the FE is called to
  4108.         redraw necessary items
  4109.     if foldAll is TRUE, all headers appearing
  4110.         below bm in the tree are folded or unfolded
  4111.         according to "fold"
  4112. */
  4113. PUBLIC void
  4114. BM_FoldHeader(MWContext* context, BM_Entry* bm, XP_Bool fold, XP_Bool refresh, XP_Bool foldAll)
  4115. {
  4116.   BM_Frame* f = GETFRAME(context);
  4117.     int32                firstChangedCell = 0;
  4118.  
  4119.     CHKCONTEXTVOID(context);
  4120.     XP_ASSERT(bm);
  4121.     if (!bm) return;
  4122.  
  4123.     bm_CancelLastFind(context);
  4124.  
  4125.     f->max_depth = 0;
  4126.     if (foldAll)
  4127.     {
  4128.       bm_start_batch(context);
  4129.       bm_fold_header_all(context, bm, fold, refresh);
  4130.       bm_end_batch(context);
  4131.       return;
  4132.     }
  4133.     else
  4134.     {
  4135.  
  4136.         if (BM_ISFOLDED(bm) != fold)
  4137.         {
  4138.             int32        count;
  4139.             firstChangedCell = BM_GetIndex(context, bm);
  4140.  
  4141.             count = firstChangedCell;
  4142.  
  4143.             if (bm)
  4144.             {
  4145.                 if (fold)
  4146.                 {
  4147.                     if (firstChangedCell != 0)
  4148.                         BM_ClearAllChildSelection(context, bm, FALSE);
  4149.                     BM_SETFLAG(bm, BM_ATTR_FOLDED);
  4150.                 }
  4151.                 else
  4152.                     BM_CLEARFLAG(bm, BM_ATTR_FOLDED);
  4153.             }
  4154.  
  4155.         }
  4156.     }
  4157.  
  4158.     bm_SetModified(context, TRUE);
  4159.     bm_SyncCount(context);
  4160.  
  4161.     if (foldAll)
  4162.         firstChangedCell = MIN(1, firstChangedCell);
  4163.  
  4164.     if (refresh && (firstChangedCell != 0))
  4165.         bm_refresh(context, firstChangedCell, BM_LAST_CELL);
  4166. }
  4167.  
  4168.  
  4169.  
  4170. /* clears the selection state of all items in the tree
  4171.     if refresh is TRUE, the FE is called to redraw items
  4172.     which need to be redrawn
  4173. */
  4174. PUBLIC void
  4175. BM_ClearAllSelection(MWContext* context, XP_Bool refresh)
  4176. {
  4177.   BM_Frame* f = GETFRAME(context);
  4178.     int32        t = 1;
  4179.     CHKCONTEXTVOID(context);
  4180.  
  4181.     bm_ClearSelection(context, BM_GetRoot(context), refresh, &t);
  4182.     f->gSelectionCount = 0;
  4183.     f->gSelectionMask = 0;
  4184. }
  4185.  
  4186. /*  Clears the selection state of all children of the item passed.
  4187.     if refresh is TRUE, the FE is called to redraw items
  4188.     which need to be redrawn
  4189. */
  4190. PUBLIC void
  4191. BM_ClearAllChildSelection(MWContext* context, BM_Entry* at, XP_Bool refresh)
  4192. {
  4193.     CHKCONTEXTVOID(context);
  4194.     if (!at) return;
  4195.     if (at->type != BM_TYPE_HEADER)
  4196.         return;
  4197.     at = at->d.header.children;
  4198.     while (at)
  4199.     {
  4200.         if (BM_ISSELECTED(at))
  4201.           BM_SelectItem(context, at, refresh, TRUE, FALSE);
  4202.         if ((at->type == BM_TYPE_HEADER) && (at->d.header.children))
  4203.             BM_ClearAllChildSelection(context, at->d.header.children, refresh);
  4204.         at = at->next;
  4205.     }
  4206. }
  4207.  
  4208. /* selects the item
  4209.     if refresh is TRUE, the FE is called to redraw the item
  4210.     if extend is TRUE, the item is added to the selection
  4211.         else the selection is cleared and the item becomes
  4212.         the selection
  4213.     if select is TRUE, the item is selected
  4214.         else it is deselected
  4215.  
  4216.     if extend is FALSE and select is FALSE, the selection
  4217.     becomes empty
  4218. */
  4219. PUBLIC void
  4220. BM_SelectItem(MWContext* context, BM_Entry* item, XP_Bool refresh,
  4221.               XP_Bool extend, XP_Bool select)
  4222. {
  4223.   BM_Frame* f = GETFRAME(context);
  4224.   CHKCONTEXTVOID(context);
  4225.   XP_ASSERT(item);
  4226.   if (!item) return;
  4227.   bm_start_batch(context);
  4228.   bm_CancelLastFind(context);
  4229.   if (!extend) {
  4230.     BM_ClearAllSelection(context, refresh);
  4231.     if (select) f->lastSelectedItem = item;
  4232.   }
  4233.   if (select) {
  4234.     if (!BM_ISSELECTED(item)) f->gSelectionCount++;
  4235.     f->gSelectionMask |= item->type;
  4236.     BM_SETFLAG(item, BM_ATTR_SELECTED);
  4237.   } else {
  4238.     BM_CLEARFLAG(item, BM_ATTR_SELECTED);
  4239.     f->gSelectionCount = -9999;
  4240.   }
  4241.   if (refresh) {
  4242.     int32 index = BM_GetIndex(context, item);
  4243.     if (index) bm_refresh(context, index, index);
  4244.   }
  4245.   if (!extend) BMFE_EditItem(context, item);
  4246.   bm_end_batch(context);
  4247. }
  4248.  
  4249.  
  4250.  
  4251. static BM_Entry*
  4252. bm_validate_selected_item(MWContext* context, BM_Entry* item)
  4253. {
  4254.   BM_Frame* f = GETFRAME(context);
  4255.   BM_Entry* result;
  4256.   for (; item ; item = item->next) {
  4257.     if (item == f->lastSelectedItem && BM_ISSELECTED(item)) return item;
  4258.     if (BM_ISHEADER(item) && !BM_ISFOLDED(item)) {
  4259.       result = bm_validate_selected_item(context, item->d.header.children);
  4260.       if (result) return result;
  4261.     }
  4262.   }
  4263.   return NULL;
  4264. }
  4265.  
  4266.  
  4267.  
  4268. static void
  4269. bm_select_range(MWContext* context, BM_Entry* item, int32 min, int32 max,
  4270.                 int32* cur)
  4271. {
  4272.   for (; item ; item = item->next) {
  4273.     if (*cur >= min) {
  4274.       if (*cur > max) return;
  4275.       BM_SelectItem(context, item, FALSE, TRUE, TRUE);
  4276.     }
  4277.     (*cur)++;
  4278.     if (BM_ISHEADER(item) && !BM_ISFOLDED(item)) {
  4279.       bm_select_range(context, item->d.header.children, min, max, cur);
  4280.     }
  4281.   }
  4282. }
  4283.  
  4284.  
  4285.  
  4286. void
  4287. BM_SelectRangeTo(MWContext* context, BM_Entry* item)
  4288. {
  4289.   BM_Frame* f = GETFRAME(context);
  4290.   int32 min;
  4291.   int32 max;
  4292.   int32 cur;
  4293.   CHKCONTEXTVOID(context);
  4294.   XP_ASSERT(item);
  4295.   if (!item) return;
  4296.   /* First very carefully validate the lastSelectedItem pointer.  That item
  4297.      might have been deleted or something, and the code in question may not
  4298.      have updated the lastSelectedItem pointer.  So, we make sure that it still
  4299.      points to a valid item, and that the item is selected. */
  4300.   f->lastSelectedItem = bm_validate_selected_item(context,
  4301.                                                   BM_GetRoot(context));
  4302.   if (!f->lastSelectedItem) {
  4303.     BM_SelectItem(context, item, TRUE, FALSE, TRUE);
  4304.     XP_ASSERT(f->lastSelectedItem == item);    /* Not that we can do much if
  4305.                                                this fails...*/
  4306.     return;
  4307.   }
  4308.   min = BM_GetIndex(context, f->lastSelectedItem);
  4309.   max = BM_GetIndex(context, item);
  4310.   if (min < 1 || max < 1) return;
  4311.   if (min > max) {
  4312.     int32 tmp = min;
  4313.     min = max;
  4314.     max = tmp;
  4315.   }
  4316.   bm_start_batch(context);
  4317.   BM_ClearAllSelection(context, TRUE);
  4318.   cur = 1;
  4319.   bm_select_range(context, BM_GetRoot(context), min, max, &cur);
  4320.   XP_ASSERT(BM_ISSELECTED(item));                /* More sanity checking; not */
  4321.   XP_ASSERT(BM_ISSELECTED(f->lastSelectedItem)); /* really much we can do if
  4322.                                                     these assertions fail.*/
  4323.   bm_refresh(context, min, max);
  4324.   bm_end_batch(context);
  4325. }
  4326.  
  4327.  
  4328. /* toggles the selected state of the item
  4329. (see BM_SelectItem) */
  4330. PUBLIC void
  4331. BM_ToggleItem(MWContext* context, BM_Entry* item, XP_Bool refresh, XP_Bool extend)
  4332. {
  4333.     CHKCONTEXTVOID(context);
  4334.     XP_ASSERT(item);
  4335.     if (!item) return;
  4336.  
  4337.     if (item->flags & BM_ATTR_SELECTED)
  4338.         BM_SelectItem(context, item, refresh, extend, FALSE);
  4339.     else
  4340.         BM_SelectItem(context, item, refresh, extend, TRUE);
  4341. }
  4342.  
  4343.  
  4344.  
  4345. static XP_Bool
  4346. bm_ConfirmSave(MWContext* context)
  4347. {
  4348. /*    XP_Bool        doSave = FALSE;
  4349.     char*        msg = "Save changes to %s?\n";
  4350.  
  4351.     sprintf(msg, f->gFile);
  4352.  
  4353.     if (BM_Modified(context))
  4354.         doSave = FE_SimpleConfirm(msg);
  4355.     return doSave;
  4356. */
  4357.     return TRUE;
  4358. }
  4359.  
  4360.  
  4361. const char*
  4362. BM_GetFileName(MWContext* context)
  4363. {
  4364.   BM_Frame* f = GETFRAME(context);
  4365.   return f->gFile;
  4366. }
  4367.  
  4368.  
  4369.  
  4370. static XP_Bool
  4371. bm_insert_bogus_aliases(XP_HashTable table, const void* key, void* value,
  4372.                         void* closure)
  4373. {
  4374.   MWContext* context = (MWContext*) closure;
  4375.   bm_alias_info* info = (bm_alias_info*) value;
  4376.   if (info->entry->parent == NULL) {
  4377.     /* This was an alias that was made up and inserted because we never could
  4378.        find the real entry for it.  So, now we had better insert it into the
  4379.        tree. */
  4380.     if (context->type == MWContextBookmarks) {
  4381.       bm_AppendChildToHeader(context, BM_GetRoot(context), info->entry);
  4382.     } else {
  4383.       bm_AddChildToHeaderSorted(context, BM_GetRoot(context), info->entry);
  4384.     }
  4385.   }
  4386.   return TRUE;
  4387. }
  4388.  
  4389.  
  4390. /* Make sure the address book is sorted. */
  4391. static void
  4392. bm_resort_headers(MWContext* context, BM_Entry* header)
  4393. {
  4394.   XP_Bool needssort;
  4395.   BM_Entry* entry;
  4396.   BM_Entry* prev;
  4397.   for ( ; header ; header = header->next) {
  4398.     if (BM_ISHEADER(header)) {
  4399.       prev = NULL;
  4400.       needssort = FALSE;
  4401.       for (entry = header->d.header.children ; entry ; entry = entry->next) {
  4402.         if (BM_ISHEADER(entry)) bm_resort_headers(context, entry);
  4403.         if (prev && bm_SortAddressBook(prev, entry) > 0) {
  4404.           needssort = TRUE;
  4405.         }
  4406.         prev = entry;
  4407.       }
  4408.       if (needssort) {
  4409.         BM_SelectItem(context, header, FALSE, FALSE, TRUE);
  4410.         bm_SortSelected(context, BM_Sort_Name);
  4411.       }
  4412.     }
  4413.   }
  4414. }
  4415.  
  4416.  
  4417. /* read bmlist file from disk
  4418.     pass in a file url */
  4419. PUBLIC void
  4420. BM_ReadBookmarksFromDisk(MWContext* context, const char* filename,
  4421.                          const char* relative_url)
  4422. {
  4423.   BM_Frame* f = GETFRAME(context);
  4424.   XP_File fp;
  4425.   char* buffer;
  4426.   UndoState* undo;
  4427.  
  4428.   CHKCONTEXTVOID(context);
  4429.  
  4430.   undo = f->undo;
  4431.   if (BM_Modified(context)) {
  4432.     if (!bm_ConfirmSave(context)) return;
  4433.     if (BM_SaveBookmarks(context, f->gFile) < 0) return;
  4434.   }
  4435.   if (f->gBookmarks) BM_FreeEntry(context, f->gBookmarks);
  4436.   f->gBookmarks = NULL;
  4437.  
  4438.   buffer = (char*) XP_ALLOC(READ_BUFFER_SIZE);
  4439.   if (!buffer) return;
  4440.  
  4441.   /* don't kill ourselves */
  4442.   if (f->gFile != filename)
  4443.       StrAllocCopy(f->gFile, filename);
  4444.   
  4445.   XP_ASSERT(f->gFile != NULL);
  4446.   
  4447.  
  4448.   if (XP_Stat(filename, &(f->laststat), xpBookmarks) != 0) {
  4449.     XP_MEMSET(&(f->laststat), 0, sizeof(f->laststat));
  4450.   }
  4451.  
  4452.   fp = XP_FileOpen(filename, xpBookmarks, XP_FILE_READ);
  4453.  
  4454.   if (!fp) {
  4455.     XP_FREE(buffer);
  4456.     return;
  4457.   }
  4458.  
  4459.   /* read in the first line */
  4460.   XP_FileReadLine(buffer, READ_BUFFER_SIZE, fp);
  4461.  
  4462.   /* DONT REQUIRE THE COOKIE FOR NOW
  4463.    *
  4464.    * if(XP_STRNCMP(buffer, BMLIST_COOKIE, strlen(BMLIST_COOKIE)
  4465.    && XP_STRNCMP(buffer, BM_ADDR_LIST_COOKIE, strlen(BM_ADDR_LIST_COOKIE))
  4466.    *   {
  4467.    *        TRACEMSG(("ERROR! - Hotlist cookie not found in bmlist file"));
  4468.    *       XP_FREE(buffer);
  4469.    *       return;
  4470.    *   }
  4471.    */
  4472.  
  4473.   f->undo = NULL;            /* No need to log all this stuff... */
  4474.   bm_start_batch(context);
  4475.   bm_refresh(context, 1, BM_LAST_CELL);
  4476.  
  4477.   bm_clear_alias_info(context);
  4478.  
  4479.   /* gBookmarks shouldn't exist yet! */
  4480.   bm_ReadFromHTML(context, fp, NULL, buffer, relative_url);
  4481.   bm_SyncCount(context);
  4482.  
  4483.   XP_FileClose(fp);
  4484.  
  4485.   XP_Maphash(f->aliasTable, bm_insert_bogus_aliases, context);
  4486.  
  4487.   if (context->type == MWContextAddressBook) {
  4488.     bm_resort_headers(context, BM_GetRoot(context));
  4489.   }
  4490.  
  4491.   bm_SetModified(context, FALSE);
  4492.  
  4493.   XP_FREE(buffer);
  4494.  
  4495.   bm_end_batch(context);
  4496.   f->undo = undo;
  4497.   UNDO_DiscardAll(undo);
  4498. }
  4499.  
  4500. PUBLIC int32
  4501. BM_SaveBookmarks(MWContext* context, const char* filename)
  4502. {
  4503.   BM_Frame* f = GETFRAME(context);
  4504.   XP_File fp = NULL;
  4505.   const char* bm_list_name;
  4506.   XP_FileType tmptype;
  4507.   char* tmpname = NULL;
  4508.   XP_StatStruct curstat;
  4509.   XP_Bool defaultFile;
  4510.   BM_SortType enSortType = f->enSortType;
  4511.   /* recognize if we're saving the current bookmarks file */
  4512.   defaultFile = (filename == NULL || (f->gFile && XP_STRCMP(filename, f->gFile) == 0));
  4513.  
  4514.   CHKCONTEXT(context);
  4515.  
  4516.   if (filename == NULL) {
  4517.     filename = f->gFile;
  4518.     if (filename == NULL) return -1; /* ### */
  4519.     if (XP_Stat(filename, &curstat, xpBookmarks) != 0) {
  4520.       /* The stat failed.  Treat it as if the stat gave the same thing as last
  4521.          time (i.e., make sure to *not* whine about the file changing from us;
  4522.          most likely, the user just removed it.) */
  4523.       XP_MEMCPY(&curstat, &(f->laststat), sizeof(curstat));
  4524.     }
  4525.     if (curstat.st_mtime != f->laststat.st_mtime ||
  4526.         curstat.st_size != f->laststat.st_size) {
  4527.       if (f->gBookmarksModified) {
  4528.         if (FE_Confirm(context,
  4529.                        XP_GetString(context->type == MWContextAddressBook ?
  4530.                                     XP_BKMKS_ADDRESSBOOK_CONFLICT :
  4531.                                     XP_BKMKS_BOOKMARKS_CONFLICT))) {
  4532.           f->gBookmarksModified = FALSE; /* Prevent BM_ReadBookmarksFromDisk
  4533.                                             from calling us back again. */
  4534.           BM_ReadBookmarksFromDisk(context, filename, NULL);
  4535.           return 0;
  4536.         }
  4537.       } else {
  4538.         FE_Alert(context,
  4539.                  XP_GetString(context->type == MWContextAddressBook ?
  4540.                               XP_BKMKS_ADDRESSBOOK_CHANGED :
  4541.                               XP_BKMKS_BOOKMARKS_CHANGED));
  4542.         BM_ReadBookmarksFromDisk(context, filename, NULL);
  4543.         return 0;
  4544.       }
  4545.     } else {
  4546.       if (!f->gBookmarksModified) return 0; /* No changes need to be saved. */
  4547.     }
  4548.   }
  4549.  
  4550.   /* Save the natural (user arranged) sort */
  4551.   if (defaultFile && (enSortType != BM_Sort_Natural)) {
  4552.     bm_SortSilent( context, BM_Sort_Natural );
  4553.   }
  4554.  
  4555.   bm_list_name = FE_UsersFullName();
  4556.   if (!bm_list_name) bm_list_name = FE_UsersMailAddress();
  4557.  
  4558.   tmpname = FE_GetTempFileFor(NULL, filename, xpBookmarks, &tmptype);
  4559.   if (!tmpname || tmpname[0] == 0) goto FAIL;
  4560.  
  4561.   fp = XP_FileOpen(tmpname, tmptype, XP_FILE_WRITE);
  4562.  
  4563.   if (!fp) goto FAIL;
  4564.  
  4565.   /* write cookie */
  4566.   if (context->type == MWContextBookmarks) {
  4567.     if (bm_write_ok(BMLIST_COOKIE, -1, fp) < 0) goto FAIL;
  4568.   } else {
  4569.     if (bm_write_ok(BM_ADDR_LIST_COOKIE, -1, fp) < 0) goto FAIL;
  4570.   }
  4571.   if (bm_write_ok(LINEBREAK, LINEBREAK_LEN, fp) < 0) goto FAIL;
  4572.  
  4573.   if (bm_write_ok(XP_GetString(XP_BKMKS_AUTOGENERATED_FILE), -1, fp) < 0) {
  4574.     goto FAIL;
  4575.   }
  4576.   if (bm_write_ok(LINEBREAK, LINEBREAK_LEN, fp) < 0) goto FAIL;
  4577.   if (bm_write_ok(XP_GetString(XP_BKMKS_READ_AND_OVERWRITE), -1, fp) < 0) goto FAIL;
  4578.   if (bm_write_ok(LINEBREAK, LINEBREAK_LEN, fp) < 0) goto FAIL;
  4579.   if (bm_write_ok(XP_GetString(XP_BKMKS_DO_NOT_EDIT), -1, fp) < 0) goto FAIL;
  4580.   if (bm_write_ok(LINEBREAK, LINEBREAK_LEN, fp) < 0) goto FAIL;
  4581.  
  4582.   if(context->type == MWContextBookmarks) {
  4583.       if(bm_list_name) {
  4584.           XP_FilePrintf(fp, XP_GetString(XP_BKMKS_SOMEONE_S_BOOKMARKS),
  4585.                     "<TITLE>", 
  4586.                     bm_list_name,
  4587.                     "</TITLE>" LINEBREAK);
  4588.           XP_FilePrintf(fp, XP_GetString(XP_BKMKS_SOMEONE_S_BOOKMARKS),
  4589.                     "<H1>",
  4590.                     bm_list_name,
  4591.                     "</H1>\n" LINEBREAK);
  4592.      } else {    
  4593.           XP_FilePrintf(fp, XP_GetString(XP_BKMKS_PERSONAL_BOOKMARKS),
  4594.                     "<TITLE>",
  4595.                     "</TITLE>" LINEBREAK);
  4596.           XP_FilePrintf(fp, XP_GetString(XP_BKMKS_PERSONAL_BOOKMARKS),
  4597.                     "<H1>",
  4598.                     "</H1>\n" LINEBREAK);
  4599.     }
  4600.   } else {
  4601.       if(bm_list_name) {
  4602.           XP_FilePrintf(fp, XP_GetString(XP_BKMKS_SOMEONE_S_ADDRESSBOOK),
  4603.                     "<TITLE>", 
  4604.                     bm_list_name,
  4605.                     "</TITLE>" LINEBREAK);
  4606.           XP_FilePrintf(fp, XP_GetString(XP_BKMKS_SOMEONE_S_ADDRESSBOOK),
  4607.                     "<H1>",
  4608.                     bm_list_name,
  4609.                     "</H1>\n" LINEBREAK);
  4610.      } else {
  4611.           XP_FilePrintf(fp, XP_GetString(XP_BKMKS_PERSONAL_ADDRESSBOOK),
  4612.                     "<TITLE>",
  4613.                     "</TITLE>" LINEBREAK);
  4614.           XP_FilePrintf(fp, XP_GetString(XP_BKMKS_PERSONAL_ADDRESSBOOK),
  4615.                     "<H1>",
  4616.                     "</H1>\n" LINEBREAK);
  4617.     }
  4618.   }    
  4619.  
  4620.   bm_clear_alias_info(context);
  4621.  
  4622.   if (BM_GetRoot(context)) {
  4623.     if (bm_WriteAsHTML(context, fp, BM_GetRoot(context), 0, FALSE) < 0) {
  4624.       goto FAIL;
  4625.     }
  4626.   } else {
  4627.     XP_TRACE(("No bmlist to write!"));   
  4628.   }
  4629.  
  4630.   if (XP_FileClose(fp) != 0) {
  4631.     fp = NULL;
  4632.     goto FAIL;
  4633.   }
  4634.   fp = NULL;
  4635.   XP_FileRename(tmpname, tmptype, filename, xpBookmarks);
  4636.   XP_FREE(tmpname);
  4637.   tmpname = NULL;
  4638.  
  4639. #ifdef XP_UNIX
  4640.   /* If we write the bookmarks with at different uid or gid 
  4641.    * than what it had, try to change it back.
  4642.    * Fix for 67572, bookmarks.html get wiped out.
  4643.    */
  4644.   if (curstat.st_uid != getuid()
  4645.       || curstat.st_gid != getgid()) {
  4646.     chown (filename, curstat.st_uid, curstat.st_gid);
  4647.   }
  4648. #endif /* XP_UNIX */
  4649.  
  4650.   /* only update global mod date if we saved the current bookmk file */ 
  4651.   if (defaultFile) {
  4652.   
  4653.     if (XP_Stat(filename, &(f->laststat), xpBookmarks) != 0) {
  4654.         XP_MEMSET(&(f->laststat), 0, sizeof(f->laststat));
  4655.       }
  4656.     
  4657.       bm_SetModified(context, FALSE);
  4658.  
  4659.     /* Reset the previous sort order */          
  4660.     if (enSortType != BM_Sort_Natural) {
  4661.       bm_SortSilent( context, enSortType );
  4662.     }
  4663.       
  4664.   }
  4665.  
  4666.   if (context->type == MWContextBookmarks) 
  4667.       f->errorSavingBookmarks = FALSE;
  4668.  
  4669.   return 1;
  4670.  
  4671. FAIL:
  4672.   if (fp) XP_FileClose(fp);
  4673.   if (tmpname) {
  4674.     XP_FileRemove(tmpname, tmptype);
  4675.     XP_FREE(tmpname);
  4676.     tmpname = NULL;
  4677.   }
  4678.  
  4679.   if (context->type == MWContextAddressBook) {
  4680.       FE_Alert(context, XP_GetString(XP_BKMKS_CANT_WRITE_ADDRESSBOOK));
  4681.   }
  4682.   else {
  4683.       if (!f->errorSavingBookmarks) {
  4684.           FE_Alert(context, XP_GetString(XP_BKMKS_CANT_WRITE_BOOKMARKS));
  4685.           f->errorSavingBookmarks = TRUE;
  4686.       }
  4687.   }
  4688.  
  4689.   return -1;
  4690. }
  4691.  
  4692.  
  4693. /* returns the first selected item
  4694.     if parent is selected, parent is returned,
  4695.     otherwise the first selected child is returned
  4696.     if there is no selected item following parent,
  4697.     NULL is returned */
  4698. PRIVATE BM_Entry*
  4699. bm_FirstSelectedItem_1(BM_Entry* parent)
  4700. {
  4701.     BM_Entry*        child;
  4702.  
  4703.     XP_ASSERT(parent);
  4704.     XP_ASSERT(BM_ISHEADER(parent));
  4705.  
  4706.     if (BM_ISSELECTED(parent)) return parent;
  4707.  
  4708.     child = parent->d.header.children;
  4709.     while (child)
  4710.     {
  4711.         if (child->flags & BM_ATTR_SELECTED)
  4712.             return child;
  4713.         if (child->type == BM_TYPE_HEADER)
  4714.         {
  4715.             BM_Entry*    rv;
  4716.             rv = bm_FirstSelectedItem_1(child);
  4717.             if (rv)
  4718.                 return rv;
  4719.         }
  4720.         child = child->next;
  4721.     }
  4722.     return NULL;
  4723. }
  4724.  
  4725. PUBLIC BM_Entry*
  4726. BM_FirstSelectedItem(MWContext* context)
  4727. {
  4728.   CHKCONTEXT(context);
  4729.   return bm_FirstSelectedItem_1(BM_GetRoot(context));
  4730. }
  4731.  
  4732. static void
  4733. bm_InsertBySelection(MWContext* context, BM_Entry* firstSelected,
  4734.                      BM_Entry* newItem)
  4735. {
  4736.   BM_Frame* f = GETFRAME(context);
  4737.   CHKCONTEXTVOID(context);
  4738.   XP_ASSERT(firstSelected);
  4739.   if (!firstSelected) firstSelected = BM_GetRoot(context);
  4740.  
  4741.   /* If we've selected the root node, then make sure it's not folded, so that
  4742.      we will be sure to put our new item inside the root, where it belongs. */
  4743.  
  4744.   if (firstSelected == BM_GetRoot(context) && BM_ISFOLDED(firstSelected)) {
  4745.     BM_FoldHeader(context, firstSelected, FALSE, TRUE, FALSE);
  4746.   }
  4747.  
  4748.   f->max_depth = 0;
  4749.  
  4750.   if (context->type == MWContextAddressBook && !BM_ISALIAS(newItem)) {
  4751.     bm_AddChildToHeaderSorted(context, BM_GetRoot(context), newItem);
  4752.   } else {
  4753.     /* insert into header if it's open, else after it */
  4754.     if (BM_ISHEADER(firstSelected) && !BM_ISFOLDED(firstSelected)) {
  4755.       if (context->type == MWContextBookmarks) {
  4756.         BM_PrependChildToHeader(context, firstSelected, newItem);
  4757.       } else {
  4758.         bm_AddChildToHeaderSorted(context, firstSelected, newItem);
  4759.       }
  4760.     } else {
  4761.       if (context->type == MWContextBookmarks) {
  4762.         bm_InsertItemAfter(context, firstSelected, newItem, TRUE);
  4763.       } else {
  4764.         bm_AddChildToHeaderSorted(context, firstSelected->parent, newItem);
  4765.       }
  4766.     }
  4767.   }
  4768. }
  4769.  
  4770. static void
  4771. bm_BeginEditNewHeader(MWContext* context)
  4772. {
  4773.     BM_Entry* header;
  4774.     BM_Entry* firstSelected;
  4775.     int32 index;
  4776.  
  4777.  
  4778.     firstSelected = BM_FirstSelectedItem(context);
  4779.     if (firstSelected)
  4780.         index = BM_GetIndex(context, firstSelected);
  4781.     else
  4782.     {
  4783.         firstSelected = BM_GetRoot(context);
  4784.         index = 1;
  4785.     }
  4786.  
  4787.     header = BM_NewHeader(XP_GetString(XP_BKMKS_NEW_HEADER));
  4788.  
  4789.     BM_SETFLAG(header, BM_ATTR_ISNEW);
  4790.  
  4791.     bm_InsertBySelection(context, firstSelected, header);
  4792.     BM_SelectItem(context, header, TRUE, FALSE, TRUE);
  4793.     bm_SyncCount(context);
  4794.     bm_refresh(context,
  4795.                context->type == MWContextBookmarks ? index + 1 : 1,
  4796.                BM_LAST_CELL);
  4797.     BMFE_OpenBookmarksWindow(context);
  4798.     BMFE_EditItem(context, header);
  4799. }
  4800.  
  4801. static void
  4802. bm_BeginEditNewUrl(MWContext* context)
  4803. {
  4804.     BM_Entry*            url;
  4805.     BM_Entry*        firstSelected;
  4806.     int32            index;
  4807.  
  4808.     firstSelected = BM_FirstSelectedItem(context);
  4809.     if (firstSelected)
  4810.         index = BM_GetIndex(context, firstSelected);
  4811.     else
  4812.     {
  4813.         firstSelected = BM_GetRoot(context);
  4814.         index = 1;
  4815.     }
  4816.  
  4817.     if (context->type == MWContextBookmarks) {
  4818.       url = BM_NewUrl(XP_GetString(XP_BKMKS_NEW_BOOKMARK), NULL, NULL, 0);
  4819.     } else {
  4820.       url = bm_NewAddress("", "");
  4821.     }
  4822.     if (!url) return;
  4823.     BM_SETFLAG(url, BM_ATTR_ISNEW);
  4824.     if (context->type == MWContextBookmarks) {
  4825.       bm_InsertBySelection(context, firstSelected, url);
  4826.     } else {
  4827.       bm_AddChildToHeaderSorted(context, BM_GetRoot(context), url);
  4828.     }
  4829.     BM_SelectItem(context, url, TRUE, FALSE, TRUE);
  4830.     bm_SyncCount(context);
  4831.     bm_refresh(context,
  4832.                context->type == MWContextBookmarks ? index + 1 : 1,
  4833.                BM_LAST_CELL);
  4834.     BMFE_OpenBookmarksWindow(context);
  4835.     BMFE_EditItem(context, url);
  4836. }
  4837.  
  4838. PUBLIC void
  4839. BM_GotoBookmark(MWContext* context, BM_Entry* item)
  4840. {
  4841.     char*    url = NULL;
  4842.     char*    target = NULL;
  4843.  
  4844.     CHKCONTEXTVOID(context);
  4845.  
  4846.     XP_ASSERT(item);
  4847.     if (!item) return;
  4848.  
  4849.     if (item->type == BM_TYPE_ALIAS)
  4850.     {
  4851.       XP_ASSERT(BM_ISURL(item->d.alias.original));
  4852.       if (BM_ISURL(item->d.alias.original)) {
  4853.         url = item->d.alias.original->d.url.address;
  4854.         target = item->d.alias.original->d.url.target;
  4855.       }
  4856.     }
  4857.     else if (item->type == BM_TYPE_URL)
  4858.     {
  4859.         url = item->d.url.address;
  4860.         target = item->d.url.target;
  4861.     }
  4862.  
  4863.     if (url)
  4864.         BMFE_GotoBookmark(context, url, target);
  4865. }
  4866.  
  4867. static void
  4868. bm_BeginFindBookmark(MWContext* context)
  4869. {
  4870.   BM_Frame* f = GETFRAME(context);
  4871.   if (!f->gFindInfo) {
  4872.     f->gFindInfo = XP_NEW_ZAP(BM_FindInfo);
  4873.     if (!f->gFindInfo) return;
  4874.     if (context->type == MWContextAddressBook) {
  4875.       f->gFindInfo->checkNickname = TRUE;
  4876.     }
  4877.     f->gFindInfo->checkName = TRUE;
  4878.     f->gFindInfo->checkLocation = TRUE;
  4879.     f->gFindInfo->checkDescription = TRUE;
  4880.   }
  4881.   f->gFindInfo->lastEntry = NULL;
  4882.   f->gTemporary = BMFE_OpenFindWindow(context, f->gFindInfo);
  4883. }
  4884.  
  4885.  
  4886. PRIVATE void
  4887. bm_SelectAliases(MWContext* context, BM_Entry* at, BM_Entry* forEntry)
  4888. {
  4889.   BM_Entry* head;
  4890.   for ( ; at ; at = at->next) {
  4891.     if (at->type == BM_TYPE_HEADER) {
  4892.       bm_SelectAliases(context, at->d.header.children, forEntry);
  4893.     } else if (BM_ISALIAS(at) && at->d.alias.original == forEntry) {
  4894.       BM_SelectItem(context, at, TRUE, TRUE, TRUE);
  4895.       for (head = at->parent ; head ; head = head->parent) {
  4896.         if (BM_ISFOLDED(head)) {
  4897.           BM_FoldHeader(context, head, FALSE, TRUE, FALSE);
  4898.         }
  4899.       }
  4900.     }
  4901.   }
  4902. }
  4903.  
  4904. PUBLIC void
  4905. BM_SelectAliases(MWContext* context, BM_Entry* forEntry)
  4906. {
  4907.   CHKCONTEXTVOID(context);
  4908.   XP_ASSERT(forEntry);
  4909.   if (forEntry) {
  4910.     bm_start_batch(context);
  4911.     bm_SelectAliases(context, BM_GetRoot(context), forEntry);
  4912.     bm_end_batch(context);
  4913.     BMFE_ScrollIntoView(context, forEntry);
  4914.   }
  4915. }
  4916.  
  4917. static void
  4918. bm_CloseLastFind_1(MWContext* context, BM_Entry* entry, XP_Bool closeit)
  4919. {
  4920.   for ( ; entry ; entry = entry->next) {
  4921.     if (BM_ISHEADER(entry)) {
  4922.       if (entry->flags & BM_ATTR_FINDAFF) {
  4923.         if (closeit) BM_FoldHeader(context, entry, TRUE, TRUE, FALSE);
  4924.         BM_CLEARFLAG(entry, BM_ATTR_FINDAFF);
  4925.       }
  4926.       bm_CloseLastFind_1(context, entry->d.header.children, closeit);
  4927.     }
  4928.   }
  4929. }
  4930.  
  4931.  
  4932. /* Close any headers that we may have opened last time we did a find. */
  4933. static void
  4934. bm_CloseLastFind(MWContext* context)
  4935. {
  4936.   BM_Frame* f = GETFRAME(context);
  4937.   if (f->unfoldedForFind) {
  4938.     f->unfoldedForFind = FALSE; /* Do this first, to not confuse
  4939.                                       BM_FoldHeader(). */
  4940.     bm_CloseLastFind_1(context, BM_GetRoot(context), TRUE);
  4941.   }
  4942. }
  4943.  
  4944. /* Forget about any headers that were opened last time we did a find; leave
  4945.    things the way they are now. */
  4946. static void
  4947. bm_CancelLastFind(MWContext* context)
  4948. {
  4949.   BM_Frame* f = GETFRAME(context);
  4950.   if (f->unfoldedForFind) {
  4951.     f->unfoldedForFind = FALSE;
  4952.     bm_CloseLastFind_1(context, BM_GetRoot(context), FALSE);
  4953.   }
  4954. }
  4955.  
  4956. static void
  4957. bm_OpenNewFind(MWContext* context, BM_Entry* entry)
  4958. {
  4959.   BM_Frame* f = GETFRAME(context);
  4960.   BM_Entry* head;
  4961.   XP_Bool found = FALSE;
  4962.   if (!entry) return;
  4963.   bm_CloseLastFind(context);
  4964.   for (head = entry->parent ; head ; head = head->parent) {
  4965.     if (BM_ISFOLDED(head)) {
  4966.       BM_FoldHeader(context, head, FALSE, TRUE, FALSE);
  4967.       BM_SETFLAG(head, BM_ATTR_FINDAFF);
  4968.       found = TRUE;
  4969.     }
  4970.   }
  4971.   f->unfoldedForFind = found; /* Must set last, to not confuse
  4972.                                     BM_FoldHeader. */
  4973. }
  4974.  
  4975.  
  4976. void
  4977. BM_DoFindBookmark(MWContext* context, BM_FindInfo* findInfo)
  4978. {
  4979.   BM_Frame* f = GETFRAME(context);
  4980.   BM_Entry* found = NULL;
  4981.   BM_Entry* startAt = NULL;
  4982.   XP_Bool unfoldedForFind;
  4983.  
  4984.   /* If no find string is specified, return. */
  4985.   if (findInfo->textToFind == NULL)
  4986.       return;
  4987.  
  4988.   bm_CloseLastFind(context);
  4989.  
  4990.   if (findInfo->lastEntry) {
  4991.     startAt = bm_GetNextSpanningWrapping(context, findInfo->lastEntry);
  4992.   } else {
  4993.     startAt = BM_GetRoot(context);
  4994.   }
  4995.   found = bm_DoFindBookmark_1(context, startAt, findInfo);
  4996.  
  4997.   if (found) {
  4998.     bm_CloseLastFind(context);
  4999.     bm_OpenNewFind(context, found);
  5000.     unfoldedForFind = f->unfoldedForFind;
  5001.     f->unfoldedForFind = FALSE; /* Don't confuse BM_SelectItem */
  5002.     BM_SelectItem(context, found, TRUE, FALSE, TRUE);
  5003.     bm_flush_updates(context);
  5004.     BMFE_ScrollIntoView(context, found);
  5005.     f->unfoldedForFind = unfoldedForFind;
  5006.   } else {
  5007.     FE_Alert(context, XP_GetString(XP_BKMKS_NOT_FOUND));
  5008.   }
  5009.   findInfo->lastEntry = found;
  5010. }
  5011.  
  5012.  
  5013. static void
  5014. bm_parse_mailto(const char* url, char** name, char** addr)
  5015. {
  5016.   char* ptr;
  5017.   char* buf;
  5018.   int32 L;
  5019.   if (name) *name = NULL;
  5020.   if (addr) *addr = NULL;
  5021.   if (strncasecomp(url, "mailto:?to=", 11) != 0) return;
  5022.   url += 11;
  5023.   ptr = XP_STRCHR(url, '&');
  5024.   L = (ptr ? (ptr - url) : XP_STRLEN(url));
  5025.   buf = (char *) XP_ALLOC(L+1);
  5026.   if (!buf) return;
  5027.   XP_MEMCPY (buf, url, L);
  5028.   buf[L] = 0;
  5029.   buf = NET_UnEscape(buf);
  5030.  
  5031. #if 0
  5032.   MSG_ParseRFC822Addresses(buf, name, addr);
  5033. #else
  5034.   /* We need to do it this way to get msg_quote_phrase_or_addr() to be
  5035.      called on the names.  Perhaps that function should just be exported...
  5036.    */
  5037. #ifdef MOZ_MAIL_NEWS
  5038.   if (name) *name = MSG_ExtractRFC822AddressNames (buf);
  5039.   if (addr) *addr = MSG_ExtractRFC822AddressMailboxes (buf);
  5040. #endif /* MOZ_MAIL_NEWS */
  5041. #endif
  5042.  
  5043.   XP_FREE(buf);
  5044. }
  5045.  
  5046. BM_Entry*
  5047. BM_FindAddress(MWContext* context, const char* url)
  5048. {
  5049.   char* address;
  5050.   BM_Entry* result = NULL;
  5051.   BM_Entry* entry;
  5052.   CHKCONTEXT(context);
  5053.   XP_ASSERT(url);
  5054.   if (!url) return NULL;
  5055.   bm_parse_mailto(url, NULL, &address);
  5056.   if (!address) return NULL;
  5057.  
  5058.   /* Takes advantage of the fact that addressbook always has all address
  5059.      entries as children of the root header. */
  5060.   for (entry = BM_GetRoot(context)->d.header.children;
  5061.        entry;
  5062.        entry = entry->next) {
  5063.     if (BM_ISADDRESS(entry) &&
  5064.         XP_STRCMP(entry->d.address.address, address) == 0) {
  5065.       result = entry;
  5066.       break;
  5067.     }
  5068.   }
  5069.   XP_FREE(address);
  5070.   return result;
  5071. }
  5072.  
  5073.  
  5074. void
  5075. BM_EditAddress(MWContext* context, const char* url)
  5076. {
  5077.   BM_Entry* entry;
  5078.   CHKCONTEXTVOID(context);
  5079.   XP_ASSERT(url);
  5080.   if (!url) return;
  5081.   bm_start_batch(context);
  5082.   entry = BM_FindAddress(context, url);
  5083.   if (!entry) {
  5084.     char* name;
  5085.     char* address;
  5086.     bm_parse_mailto(url, &name, &address); /* Parsing a second time.  Oh,
  5087.                                               well. ### */
  5088.     if (!name) name = XP_STRDUP("");
  5089.     if (!address) address = XP_STRDUP("");
  5090.     if (!name || !address) goto FAIL;
  5091.  
  5092.     entry = bm_NewAddress(name, address);
  5093.     XP_FREE(name);
  5094.     XP_FREE(address);
  5095.     if (!entry) goto FAIL;
  5096.     BM_SETFLAG(entry, BM_ATTR_ISNEW);
  5097.     bm_AddChildToHeaderSorted(context, BM_GetRoot(context), entry);
  5098.     BM_SelectItem(context, entry, TRUE, FALSE, TRUE);
  5099.     bm_SyncCount(context);
  5100.     bm_refresh(context, 1, BM_LAST_CELL);
  5101.   }
  5102.   BMFE_OpenBookmarksWindow(context);
  5103.   BMFE_EditItem(context, entry);
  5104. FAIL:
  5105.   bm_end_batch(context);
  5106. }
  5107.  
  5108.  
  5109.  
  5110.  
  5111. /*
  5112.  * Utilities to fuss with bookmarks in a drag and drop evnironment
  5113.  *
  5114.  * There are two user visible functions in this file:
  5115.  *
  5116.  * Allocate and return a string that contains the text representation of
  5117.  *   a list of bookmarks entries (including headers and their contents).
  5118.  * The caller is responsible for freeing the string.  The total length of
  5119.  *   the block that was allocated is returned in lTotalLen.  List is the
  5120.  *   list of pointers to bookmarks items that are selected, iCount is the
  5121.  *   length of that list.
  5122.  * This function has two modes of operation, a short mode and a long mode.
  5123.  *   If bLongFormat == FALSE the returned block just has URLs separated by
  5124.  *   \n's.  If bLongFormat == TRUE all of the information needed to recreate
  5125.  *   the bookmarks item is included
  5126.  *
  5127.  
  5128. PUBLIC char *
  5129. BM_ConvertSelectionsToBlock(BM_Entry ** list,
  5130.                              int iCount,
  5131.                              int bLongFormat,
  5132.                              int32 * lTotalLen);
  5133.  *
  5134.  * ------------------------
  5135.  *
  5136.  * Take a block of memory formatted by BM_ConvertSelectionsToBlock and insert
  5137.  *   the items it represents into the bookmarks following 'item'.  If item is
  5138.  *   NULL insert at the beginning of the bookmarks.  bLongFormat has the same
  5139.  *   meaning as in BM_ConvertSelectionsToBlock().  lTotalLen should be the
  5140.  *   length of the block of memory --- I'm not sure if this is necessary
  5141.  *   because on Windows at least the value we get back is meaningless, so
  5142.  *   this function just ignores it.
  5143.  *
  5144. PUBLIC void
  5145. BM_InsertBlockAt(char * pOriginalBlock,
  5146.                   BM_Entry * item,
  5147.                   int bLongFormat,
  5148.                   int32 lTotalLen);
  5149.  *
  5150.  */
  5151.  
  5152.  
  5153.  
  5154. #define TEXT_INDENT 3
  5155.  
  5156. /*
  5157.  * Measure a boring URL bookmarks entry and return the length in bytes
  5158.  *
  5159.  * Short format:
  5160.  *  "     item->address\n"
  5161.  *    where there are nIndent number of spaces before the item
  5162.  *
  5163.  * Long format:
  5164.  *  uint16        type
  5165.  *  char        item->name\n
  5166.  *  char        item->address\n
  5167.  *  time_t        addition_date
  5168.  *  time_t        last_visit_date
  5169.  *  time_t        last_modified_date
  5170.  *  char        item->description\0
  5171.  *
  5172.  * The item->description field is *NOT* \n terminated since it might
  5173.  *  be a multi-line string and is therefore \0 terminated
  5174.  */
  5175. PRIVATE int32
  5176. bm_measure_URL(BM_Entry* item, XP_Bool bLongFormat, int nIndent)
  5177. {
  5178.     int32        iSpace = 0;
  5179.  
  5180.     if (!item)
  5181.         return 0;
  5182.  
  5183.     XP_ASSERT(BM_ISURL(item));
  5184.  
  5185.     /* NO. We cannot check for ISSELECTED here. We could be called
  5186.      * by bm_measure_Header which was selected although we by
  5187.      * ourselves are not selected.
  5188.      *
  5189.      * if (! BM_ISSELECTED(item))
  5190.      *    return 0;
  5191.      */
  5192.  
  5193.     if (bLongFormat)
  5194.     {
  5195.         iSpace +=     sizeof(item->type) +
  5196.                     sizeof(item->addition_date) +
  5197.                     sizeof(item->d.url.last_visit) +
  5198.                       sizeof(item->d.url.last_modified);
  5199.  
  5200.         if (item->name)
  5201.             iSpace += XP_STRLEN(item->name);
  5202.         iSpace++;    /* +1 for '\n' */
  5203.         if (item->description)
  5204.             iSpace += XP_STRLEN(item->description);
  5205.         iSpace++;    /* +1 for '\0' */
  5206.     }
  5207.     else
  5208.     {
  5209.         /* space indentation and '\n' terminator */
  5210.         iSpace = nIndent;
  5211.     }
  5212.  
  5213.     /* the address appears in both formats */
  5214.     if (item->d.url.address)
  5215.         iSpace += (XP_STRLEN(item->d.url.address) + 1); /* +1 for '\n' */
  5216.  
  5217.     return (iSpace);
  5218. }
  5219.  
  5220. PRIVATE int32
  5221. bm_measure_Alias(BM_Entry* item, XP_Bool bLongFormat, int nIndent)
  5222. {
  5223.     int32        iSpace = 0;
  5224.  
  5225.     XP_ASSERT(BM_ISALIAS(item));
  5226.  
  5227.     if (bLongFormat)
  5228.     {
  5229.         iSpace += sizeof(item->type);
  5230.     }
  5231.     return (iSpace);
  5232. }
  5233.  
  5234. /*
  5235.  * Measure a separator entry and return the length in bytes
  5236.  *
  5237.  * Short format:
  5238.  *  "     -------------\0"
  5239.  *    where there are nIndent number of spaces before the 13 -'s
  5240.  *
  5241.  * Long format:
  5242.  *  uint16        type
  5243.  *
  5244.  */
  5245. PRIVATE int32
  5246. bm_measure_Separator(BM_Entry* item, XP_Bool bLongFormat, int nIndent)
  5247. {
  5248.     int32        iSpace = 0;
  5249.  
  5250.     if (!item)
  5251.         return 0;
  5252.  
  5253.     XP_ASSERT(BM_ISSEPARATOR(item));
  5254.  
  5255.     if (bLongFormat)
  5256.     {
  5257.         iSpace +=     sizeof(item->type);
  5258.     }
  5259.     else
  5260.     {
  5261.         /* space indentation and '\n' terminator */
  5262.         iSpace = nIndent;
  5263.         iSpace += 13;
  5264.         iSpace ++ /* for '\n' */;
  5265.     }
  5266.  
  5267.     return (iSpace);
  5268. }
  5269.  
  5270. /*
  5271.  * Measure a header entry and all its children
  5272.  *
  5273.  * Short format:
  5274.  *  "     item->name\n"
  5275.  *  "        child1->address\n"
  5276.  *  "        child2->address\n"
  5277.  *    where there are nIndent number of spaces before the item and
  5278.  *    TEXT_INDENT spaces between levels
  5279.  *
  5280.  * Long format:
  5281.  *  uint16        type
  5282.  *  char        item->name\n
  5283.  *  time_t        addition_date
  5284.  *  uint32        number of children
  5285.  *  char        item->description\0
  5286.  *
  5287.  * The item->description field is *NOT* \n terminated since it might
  5288.  *  be a multi-line string and is therefore \0 terminated.  Note that
  5289.  *  the address field is *NOT* written for headers since its it meaningless
  5290.  */
  5291. PRIVATE int32
  5292. bm_measure_Header(BM_Entry* item, XP_Bool bLongFormat, int nIndent)
  5293. {
  5294.     int32        iSpace = 0;
  5295.     BM_Entry*    child = NULL;
  5296.  
  5297.     if (!item)
  5298.         return 0;
  5299.  
  5300.     XP_ASSERT(BM_ISHEADER(item));
  5301.  
  5302.     /* if the header is selected, count it as well */
  5303.     if (bLongFormat)
  5304.     {
  5305.         iSpace +=     sizeof(item->type) +
  5306.                     sizeof(item->addition_date) +
  5307.                     sizeof(item->d.header.childCount);
  5308.  
  5309.         if (item->description)
  5310.             iSpace += XP_STRLEN(item->description);
  5311.         iSpace++;    /* for \0 */
  5312.     }
  5313.     else
  5314.     {
  5315.         /* space indentation and '\n' terminator */
  5316.         iSpace = nIndent;
  5317.     }
  5318.  
  5319.     /* the name appears in both formats */
  5320.     if (item->name)
  5321.         iSpace += XP_STRLEN(item->name);
  5322.     iSpace ++;    /* for \n terminator */
  5323.  
  5324.     /* measure the amount of space taken up by this item's children */
  5325.     child = item->d.header.children;
  5326.     while (child)
  5327.     {
  5328.         switch (child->type)
  5329.         {
  5330.             case BM_TYPE_URL:
  5331.                 iSpace += bm_measure_URL(child, bLongFormat, nIndent + TEXT_INDENT);
  5332.             break;
  5333.             case BM_TYPE_ALIAS:
  5334.                 iSpace += bm_measure_Alias(child, bLongFormat, nIndent + TEXT_INDENT);
  5335.             break;
  5336.             case BM_TYPE_HEADER:
  5337.                 iSpace += bm_measure_Header(child, bLongFormat, nIndent + TEXT_INDENT);
  5338.             break;
  5339.             case BM_TYPE_SEPARATOR:
  5340.                 iSpace += bm_measure_Separator(child, bLongFormat, nIndent + TEXT_INDENT);
  5341.             break;
  5342.             default:
  5343.             break;
  5344.         }
  5345.         child = child->next;
  5346.     }
  5347.  
  5348.     return iSpace;
  5349. }
  5350.  
  5351. /*
  5352.  * Write out a separator bookmarks entry.
  5353.  */
  5354. PRIVATE char*
  5355. bm_write_Separator(char* buffer, BM_Entry* item, XP_Bool bLongFormat, int nIndent)
  5356. {
  5357.     int32                iLen;
  5358.     BM_Type                type;
  5359.  
  5360.     if (!item || !buffer)
  5361.         return buffer;
  5362.  
  5363.     XP_ASSERT(BM_ISSEPARATOR(item));
  5364.  
  5365.     if (bLongFormat)
  5366.     {
  5367.         /* copy the type */
  5368.         type = item->type;
  5369.         iLen = sizeof(BM_Type);
  5370.         XP_MEMCPY(buffer, &type, iLen);
  5371.         buffer += iLen;
  5372.     }
  5373.     else
  5374.     {
  5375.         XP_MEMSET(buffer, ' ', nIndent);
  5376.         buffer += nIndent;
  5377.         XP_MEMSET(buffer, '-', 13);
  5378.         buffer += 13;
  5379.         *buffer++ = '\0';
  5380.     }
  5381.  
  5382.     return buffer;
  5383. }
  5384.  
  5385. /*
  5386.  * Write out a boring URL bookmarks entry.  See comment at the top of
  5387.  *   bm_measure_URL for the format used.  Assume we start writing at
  5388.  *   the start of the buffer passed in.  Return a pointer to where the
  5389.  *   buffer ends when we get done.
  5390.  */
  5391. PRIVATE char*
  5392. bm_write_URL(char* buffer, BM_Entry* item, XP_Bool bLongFormat, int nIndent)
  5393. {
  5394.     int32                iLen;
  5395.     BM_Date                lVal;
  5396.     BM_Type                type;
  5397.  
  5398.     if (!item || !buffer)
  5399.         return buffer;
  5400.  
  5401.     XP_ASSERT(BM_ISURL(item) || BM_ISALIAS(item));
  5402.  
  5403.     if (bLongFormat)
  5404.     {
  5405.         /* copy the type */
  5406.         type = item->type;
  5407.         iLen = sizeof(BM_Type);
  5408.         XP_MEMCPY(buffer, &type, iLen);
  5409.         buffer += iLen;
  5410.  
  5411.         if (BM_ISALIAS(item))
  5412.             return buffer;
  5413.  
  5414.         /* copy the name */
  5415.         if (item->name)
  5416.         {
  5417.             iLen = XP_STRLEN(item->name);
  5418.             XP_MEMCPY(buffer, item->name, iLen);
  5419.             buffer += iLen;
  5420.         }
  5421.         /* put the \n terminator on */
  5422.         *buffer++ = '\n';
  5423.  
  5424.         /* copy the address */
  5425.         if (item->d.url.address)
  5426.         {
  5427.             iLen = XP_STRLEN(item->d.url.address);
  5428.             XP_MEMCPY(buffer, item->d.url.address, iLen);
  5429.             buffer += iLen;
  5430.         }
  5431.         /* put the \n terminator on */
  5432.         *buffer++ = '\n';
  5433.  
  5434.         /* addition date */
  5435.         lVal = item->addition_date;
  5436.         iLen = sizeof(BM_Date);
  5437.         XP_MEMCPY(buffer, &lVal, iLen);
  5438.         buffer += iLen;
  5439.  
  5440.         /* last visit date */
  5441.         lVal = item->d.url.last_visit;
  5442.         iLen = sizeof(BM_Date);
  5443.         XP_MEMCPY(buffer, &lVal, iLen);
  5444.         buffer += iLen;
  5445.  
  5446.         /* last modified date */
  5447.         lVal = item->d.url.last_modified;
  5448.         iLen = sizeof(BM_Date);
  5449.         XP_MEMCPY(buffer, &lVal, iLen);
  5450.         buffer += iLen;
  5451.  
  5452.         /* copy the description */
  5453.         if (item->description)
  5454.         {
  5455.             iLen = XP_STRLEN(item->description);
  5456.             XP_MEMCPY(buffer, item->description, iLen);
  5457.             buffer += iLen;
  5458.         }
  5459.         /* put the \n terminator on */
  5460.         *buffer++ = '\0';
  5461.  
  5462.     }
  5463.     else if (BM_ISURL(item))
  5464.     {
  5465.         XP_MEMSET(buffer, ' ', nIndent);
  5466.         buffer += nIndent;
  5467.  
  5468.         if(item->d.url.address)
  5469.         {
  5470.             XP_STRCPY(buffer, item->d.url.address);
  5471.             buffer += XP_STRLEN(item->d.url.address);
  5472.         }
  5473.         *buffer++ = '\n';
  5474.     }
  5475.  
  5476.     return buffer;
  5477. }
  5478.  
  5479.  
  5480. /*
  5481.  * Write out a bookmarks header entry.  See comment at the top of
  5482.  *   bm_measure_Header for the format used.  Assume we start writing at
  5483.  *   the start of the buffer passed in.  Return a pointer to where the
  5484.  *   buffer ends when we get done.
  5485.  */
  5486. PRIVATE char*
  5487. bm_write_Header(char* buffer, BM_Entry* item, XP_Bool bLongFormat, int nIndent)
  5488. {
  5489.     long            iLen;
  5490.     BM_Date            lVal;
  5491.     BM_Type            type;
  5492.     uint32            children;
  5493.     BM_Entry*        child = NULL;
  5494.  
  5495.     if (!item || !buffer)
  5496.         return buffer;
  5497.  
  5498.     XP_ASSERT(BM_ISHEADER(item));
  5499.  
  5500.     if (bLongFormat)
  5501.     {
  5502.         /* copy the type */
  5503.         type = item->type;
  5504.         iLen = sizeof(BM_Type);
  5505.         XP_MEMCPY(buffer, &type, iLen);
  5506.         buffer += iLen;
  5507.  
  5508.         /* copy the name */
  5509.         if (item->name)
  5510.         {
  5511.             iLen = XP_STRLEN(item->name);
  5512.             XP_MEMCPY(buffer, item->name, iLen);
  5513.             buffer += iLen;
  5514.         }
  5515.         /* put the \n terminator on */
  5516.         *buffer++ = '\n';
  5517.  
  5518.         /* addition date */
  5519.         lVal = item->addition_date;
  5520.         iLen = sizeof(BM_Date);
  5521.         XP_MEMCPY(buffer, &lVal, iLen);
  5522.         buffer += iLen;
  5523.  
  5524.         /* number of children */
  5525.         children = item->d.header.childCount;
  5526.         iLen = sizeof(uint32);
  5527.         XP_MEMCPY(buffer, &children, iLen);
  5528.         buffer += iLen;
  5529.  
  5530.         /* copy the description */
  5531.         if (item->description)
  5532.         {
  5533.             iLen = XP_STRLEN(item->description);
  5534.             XP_MEMCPY(buffer, item->description, iLen);
  5535.             buffer += iLen;
  5536.         }
  5537.         /* put the \n terminator on */
  5538.         *buffer++ = '\0';
  5539.  
  5540.     }
  5541.     else
  5542.     {
  5543.         XP_MEMSET(buffer, ' ', nIndent);
  5544.         buffer += nIndent;
  5545.         if(item->name)
  5546.         {
  5547.             XP_STRCPY(buffer, item->name);
  5548.             buffer += XP_STRLEN(item->name);
  5549.         }
  5550.         *buffer++ = '\n';
  5551.     }
  5552.  
  5553.     child = item->d.header.children;
  5554.     while (child)
  5555.     {
  5556.         switch (child->type)
  5557.         {
  5558.             case BM_TYPE_URL:
  5559.             case BM_TYPE_ALIAS:
  5560.                 buffer = bm_write_URL(buffer, child, bLongFormat, nIndent + TEXT_INDENT);
  5561.             break;
  5562.             case BM_TYPE_HEADER:
  5563.                 buffer = bm_write_Header(buffer, child, bLongFormat, nIndent + TEXT_INDENT);
  5564.             break;
  5565.             case BM_TYPE_SEPARATOR:
  5566.                 buffer = bm_write_Separator(buffer, child, bLongFormat, nIndent + TEXT_INDENT);
  5567.             break;
  5568.             default:
  5569.             break;
  5570.         }
  5571.         child = child->next;
  5572.     }
  5573.  
  5574.     return buffer;
  5575. }
  5576.  
  5577. /*
  5578.  * Take a separator packed in a block the way bm_write_Separator packs it.
  5579.  * Return the new item if we created one
  5580.  */
  5581. PRIVATE BM_Entry*
  5582. bm_read_Separator(char* buffer, XP_Bool bLongFormat, int32* lBytesEaten)
  5583. {
  5584.     BM_Entry* new_item = NULL;
  5585.  
  5586.     if (!buffer)
  5587.         return NULL;
  5588.  
  5589.     if (bLongFormat)
  5590.     {
  5591.         /* for now the separator written has only the type.
  5592.            since that was already read in before this was clled
  5593.            we have nothing to eat here */
  5594.         new_item = bm_NewSeparator();
  5595.         *lBytesEaten = 0;
  5596.     }
  5597.     else
  5598.     {
  5599.         /* we should really strip leading whitespace */
  5600.         new_item = bm_NewSeparator();
  5601.         *lBytesEaten = XP_STRLEN(buffer) + 1;
  5602.     }
  5603.  
  5604.     return new_item;
  5605. }
  5606.  
  5607. /*
  5608.  * Take a URL packed in a block the way bm_write_URL packs it.
  5609.  * Return the new item if we created one
  5610.  */
  5611. PRIVATE BM_Entry*
  5612. bm_read_url_long(char* buffer, XP_Bool bLongFormat, int32* lBytesEaten)
  5613. {
  5614.     BM_Entry* new_item = NULL;
  5615.  
  5616.     if (!buffer)
  5617.         return NULL;
  5618.  
  5619.     if (bLongFormat)
  5620.     {
  5621.         BM_Date        addition;
  5622.         BM_Date        visit;
  5623.         BM_Date        modified;
  5624.  
  5625.         /* get the name */
  5626.         char* name = buffer;
  5627.         char* address = strchr(name, '\n');
  5628.         char* description = NULL;
  5629.         char* ptr;
  5630.         if (!address)
  5631.             return NULL;
  5632.         *address++ = '\0';
  5633.  
  5634.         /* get the address */
  5635.         ptr = strchr(address, '\n');
  5636.         if(!ptr)
  5637.             return NULL;
  5638.         *ptr++ = '\0';
  5639.  
  5640.         /* addition date */
  5641.         XP_MEMCPY(&addition, ptr, sizeof(BM_Date));
  5642.         ptr += sizeof(BM_Date);
  5643.  
  5644.         /* visiting date */
  5645.         XP_MEMCPY(&visit, ptr, sizeof(BM_Date));
  5646.         ptr += sizeof(BM_Date);
  5647.  
  5648.         /* modified date */
  5649.         XP_MEMCPY(&modified, ptr, sizeof(BM_Date));
  5650.         ptr += sizeof(BM_Date);
  5651.  
  5652.         /* get the description (it should be NULL terminated) */
  5653.         description = ptr;
  5654.  
  5655.         /* we should really strip leading whitespace */
  5656.         new_item = BM_NewUrl(name, address, 0, visit);
  5657.         new_item->addition_date = addition;
  5658.         new_item->description = XP_STRDUP(description);
  5659.         new_item->d.url.last_modified = modified;
  5660.         *lBytesEaten = XP_STRLEN(description) + (description - buffer) + 1;
  5661.  
  5662.     }
  5663.     else
  5664.     {
  5665.         char* end = strchr(buffer, '\n');
  5666.  
  5667.         /* if there was a return NULL terminate the current string */
  5668.         if (end)
  5669.             *end++ = '\0';
  5670.  
  5671.         /* we should really strip leading whitespace */
  5672.         new_item = BM_NewUrl(buffer, buffer, 0, 0);
  5673.         new_item->addition_date = XP_TIME();
  5674.         *lBytesEaten = XP_STRLEN(buffer) + 1;
  5675.     }
  5676.  
  5677.     return new_item;
  5678. }
  5679.  
  5680. /*
  5681.  * Take a header and children packed in a block the way bm_write_Header
  5682.  * packs it.  Return the new header item if we created one
  5683.  */
  5684. PRIVATE BM_Entry*
  5685. bm_read_header_long(MWContext* context, char* buffer, XP_Bool bLongFormat,
  5686.                int32* lBytesEaten)
  5687. {
  5688.     uint32            kids = 0;
  5689.     BM_Type            type;
  5690.     BM_Entry*        new_item = NULL;
  5691.     BM_Entry*        kid = NULL;
  5692.     char*            name = NULL;
  5693.     char*            ptr = NULL;
  5694.     char*            description = NULL;
  5695.     BM_Date            addition;
  5696.     uint32            i;
  5697.     int32            lEat;
  5698.  
  5699.     if (!buffer)
  5700.         return NULL;
  5701.  
  5702.     /* can only read long format headers */
  5703.     if (bLongFormat)
  5704.     {
  5705.         /* get the name */
  5706.         name = buffer;
  5707.         ptr = strchr(name, '\n');
  5708.         description = NULL;
  5709.         if (!ptr)
  5710.             return NULL;
  5711.  
  5712.         /* skip over the \n but change it to a \0 so strcpy() will work */
  5713.         *ptr++ = '\0';
  5714.  
  5715.         /* addition date */
  5716.         XP_MEMCPY(&addition, ptr, sizeof(BM_Date));
  5717.         ptr += sizeof(BM_Date);
  5718.  
  5719.         /* number of children to read */
  5720.         XP_MEMCPY(&kids, ptr, sizeof(uint32));
  5721.         ptr += sizeof(uint32);
  5722.  
  5723.         /* get the description (it should be NULL terminated) */
  5724.         description = ptr;
  5725.  
  5726.         /* we should really strip leading whitespace */
  5727.         new_item = BM_NewHeader(name);
  5728.         new_item->addition_date = addition;
  5729.         new_item->description = XP_STRDUP(description);
  5730.         *lBytesEaten = XP_STRLEN(description) + (description - buffer) + 1;
  5731.  
  5732.         /* handle all of the kids now */
  5733.         if (kids)
  5734.         {
  5735.             buffer += *lBytesEaten;
  5736.  
  5737.             for (i = 0; i < kids; i++)
  5738.             {
  5739.                 /* determine the type of the next entry */
  5740.                 XP_MEMCPY(&type, buffer, sizeof(BM_Type));
  5741.                 buffer += sizeof(BM_Type);
  5742.                 *lBytesEaten += sizeof(BM_Type);
  5743.  
  5744.                 switch (type)
  5745.                 {
  5746.                     case BM_TYPE_URL:
  5747.                         kid = bm_read_url_long(buffer, bLongFormat, &lEat);
  5748.                         *lBytesEaten += lEat;
  5749.                         buffer += lEat;
  5750.                         bm_AppendChildToHeader(context, new_item, kid);
  5751.                     break;
  5752.                     case BM_TYPE_ALIAS:
  5753.                     break;
  5754.                     case BM_TYPE_HEADER:
  5755.                         kid = bm_read_header_long(context, buffer, bLongFormat, &lEat);
  5756.                         *lBytesEaten += lEat;
  5757.                         buffer += lEat;
  5758.                         bm_AppendChildToHeader(context, new_item, kid);
  5759.                     break;
  5760.                     case BM_TYPE_SEPARATOR:
  5761.                         kid = bm_read_Separator(buffer, bLongFormat, &lEat);
  5762.                         *lBytesEaten += lEat;
  5763.                         buffer += lEat;
  5764.                         bm_AppendChildToHeader(context, new_item, kid);
  5765.                     break;
  5766.                     case 12345:
  5767.                         /* Ah ha! this is the end marker we wrote. Something
  5768.                            terribly wrong here. We shouldn't have hit this
  5769.                            before we read all the kids in. */
  5770.                         abort();
  5771.                     break;
  5772.                     default:
  5773.                     /* bogus type.  Who knows whats going on.  Just quit and get out */
  5774.                     break;
  5775.                 }
  5776.  
  5777.             }
  5778.  
  5779.         }
  5780.     }
  5781.  
  5782.     return new_item;
  5783. }
  5784.  
  5785. PRIVATE int32
  5786. bm_measure(BM_Entry* root, XP_Bool bLongFormat, int32 indent)
  5787. {
  5788.     int32        iSpace = 0;
  5789.     BM_Entry*    child;
  5790.  
  5791.     XP_ASSERT(root);
  5792.     XP_ASSERT(BM_ISHEADER(root));
  5793.  
  5794.     child = root->d.header.children;
  5795.  
  5796.     while (child)
  5797.     {
  5798.         switch (child->type)
  5799.         {
  5800.             case BM_TYPE_URL:
  5801.                 if (BM_ISSELECTED(child))
  5802.                     iSpace += bm_measure_URL(child, bLongFormat, indent);
  5803.             break;
  5804.             case BM_TYPE_ALIAS:
  5805.                 iSpace += bm_measure_Alias(child, bLongFormat, indent + TEXT_INDENT);
  5806.             break;
  5807.             case BM_TYPE_HEADER:
  5808.                 if (BM_ISSELECTED(child))
  5809.                     iSpace += bm_measure_Header(child, bLongFormat, indent);
  5810.                 else
  5811.                     if (! BM_ISFOLDED(child))
  5812.                         iSpace += bm_measure(child, bLongFormat, indent);
  5813.             break;
  5814.             case BM_TYPE_SEPARATOR:
  5815.                 if (BM_ISSELECTED(child))
  5816.                     iSpace += bm_measure_Separator(child, bLongFormat, indent);
  5817.             break;
  5818.         }
  5819.         child = child->next;
  5820.     }
  5821.     return iSpace;
  5822. }
  5823.  
  5824. PRIVATE char*
  5825. bm_write(char* buffer, BM_Entry* root, XP_Bool bLongFormat, int32 indent)
  5826. {
  5827.     BM_Entry*    child;
  5828.  
  5829.     XP_ASSERT(root);
  5830.     XP_ASSERT(BM_ISHEADER(root));
  5831.  
  5832.     child = root->d.header.children;
  5833.  
  5834.     while (child)
  5835.     {
  5836.         switch (child->type)
  5837.         {
  5838.             case BM_TYPE_URL:
  5839.             case BM_TYPE_ALIAS:
  5840.                 if (BM_ISSELECTED(child))
  5841.                     buffer = bm_write_URL(buffer, child, bLongFormat, indent);
  5842.             break;
  5843.  
  5844.             case BM_TYPE_HEADER:
  5845.                 if (BM_ISSELECTED(child))
  5846.                     buffer = bm_write_Header(buffer, child, bLongFormat, indent);
  5847.                 else
  5848.                     if (! BM_ISFOLDED(child))
  5849.                         buffer = bm_write(buffer, child, bLongFormat, indent);
  5850.             break;
  5851.             case BM_TYPE_SEPARATOR:
  5852.                 if (BM_ISSELECTED(child))
  5853.                     buffer = bm_write_Separator(buffer, child, bLongFormat, indent);
  5854.             break;
  5855.         }
  5856.         child = child->next;
  5857.     }
  5858.     return buffer;
  5859. }
  5860.  
  5861. /*
  5862.  * Allocate and return a string that contains the text representation of
  5863.  *   a list of bookmarks entries from specified selections in a History window.
  5864.  * The caller is responsible for freeing the string
  5865.  */
  5866. PUBLIC char *
  5867. BM_ClipCopyHistorySelection( void *pHistCsr, uint32 *pSelections, int iCount, int *pSize, XP_Bool bLongFormat )
  5868. {
  5869.    int i, iLen, iSize = 0;
  5870.    uint16 marker;
  5871.    char *      pStorage = NULL;
  5872.    char *      pStgCsr  = NULL;
  5873.    BM_Entry *  pBM      = NULL;
  5874.    BM_Entry *  pPrevBM  = NULL;   
  5875.    BM_Entry *  pCsr     = NULL;      
  5876.    BM_Entry *  pBMList  = NULL;         
  5877.    
  5878.    if( !pHistCsr || !pSelections || iCount <= 0 )
  5879.    {
  5880.       return NULL;
  5881.    }
  5882.    
  5883.    /*
  5884.     * Build the list of BM_Entrys and calc the total size
  5885.     */
  5886.    for( i = 0; i < iCount; i++ )
  5887.    {
  5888.       gh_HistEntry *pHistEntry = GH_GetRecord( pHistCsr, pSelections[i] );
  5889.       if( !pHistEntry )
  5890.       {
  5891.          /* In case history file was somehow compromised */
  5892.          continue;
  5893.       }
  5894.  
  5895.       /*
  5896.        * Map the gh_HistEntry to the BM_Entry
  5897.        */
  5898.             
  5899.       pBM = (BM_Entry *)XP_ALLOC( sizeof(BM_Entry) );
  5900.       XP_MEMSET( pBM, 0, sizeof(BM_Entry) );
  5901.       if( !pPrevBM )
  5902.       {
  5903.          pBMList = pBM;
  5904.       }
  5905.       else
  5906.       {
  5907.          pPrevBM->next = pBM;
  5908.       }
  5909.       pPrevBM = pBM;
  5910.  
  5911.       /* type */
  5912.       pBM->type = BM_TYPE_URL;
  5913.  
  5914.       if( bLongFormat )
  5915.       {      
  5916.          /* name */
  5917.          iLen = pHistEntry->pszName ? XP_STRLEN( pHistEntry->pszName )+1 : 0;
  5918.          pBM->name = iLen ? (char *)XP_ALLOC( iLen*sizeof(char) ) : NULL;
  5919.          if( iLen )
  5920.          {
  5921.             XP_STRCPY( pBM->name, pHistEntry->pszName );
  5922.          }
  5923.          
  5924.          /* addition date */
  5925.          time( &pBM->addition_date );
  5926.  
  5927.          /* last visit date */
  5928.          pBM->d.url.last_visit = pHistEntry->last_accessed;
  5929.          
  5930.          /* last modified */
  5931.          time( &pBM->d.url.last_modified );
  5932.  
  5933.          /* description */
  5934.          pBM->description = NULL;
  5935.       }
  5936.       
  5937.       /* address */
  5938.       iLen = pHistEntry->address ? XP_STRLEN( pHistEntry->address )+1 : 0;
  5939.       pBM->d.url.address = iLen ? (char *)XP_ALLOC( iLen*sizeof(char) ) : NULL;
  5940.       if( iLen )
  5941.       {
  5942.          XP_STRCPY( pBM->d.url.address, pHistEntry->address );
  5943.       }
  5944.       
  5945.       
  5946.       /*
  5947.        * Calc the size
  5948.        */
  5949.       
  5950.       iSize += bm_measure_URL( pBM, bLongFormat, 0 );
  5951.    }
  5952.    
  5953.    if( bLongFormat )
  5954.    {
  5955.       iSize += sizeof(uint16);
  5956.    }
  5957.    
  5958.    /* Leave room for the termination character */
  5959.    iSize++;
  5960.  
  5961.   #ifdef XP_WIN16
  5962.    if( (uint16)iSize > (uint16)(32*1024-1) )
  5963.    {
  5964.       return NULL;
  5965.    }
  5966.   #endif
  5967.  
  5968.    /* Allocate the storage */
  5969.    pStorage = pStgCsr = (char *)XP_ALLOC( iSize*sizeof(char) );
  5970.    if( !pStorage )
  5971.    {
  5972.       return NULL;
  5973.    }
  5974.    
  5975.    /* Copy the bookmarks to the storage */
  5976.    pCsr = pBMList;
  5977.    while( pCsr )
  5978.    {
  5979.       pStgCsr = bm_write_URL( pStgCsr, pCsr, bLongFormat, 0 );
  5980.       
  5981.       pPrevBM = pCsr;
  5982.       
  5983.       pCsr = pCsr->next;
  5984.    
  5985.       /* Free the entry */
  5986.       pPrevBM->next = NULL;
  5987.       bm_ShallowFreeEntry( pPrevBM );
  5988.    }
  5989.  
  5990.    if (bLongFormat)
  5991.    {
  5992.       /* Write the end-of-list marker */
  5993.       marker = 12345;
  5994.       XP_MEMCPY( pStgCsr, &marker, 2 );
  5995.       pStgCsr += sizeof(uint16);
  5996.    }
  5997.    
  5998.    /* End the string */
  5999.    *pStgCsr++ = '\0';
  6000.    *pSize = (pStgCsr - pStorage);
  6001.    
  6002.    return pStorage;
  6003. }
  6004.  
  6005.  
  6006. /*
  6007.  * Allocate and return a string that contains the text representation of
  6008.  *   a list of bookmarks entries (including headers and their contents).
  6009.  * The caller is responsible for freeing the string
  6010.  */
  6011. PUBLIC char*
  6012. BM_ConvertSelectionsToBlock(MWContext* context,
  6013.     XP_Bool bLongFormat,
  6014.     int32* lTotalLen)
  6015. {
  6016.     uint16            marker;
  6017.     int32            iSpace = 0;
  6018.     char*            pString;
  6019.     char*            pOriginal;
  6020.  
  6021.     BM_Entry*        tmp;
  6022.  
  6023.     CHKCONTEXT(context);
  6024.  
  6025.     tmp = BM_GetRoot(context);
  6026.     iSpace = bm_measure(tmp, bLongFormat, 0);
  6027.  
  6028.     /* leave room for end of list marker */
  6029.     if (bLongFormat)
  6030.         iSpace += sizeof(uint16);
  6031.  
  6032.     /* leave room for the termination character */
  6033.     iSpace++;
  6034.  
  6035. #ifdef XP_WIN16
  6036.     if (iSpace > 32000)
  6037.         return NULL;
  6038. #endif
  6039.  
  6040.     /* allocate the string */
  6041.     pOriginal = pString = (char*)XP_ALLOC(iSpace * sizeof(char));
  6042.     if (!pString)
  6043.         return NULL;
  6044.  
  6045.     /* Make a big string */
  6046.     pString = bm_write(pString, tmp, bLongFormat, 0);
  6047.  
  6048.     /* stick the end of list marker on so that when we are decoding this */
  6049.     /* block we know when we are done                                    */
  6050.     if (bLongFormat)
  6051.     {
  6052.         marker = 12345;
  6053.         XP_MEMCPY(pString, &marker, 2);
  6054.         pString += sizeof(uint16);
  6055.     }
  6056.  
  6057.     /* end the string and return the total length to our caller */
  6058.     *pString++ = '\0';
  6059.     *lTotalLen = (pString - pOriginal);
  6060.     return pOriginal;
  6061. }
  6062.  
  6063.  
  6064. /*
  6065.  * Take a block of memory formatted by BM_ConvertSelectionsToBlock and insert
  6066.  *   the items it represents into the bookmarks following 'item'.  If item is
  6067.  *   NULL insert at the beginning of the bookmarks.
  6068.  */
  6069. PUBLIC void
  6070. BM_InsertBlockAt(MWContext* context,
  6071.                  char* pOriginalBlock,
  6072.                  BM_Entry* addTo,
  6073.                  XP_Bool bLongFormat,
  6074.                  int32 lTotalLen)
  6075. {
  6076.   BM_Type type;
  6077.   int32 lBytesEaten = 0;   /* total number of bytes eaten */
  6078.   int32 lEat;              /* number of bytes eaten on this item */
  6079.   char* pCurrentPos;
  6080.   char* pBlock;
  6081.   BM_Entry*    tmp;
  6082.   BM_Entry*    item;
  6083.   XP_Bool first = TRUE;
  6084.  
  6085.   CHKCONTEXTVOID(context);
  6086.   if (!pOriginalBlock) return;
  6087.  
  6088.   if (addTo == NULL) addTo = BM_GetRoot(context);
  6089.  
  6090.   /* make a copy of the string we can write into */
  6091.   pCurrentPos = pBlock = (char*) XP_ALLOC(lTotalLen + 1);
  6092.   if (!pBlock) return;
  6093.  
  6094.   bm_start_batch(context);
  6095.   /* copy the data over and make sure we are NULL terminated to make life
  6096.      easier */
  6097.   XP_MEMCPY(pBlock, pOriginalBlock, lTotalLen);
  6098.   pBlock[lTotalLen] = '\0';
  6099.  
  6100.   /* long format can have all kinds of different types of things in it */
  6101.   if (bLongFormat) {
  6102.     while (lBytesEaten < lTotalLen) {
  6103.       /* determine the type of the next entry */
  6104.       XP_MEMCPY(&type, pCurrentPos, sizeof(BM_Type));
  6105.       pCurrentPos += sizeof(BM_Type);
  6106.       lBytesEaten += sizeof(BM_Type);
  6107.  
  6108.       item = NULL;
  6109.  
  6110.       switch (type) {
  6111.       case BM_TYPE_URL:
  6112.         item = bm_read_url_long(pCurrentPos, bLongFormat, &lEat);
  6113.         lBytesEaten += lEat;
  6114.         pCurrentPos += lEat;
  6115.         break;
  6116.  
  6117.       case BM_TYPE_ALIAS:
  6118.         break;
  6119.  
  6120.       case BM_TYPE_HEADER:
  6121.         item = bm_read_header_long(context, pCurrentPos, bLongFormat, &lEat);
  6122.         lBytesEaten += lEat;
  6123.         pCurrentPos += lEat;
  6124.         break;
  6125.  
  6126.       case BM_TYPE_SEPARATOR:
  6127.         item = bm_read_Separator(pCurrentPos, bLongFormat, &lEat);
  6128.         lBytesEaten += lEat;
  6129.         pCurrentPos += lEat;
  6130.         break;
  6131.  
  6132.       case 12345:
  6133.         /* Ah ha! this is the end marker we wrote! remember... */
  6134.         XP_ASSERT((lBytesEaten+1) == lTotalLen);
  6135.         break;
  6136.       default:
  6137.         /* bogus type.  Who knows whats going on.  Just quit and
  6138.            get out */
  6139.         goto GETOUT;
  6140.  
  6141.         break;
  6142.       }
  6143.       if (item) {
  6144.         if (first && BM_ISHEADER(addTo) && !BM_ISFOLDED(addTo)) {
  6145.           /* Adding inside a folder as first child. */
  6146.           BM_PrependChildToHeader(context, addTo, item);
  6147.         } else {
  6148.           bm_InsertItemAfter(context, addTo, item, FALSE);
  6149.         }
  6150.         addTo = item;
  6151.         first = FALSE;
  6152.       }
  6153.     }
  6154.   } else {
  6155.     item = NULL;
  6156.     /* short format is just a list of URLs separated by \n's */
  6157.     while (lBytesEaten < lTotalLen) {
  6158.       item = bm_read_url_long(pCurrentPos, bLongFormat, &lEat);
  6159.       lBytesEaten += lEat;
  6160.       pCurrentPos += lEat;
  6161.  
  6162.       if (item) {
  6163.         tmp = addTo->next;
  6164.         addTo->next = item;
  6165.         item->next = tmp;
  6166.         addTo = item;
  6167.       }
  6168.       /* if we just walked over a \0 we are done */
  6169.       if (pOriginalBlock[lBytesEaten - 1] == '\0') {
  6170.         lBytesEaten = lTotalLen;
  6171.       }
  6172.     }
  6173.   }
  6174.  
  6175. GETOUT:
  6176.   /* mark the bookmark list as changed and clean up */
  6177.   bm_SetModified(context, TRUE);
  6178.   XP_FREE(pBlock);
  6179.   bm_end_batch(context);
  6180. }
  6181.  
  6182.  
  6183.  
  6184.  
  6185. static void
  6186. bm_make_alias(MWContext* context, BM_Entry* entry, void* closure)
  6187. {
  6188.   BM_Entry*        newAlias;
  6189.   int32            index;
  6190.  
  6191.   CHKCONTEXTVOID(context);
  6192.  
  6193.   if (BM_ISURL(entry)) {
  6194.     newAlias = bm_NewAlias(entry);
  6195.     if (!newAlias) return;
  6196.  
  6197.     bm_InsertItemAfter(context, entry, newAlias, FALSE);
  6198.  
  6199.     index = BM_GetIndex(context, newAlias);
  6200.     if (index > 0) {
  6201.       bm_refresh(context, index, BM_LAST_CELL);
  6202.     }
  6203.   }
  6204. }
  6205.  
  6206. PUBLIC void
  6207. BM_MakeAliases(MWContext* context)
  6208. {
  6209.   CHKCONTEXTVOID(context);
  6210.   bm_start_batch(context);
  6211.   BM_EachSelectedEntryDo(context, bm_make_alias, NULL);
  6212.   bm_SyncCount(context);
  6213.   bm_end_batch(context);
  6214. }
  6215.  
  6216.  
  6217. BM_Entry*
  6218. BM_GetAliasOriginal(BM_Entry* entry)
  6219. {
  6220.   XP_ASSERT(BM_ISALIAS(entry));
  6221.   return BM_ISALIAS(entry) ? entry->d.alias.original : NULL;
  6222. }
  6223.  
  6224.  
  6225. static XP_Bool
  6226. bm_SelectionIsContiguous_1(MWContext* context, BM_Entry* entry,
  6227.                            XP_Bool* started, XP_Bool* finished)
  6228. {
  6229.   XP_Bool result;
  6230.   for (; entry ; entry = entry->next) {
  6231.     if (BM_ISSELECTED(entry)) {
  6232.       if (*finished) return FALSE;
  6233.       *started = TRUE;
  6234.     } else {
  6235.       if (*started) *finished = TRUE;
  6236.     }
  6237.     if (BM_ISHEADER(entry) && !BM_ISFOLDED(entry)) {
  6238.       result = bm_SelectionIsContiguous_1(context, entry->d.header.children,
  6239.                                           started, finished);
  6240.       if (!result) return result;
  6241.     }
  6242.   }
  6243.   return TRUE;
  6244. }
  6245.  
  6246. static XP_Bool
  6247. bm_SelectionIsContiguous(MWContext* context)
  6248. {
  6249.   BM_Frame* f = GETFRAME(context);
  6250.   XP_Bool started = FALSE;
  6251.   XP_Bool finished = FALSE;
  6252.   if (f->gSelectionCount < 0) bm_SyncSelection(context);
  6253.   if (f->gSelectionCount < 2) return TRUE;
  6254.   return bm_SelectionIsContiguous_1(context, BM_GetRoot(context),
  6255.                                     &started, &finished);
  6256. }
  6257.  
  6258. XP_Bool BM_FindCommandStatus(MWContext* context, BM_CommandType command)
  6259. {
  6260.   BM_Frame* f = GETFRAME(context);
  6261.   int32    length;
  6262.  
  6263.   CHKCONTEXT(context);
  6264.  
  6265.   if (f->gSelectionCount < 0) bm_SyncSelection(context);
  6266.  
  6267.   switch (command) {
  6268.   case BM_Cmd_Invalid:
  6269.     return FALSE;
  6270.  
  6271.   case BM_Cmd_Open: /**/
  6272.   case BM_Cmd_ImportBookmarks:
  6273.   case BM_Cmd_SaveAs: /**/
  6274.     return TRUE;
  6275.  
  6276.   case BM_Cmd_Close: /**/
  6277.     return TRUE;
  6278.  
  6279.   case BM_Cmd_Undo:
  6280.     return UNDO_CanUndo(f->undo);
  6281.   case BM_Cmd_Redo:
  6282.     return UNDO_CanRedo(f->undo);
  6283.  
  6284.   case BM_Cmd_Cut:
  6285.   case BM_Cmd_Copy:
  6286.   case BM_Cmd_Delete:
  6287.     return (f->gSelectionCount > 0);
  6288.  
  6289.   case BM_Cmd_Paste:
  6290.     return BMFE_GetClipContents(context, &length) ? TRUE : FALSE;
  6291.     break;
  6292.  
  6293.  
  6294.   case BM_Cmd_SelectAllBookmarks: /**/
  6295.     return TRUE;
  6296.  
  6297.   case BM_Cmd_Find:
  6298.     return TRUE;
  6299.  
  6300.   case BM_Cmd_FindAgain:
  6301.     return ((f->gFindInfo != NULL) && (f->gFindInfo->textToFind != NULL));
  6302.  
  6303.   case BM_Cmd_BookmarkProps: /**/
  6304.     return (f->gSelectionCount == 1 &&
  6305.             !(f->gSelectionMask & BM_TYPE_SEPARATOR));
  6306.  
  6307.   case BM_Cmd_Sort_Name:
  6308.   case BM_Cmd_Sort_Name_Asc:
  6309.   case BM_Cmd_Sort_Address:
  6310.   case BM_Cmd_Sort_Address_Asc:
  6311.   case BM_Cmd_Sort_AddDate:
  6312.   case BM_Cmd_Sort_AddDate_Asc:
  6313.   case BM_Cmd_Sort_LastVisit:
  6314.   case BM_Cmd_Sort_LastVisit_Asc:
  6315.   case BM_Cmd_Sort_Natural:
  6316.     if (f->gSelectionCount == 0) return FALSE;
  6317.     if (f->gSelectionCount == 1) {
  6318.       return f->gSelectionMask == BM_TYPE_HEADER;
  6319.     }
  6320.     return bm_SelectionIsContiguous(context);
  6321.  
  6322.   case BM_Cmd_InsertBookmark:
  6323.   case BM_Cmd_InsertHeader:
  6324.     return TRUE;
  6325.  
  6326.   case BM_Cmd_InsertSeparator:
  6327.     return context->type == MWContextBookmarks;
  6328.  
  6329.   case BM_Cmd_GotoBookmark:
  6330.     if (context->type == MWContextBookmarks) {
  6331.       return (f->gSelectionCount == 1 &&
  6332.               (f->gSelectionMask & (BM_TYPE_URL | BM_TYPE_ALIAS)));
  6333.     } else {
  6334.       return f->gSelectionCount > 0;
  6335.     }
  6336.  
  6337.   case BM_Cmd_MakeAlias:
  6338.     return (f->gSelectionCount == 1 &&
  6339.             (f->gSelectionMask & BM_TYPE_URL));
  6340.  
  6341.   case BM_Cmd_SetAddHeader:
  6342.     return (context->type == MWContextBookmarks &&
  6343.             f->gSelectionCount == 1 &&
  6344.             (f->gSelectionMask & BM_TYPE_HEADER) &&
  6345.             BM_FirstSelectedItem(context) != f->addheader);
  6346.             
  6347.   case BM_Cmd_SetMenuHeader:
  6348.     return (context->type == MWContextBookmarks &&
  6349.             f->gSelectionCount == 1 &&
  6350.             (f->gSelectionMask & BM_TYPE_HEADER) &&
  6351.             BM_FirstSelectedItem(context) != f->menuheader);
  6352.  
  6353.   default:
  6354.     XP_ASSERT(0);
  6355.     break;
  6356.   }
  6357.   return FALSE;
  6358. }
  6359.  
  6360. static void
  6361. bm_open_file(MWContext* context, char* newFile, void* closure)
  6362. {
  6363.   if (newFile) {
  6364. #ifdef XP_WIN
  6365.     BMFE_ChangingBookmarksFile();
  6366. #endif
  6367.  
  6368.     BM_ReadBookmarksFromDisk(context, newFile, NULL);
  6369.  
  6370. #ifdef XP_WIN
  6371.     BMFE_ChangedBookmarksFile();
  6372. #endif
  6373.  
  6374.     XP_FREE(newFile);
  6375.   }
  6376. }
  6377.  
  6378.  
  6379. /* LI_STUFF give li the ability to open a new bookmarks file */
  6380. void
  6381. BM_Open_File(MWContext* context, char* newFile)
  6382. {
  6383.     bm_open_file(context, newFile, NULL);
  6384. }
  6385.  
  6386.  
  6387. static void
  6388. bm_import_file(MWContext* context, char* newFile, void* closure)
  6389. {
  6390.   BM_Frame* f = GETFRAME(context);
  6391.   bm_start_batch(context);
  6392.   UNDO_DiscardAll(f->undo);
  6393.   if (newFile) {
  6394.     BM_Entry* oldroot = f->gBookmarks;
  6395.     BM_Entry* oldmenuheader = f->menuheader;
  6396.     BM_Entry* oldaddheader = f->addheader;
  6397.     BM_Entry* newroot;
  6398.     BM_Entry* entry;
  6399.     BM_Entry* next;
  6400.     char* oldfile = NULL;
  6401.     XP_StatStruct savedStat;
  6402.     
  6403.     if (f->gFile) {
  6404.       oldfile = XP_STRDUP(f->gFile);
  6405.       if (!oldfile) return; /* Out of memory... */
  6406.     }
  6407.     f->gBookmarks = NULL;
  6408.     f->gBookmarksModified = FALSE; /* Don't save now. */
  6409.     
  6410.     /* save real stat 'cause it's about to get busted */
  6411.     XP_MEMCPY(&savedStat, &(f->laststat), sizeof(savedStat));
  6412.     
  6413.     BM_ReadBookmarksFromDisk(context, newFile, NULL);
  6414.     
  6415.     /* restore real stat */
  6416.     XP_MEMCPY(&(f->laststat), &savedStat,  sizeof(savedStat));
  6417.         
  6418.     newroot = f->gBookmarks;
  6419.     if (newroot && oldroot) {
  6420.       /* Make the new stuff be the first folder of the old stuff. */
  6421.       f->gBookmarks = oldroot;
  6422.       if (context->type == MWContextAddressBook) {
  6423.         XP_ASSERT(BM_ISHEADER(newroot));
  6424.         if (BM_ISHEADER(newroot)) {
  6425.           for (entry = newroot->d.header.children;
  6426.                entry;
  6427.                entry = next) {
  6428.             next = entry->next;
  6429.             entry->next = NULL;
  6430.             bm_AddChildToHeaderSorted(context, oldroot, entry);
  6431.           }
  6432.         }
  6433.       } else {
  6434.         BM_PrependChildToHeader(context, oldroot, newroot);
  6435.       }
  6436.       f->menuheader = oldmenuheader;
  6437.       f->addheader = oldaddheader;
  6438.     } else {
  6439.       f->gBookmarks = oldroot ? oldroot : newroot;
  6440.     }
  6441.     FREEIF(f->gFile);
  6442.     f->gFile = oldfile;
  6443.     oldfile = NULL;
  6444.     bm_SetModified(context, TRUE);
  6445.     bm_refresh(context, 1, BM_LAST_CELL);
  6446.     XP_FREE(newFile);
  6447.   }
  6448.   bm_end_batch(context);
  6449. }
  6450.  
  6451.  
  6452. static void
  6453. bm_save_as_file(MWContext* context, char* saveName, void* closure)
  6454. {
  6455.   if (saveName) {
  6456.     BM_SaveBookmarks(context, saveName);
  6457.     XP_FREE(saveName);
  6458.   }
  6459. }
  6460.  
  6461.  
  6462. static int
  6463. bm_comparenames(const void* e1, const void* e2)
  6464. {
  6465. #ifdef INTL_SORT
  6466.   return XP_StrColl((*((BM_Entry**)e1))->name, (*((BM_Entry**)e2))->name);
  6467. #else
  6468.   return XP_STRCMP((*((BM_Entry**)e1))->name, (*((BM_Entry**)e2))->name);
  6469. #endif
  6470. }
  6471. static int
  6472. bm_comparenames_Asc(const void* e1, const void* e2)
  6473. {
  6474. #ifdef INTL_SORT
  6475.   return XP_StrColl((*((BM_Entry**)e2))->name, (*((BM_Entry**)e1))->name);
  6476. #else
  6477.   return XP_STRCMP((*((BM_Entry**)e2))->name, (*((BM_Entry**)e1))->name);
  6478. #endif
  6479. }
  6480.  
  6481. static int bm_compare_address( const void *elem1, const void *elem2 )
  6482. {
  6483.     BM_Entry *p1 = *(BM_Entry **)elem1;
  6484.     BM_Entry *p2 = *(BM_Entry **)elem2;
  6485.  
  6486.     if( !(p1->type == BM_TYPE_URL ||
  6487.           p1->type == BM_TYPE_ADDRESS) )
  6488.     {
  6489.         if( !(p2->type == BM_TYPE_URL ||
  6490.               p2->type == BM_TYPE_ADDRESS) )
  6491.         {
  6492.             return 0;
  6493.         }
  6494.         return -1;
  6495.     }
  6496.     else if( !(p2->type == BM_TYPE_URL ||
  6497.                p2->type == BM_TYPE_ADDRESS) )
  6498.     {
  6499.         return 1;
  6500.     }
  6501.  
  6502.     /* Note we rely on d.url.address is at same mem address as d.address.address */
  6503.  
  6504.    #ifdef INTL_SORT
  6505.     return XP_StrColl( p1->d.url.address, p2->d.url.address );
  6506.    #else
  6507.     return XP_STRCMP( p1->d.url.address, p2->d.url.address );
  6508.    #endif
  6509. }
  6510. static int bm_compare_address_Asc( const void *elem1, const void *elem2 )
  6511. {
  6512.     BM_Entry *p2 = *(BM_Entry **)elem1;
  6513.     BM_Entry *p1 = *(BM_Entry **)elem2;
  6514.  
  6515.     if( !(p1->type == BM_TYPE_URL ||
  6516.           p1->type == BM_TYPE_ADDRESS) )
  6517.     {
  6518.         if( !(p2->type == BM_TYPE_URL ||
  6519.               p2->type == BM_TYPE_ADDRESS) )
  6520.         {
  6521.             return 0;
  6522.         }
  6523.         return -1;
  6524.     }
  6525.     else if( !(p2->type == BM_TYPE_URL ||
  6526.                p2->type == BM_TYPE_ADDRESS) )
  6527.     {
  6528.         return 1;
  6529.     }
  6530.  
  6531.     /* Note we rely on d.url.address is at same mem address as d.address.address */
  6532.  
  6533.    #ifdef INTL_SORT
  6534.     return XP_StrColl( p1->d.url.address, p2->d.url.address );
  6535.    #else
  6536.     return XP_STRCMP( p1->d.url.address, p2->d.url.address );
  6537.    #endif
  6538. }
  6539.  
  6540. #ifdef SUNOS4
  6541. /* difftime() doesn't seem to exist on SunOS anywhere. -mcafee */
  6542. static double difftime(time_t time1, time_t time0)
  6543. {
  6544.   return (double) (time1 - time0);
  6545. }
  6546. #endif
  6547.  
  6548. static int bm_compare_time (time_t time1, time_t time0)
  6549. {
  6550.   double diff = difftime( time1, time0 );
  6551.  
  6552.   if (diff > 0.0) return 1;
  6553.   if (diff < 0.0) return -1;
  6554.  
  6555.   return 0;
  6556. }
  6557.  
  6558. static int bm_compare_addition_date( const void *elem1, const void *elem2 )
  6559. {
  6560.     BM_Entry *p1 = *(BM_Entry **)elem1;
  6561.     BM_Entry *p2 = *(BM_Entry **)elem2;
  6562.     
  6563.     return bm_compare_time( p2->addition_date, p1->addition_date );
  6564. }
  6565. static int bm_compare_addition_date_Asc( const void *elem1, const void *elem2 )
  6566. {
  6567.     BM_Entry *p2 = *(BM_Entry **)elem1;
  6568.     BM_Entry *p1 = *(BM_Entry **)elem2;
  6569.     
  6570.     return bm_compare_time( p2->addition_date, p1->addition_date );
  6571. }
  6572.  
  6573. static int bm_compare_natural( const void *elem1, const void *elem2 )
  6574. {
  6575.     BM_Entry *p2 = *(BM_Entry **)elem1;
  6576.     BM_Entry *p1 = *(BM_Entry **)elem2;
  6577.     
  6578.     return (p2->iNaturalIndex >= p1->iNaturalIndex) ? 1 : -1;
  6579. }
  6580.  
  6581. static int bm_compare_last_visit( const void *elem1, const void *elem2 )
  6582. {
  6583.     BM_Entry *p1 = *(BM_Entry **)elem1;
  6584.     BM_Entry *p2 = *(BM_Entry **)elem2;
  6585.     if( p2->type != BM_TYPE_URL )
  6586.     {
  6587.         if( p1->type != BM_TYPE_URL )
  6588.         {
  6589.             return 0;
  6590.         }
  6591.         return -1;
  6592.     }
  6593.     else if( p1->type != BM_TYPE_URL )
  6594.     {
  6595.         return 1;
  6596.     }
  6597.     
  6598.     return bm_compare_time( p2->d.url.last_visit, p1->d.url.last_visit );
  6599. }
  6600. static int bm_compare_last_visit_Asc( const void *elem1, const void *elem2 )
  6601. {
  6602.     BM_Entry *p2 = *(BM_Entry **)elem1;
  6603.     BM_Entry *p1 = *(BM_Entry **)elem2;
  6604.     if( p2->type != BM_TYPE_URL )
  6605.     {
  6606.         if( p1->type != BM_TYPE_URL )
  6607.         {
  6608.             return 0;
  6609.         }
  6610.         return -1;
  6611.     }
  6612.     else if( p1->type != BM_TYPE_URL )
  6613.     {
  6614.         return 1;
  6615.     }
  6616.     
  6617.     return bm_compare_time( p2->d.url.last_visit, p1->d.url.last_visit );
  6618. }
  6619.  
  6620. static void
  6621. bm_SortSelected_1(MWContext* context, BM_Entry* header, BM_SortType enSortType )
  6622. {
  6623.   BM_Frame* f = GETFRAME(context);
  6624.   BM_Entry** list = NULL;
  6625.   int numlist;
  6626.   BM_Entry* entry;
  6627.   BM_Entry* previous = NULL;
  6628.   int i;
  6629. #ifdef XP_WIN  
  6630.   int (__cdecl *pfSort)(const void *, const void *);
  6631. #else
  6632.   int (*pfSort)(const void *, const void *);
  6633. #endif
  6634.  
  6635.   XP_ASSERT(BM_ISHEADER(header));
  6636.   
  6637.   switch( enSortType )
  6638.   {
  6639.      case BM_Sort_Name:
  6640.         pfSort = bm_comparenames;
  6641.         break;
  6642.      case BM_Sort_Name_Asc:
  6643.         pfSort = bm_comparenames_Asc;
  6644.         break;
  6645.  
  6646.      case BM_Sort_Address:
  6647.         pfSort = bm_compare_address;
  6648.         break;
  6649.      case BM_Sort_Address_Asc:
  6650.         pfSort = bm_compare_address_Asc;
  6651.         break;
  6652.  
  6653.      case BM_Sort_AddDate:
  6654.         pfSort = bm_compare_addition_date;
  6655.         break;
  6656.      case BM_Sort_AddDate_Asc:
  6657.         pfSort = bm_compare_addition_date_Asc;
  6658.         break;
  6659.  
  6660.      case BM_Sort_LastVisit:
  6661.         pfSort = bm_compare_last_visit;
  6662.         break;
  6663.      case BM_Sort_LastVisit_Asc:
  6664.         pfSort = bm_compare_last_visit_Asc;
  6665.         break;
  6666.         
  6667.      case BM_Sort_Natural:
  6668.      default:
  6669.         pfSort = bm_compare_natural;     
  6670.         break;
  6671.   }
  6672.   
  6673.   if (header->d.header.childCount == 0) return;
  6674.   if (BM_ISSELECTED(header) && f->gSelectionCount == 1) {
  6675.     numlist = header->d.header.childCount;
  6676.     list = (BM_Entry**) XP_ALLOC(numlist * sizeof(BM_Entry*));
  6677.     if (!list) return;
  6678.     for (i = 0, entry = header->d.header.children;
  6679.          i < numlist;
  6680.          i++, entry = entry->next) {
  6681.       list[i] = entry;
  6682.     }
  6683.   } else {
  6684.     i = 0;
  6685.     for (entry = header->d.header.children ; entry ; entry = entry->next) {
  6686.       if (BM_ISSELECTED(entry)) {
  6687.         if (list == NULL) {
  6688.           list = (BM_Entry**) XP_ALLOC(header->d.header.childCount *
  6689.                                        sizeof(BM_Entry*));
  6690.           if (list == NULL) return;
  6691.         }
  6692.         list[i++] = entry;
  6693.       } else {
  6694.         if (list == NULL) previous = entry;
  6695.       }
  6696.     }
  6697.     numlist = i;
  6698.   }
  6699.   if (list) {
  6700.     if (numlist > 1) {
  6701.       for (i=0 ; i<numlist ; i++) {
  6702.         if (list[i]->name == NULL) {
  6703.           list[i]->name = XP_STRDUP("");
  6704.           if (list[i]->name == NULL) return;
  6705.         }
  6706.         BM_RemoveChildFromHeader(context, header, list[i]);
  6707.       }
  6708.       XP_QSORT(list, numlist, sizeof(BM_Entry*), pfSort);
  6709.       for (i=0 ; i<numlist ; i++) {
  6710.         if (previous) {
  6711.           bm_InsertItemAfter(context, previous, list[i], FALSE);
  6712.         } else {
  6713.           BM_PrependChildToHeader(context, header, list[i]);
  6714.         }
  6715.         previous = list[i];
  6716.       }
  6717.     }
  6718.     XP_FREE(list);
  6719.     list = NULL;
  6720.   }
  6721.   for (entry = header->d.header.children ; entry ; entry = entry->next) {
  6722.     if (BM_ISHEADER(entry)) bm_SortSelected_1(context, entry, enSortType );
  6723.   }
  6724. }
  6725.  
  6726. static void
  6727. bm_SortSilent_1(MWContext* context, BM_Entry* header, BM_SortType enSortType )
  6728. {
  6729.   BM_Frame* f = GETFRAME(context);
  6730.   BM_Entry** list = NULL;
  6731.   int numlist;
  6732.   BM_Entry* entry;
  6733.   BM_Entry* previous = NULL;
  6734.   int i;
  6735.   XP_Bool bSelected = FALSE;
  6736. #ifdef XP_WIN  
  6737.   int (__cdecl *pfSort)(const void *, const void *);
  6738. #else
  6739.   int (*pfSort)(const void *, const void *);
  6740. #endif
  6741.  
  6742.   XP_ASSERT(BM_ISHEADER(header));
  6743.   
  6744.   switch( enSortType )
  6745.   {
  6746.      case BM_Sort_Name:
  6747.         pfSort = bm_comparenames;
  6748.         break;
  6749.      case BM_Sort_Name_Asc:
  6750.         pfSort = bm_comparenames_Asc;
  6751.         break;
  6752.  
  6753.      case BM_Sort_Address:
  6754.         pfSort = bm_compare_address;
  6755.         break;
  6756.      case BM_Sort_Address_Asc:
  6757.         pfSort = bm_compare_address_Asc;
  6758.         break;
  6759.  
  6760.      case BM_Sort_AddDate:
  6761.         pfSort = bm_compare_addition_date;
  6762.         break;
  6763.      case BM_Sort_AddDate_Asc:
  6764.         pfSort = bm_compare_addition_date_Asc;
  6765.         break;
  6766.  
  6767.      case BM_Sort_LastVisit:
  6768.         pfSort = bm_compare_last_visit;
  6769.         break;
  6770.      case BM_Sort_LastVisit_Asc:
  6771.         pfSort = bm_compare_last_visit_Asc;
  6772.         break;
  6773.         
  6774.      case BM_Sort_Natural:
  6775.      default:
  6776.         pfSort = bm_compare_natural;     
  6777.         break;
  6778.   }
  6779.   
  6780.   if (header->d.header.childCount == 0) return;
  6781.   numlist = header->d.header.childCount;
  6782.   list = (BM_Entry**) XP_ALLOC(numlist * sizeof(BM_Entry*));
  6783.   if (!list) return;
  6784.   for (i = 0, entry = header->d.header.children;
  6785.        i < numlist;
  6786.        i++, entry = entry->next) {
  6787.     list[i] = entry;
  6788.   }
  6789.   if (numlist > 1) {
  6790.     for (i=0 ; i<numlist ; i++) {
  6791.       if (list[i]->name == NULL) {
  6792.           list[i]->name = XP_STRDUP("");
  6793.          if (list[i]->name == NULL) return;
  6794.       }
  6795.       if (BM_ISSELECTED(list[i])) {
  6796.         BM_CLEARFLAG(list[i], BM_ATTR_SELECTED);
  6797.         bSelected = TRUE;
  6798.       }
  6799.       BM_RemoveChildFromHeader(context, header, list[i]);
  6800.       if (bSelected) {
  6801.         BM_SETFLAG(list[i], BM_ATTR_SELECTED);
  6802.         bSelected = FALSE;
  6803.       }
  6804.     }
  6805.     XP_QSORT(list, numlist, sizeof(BM_Entry*), pfSort);
  6806.     for (i=0 ; i<numlist ; i++) {
  6807.       if (previous) {
  6808.         bm_InsertItemAfter(context, previous, list[i], FALSE);
  6809.       } else {
  6810.         BM_PrependChildToHeader(context, header, list[i]);
  6811.       }
  6812.       previous = list[i];
  6813.     }
  6814.   }
  6815.   XP_FREE(list);
  6816.   list = NULL;
  6817.   for (entry = header->d.header.children ; entry ; entry = entry->next) {
  6818.     if (BM_ISHEADER(entry)) bm_SortSilent_1(context, entry, enSortType );
  6819.   }
  6820. }
  6821.  
  6822. /*
  6823. //  Normalize bookmarks based on the current sort.  Note the current sort
  6824. //  should be the natural sort order (aka BM_Sort_Natural) as no other 
  6825. //  sort order has a mapping to the natural index.
  6826. */
  6827. static void
  6828. bm_Normalize(MWContext* context, BM_Entry* at)
  6829. {
  6830.   BM_Entry* nextChild;
  6831.   BM_Entry* children;
  6832.  
  6833.   int32 iNaturalIndex = 0;
  6834.   
  6835.   while (at) {
  6836.     nextChild = at->next;
  6837.     if (BM_ISHEADER(at)) {
  6838.       children = at->d.header.children;
  6839.     } else {
  6840.       children = NULL;
  6841.     }
  6842.  
  6843.     at->iNaturalIndex = iNaturalIndex++;
  6844.  
  6845.     if (children) {
  6846.       bm_Normalize(context, children);
  6847.     }
  6848.  
  6849.     at = nextChild;
  6850.   }
  6851. }
  6852.  
  6853. static void
  6854. bm_SortSelected(MWContext* context, BM_SortType enSortType )
  6855. {
  6856.   BM_Frame* f = GETFRAME(context);
  6857.   if (f->gSelectionCount < 0) bm_SyncSelection(context);
  6858.   if (f->enSortType == BM_Sort_Natural) bm_Normalize(context, BM_GetRoot(context));
  6859.   f->enSortType = enSortType;
  6860.   f->bSorting = TRUE;
  6861.   bm_SortSelected_1(context, BM_GetRoot(context), enSortType);
  6862.   f->bSorting = FALSE;
  6863.   bm_refresh(context, 1, BM_LAST_CELL);
  6864. }
  6865.  
  6866. static void
  6867. bm_SortSilent(MWContext* context, BM_SortType enSortType )
  6868. {
  6869.   BM_Frame* f = GETFRAME(context);
  6870.   if (f->gSelectionCount < 0) bm_SyncSelection(context);
  6871.   if (f->enSortType == BM_Sort_Natural) bm_Normalize(context, BM_GetRoot(context));
  6872.   f->enSortType = enSortType;
  6873.   f->bSorting = TRUE;
  6874.   bm_SortSilent_1(context, BM_GetRoot(context), enSortType);
  6875.   f->bSorting = FALSE;
  6876. }
  6877.  
  6878. static void bm_append_address_string(MWContext* context, BM_Entry* entry,
  6879.                                      void* closure);
  6880.  
  6881. static void
  6882. bm_append_fulladdress_string(MWContext* context, BM_Entry* entry,
  6883.                              void* closure)
  6884. {
  6885.   if (BM_ISALIAS(entry)) entry = entry->d.alias.original;
  6886.   if (entry->flags & BM_ATTR_MARKED) return;
  6887.   if (BM_ISHEADER(entry)) {
  6888.     BM_SETFLAG(entry, BM_ATTR_MARKED);
  6889.     for (entry = entry->d.header.children ; entry ; entry = entry->next) {
  6890.       bm_append_fulladdress_string(context, entry, closure);
  6891.     }
  6892.   } else if (BM_ISADDRESS(entry)) {
  6893.     bm_append_address_string(context, entry, closure);
  6894.     XP_ASSERT(entry->flags & BM_ATTR_MARKED);
  6895.   }
  6896. }
  6897.  
  6898. static void
  6899. bm_append_address_string(MWContext* context, BM_Entry* entry, void* closure)
  6900. {
  6901.   if (BM_ISALIAS(entry)) entry = entry->d.alias.original;
  6902.   if (entry->flags & BM_ATTR_MARKED) return;
  6903.   if (BM_ISADDRESS(entry) || BM_ISHEADER(entry)) {
  6904.     char* address =
  6905.       BM_ISHEADER(entry) ? BM_GetNickName(entry) : BM_GetAddress(entry);
  6906.     if (BM_ISHEADER(entry) && (address == NULL || *address == '\0')) {
  6907.       /* No nickname for a header, so we don't have anything to write down
  6908.          that we can remember later.  Just write down all the members of
  6909.          this list. */
  6910.       bm_append_fulladdress_string(context, entry, closure);
  6911.     } else {
  6912. #ifdef MOZ_MAIL_NEWS
  6913.       char** buf = (char**) closure;
  6914.       char* full = MSG_MakeFullAddress(BM_GetName(entry), address);
  6915.       if (full) {
  6916.         if (*buf) NET_SACat(buf, ", ");
  6917.         NET_SACat(buf, full);
  6918.         XP_FREE(full);
  6919.       }
  6920. #endif /* MOZ_MAIL_NEWS */
  6921.     }
  6922.   }    
  6923.   BM_SETFLAG(entry, BM_ATTR_MARKED);
  6924. }
  6925.  
  6926. char*
  6927. BM_GetFullAddress(MWContext* context, BM_Entry* entry)
  6928. {
  6929.   char* result = NULL;
  6930.   bm_append_address_string(context, entry, &result);
  6931.   return result;
  6932. }
  6933.  
  6934. static void
  6935. bm_ComposeMessageToSelected(MWContext* context)
  6936. {
  6937.   char* buf = NULL;
  6938.   char* tmp;
  6939.   URL_Struct *url_struct;
  6940.   bm_ClearMarkEverywhere(context);
  6941.   BM_EachSelectedEntryDo(context, bm_append_address_string, &buf);
  6942.   if (!buf) return;
  6943.   tmp = NET_Escape(buf, URL_PATH);
  6944.   XP_FREE(buf);
  6945.   buf = tmp;
  6946.   if (!buf) return;
  6947.   tmp = XP_Cat("mailto:?to=", buf, (char*)/*Win16*/ NULL);
  6948.   XP_FREE(buf);
  6949.   buf = tmp;
  6950.   if (!buf) return;
  6951.   url_struct = NET_CreateURLStruct (buf, NET_NORMAL_RELOAD);
  6952.   if (url_struct) {
  6953.     url_struct->internal_url = TRUE;
  6954.     FE_GetURL(context, url_struct);
  6955.   }
  6956.   XP_FREE(buf);
  6957. }
  6958.  
  6959.  
  6960. char*
  6961. BM_ExpandHeaderString(MWContext* context, const char* value,
  6962.                       XP_Bool expandfull)
  6963. {
  6964.   BM_Frame* f = GETFRAME(context);
  6965.   char* name;
  6966.   char* address;
  6967.   char* curname;
  6968.   char* curaddress;
  6969.   char* pHashStr;
  6970.   int num;
  6971.   int i,j;
  6972.   XP_Bool found = FALSE;
  6973.   BM_Entry* entry;
  6974.   char* result = NULL;
  6975.   char* pTempStr = NULL;
  6976.   int tempBufLen = 0;
  6977.   CHKCONTEXT(context);
  6978. #ifdef MOZ_MAIL_NEWS
  6979.   num = MSG_ParseRFC822Addresses(value, &name, &address);
  6980. #else
  6981.   num = 0;
  6982. #endif /* MOZ_MAIL_NEWS */
  6983.   curname = name;
  6984.   curaddress = address;
  6985.   bm_ClearMarkEverywhere(context);
  6986.   for (i=0 ; i<num ; i++) {
  6987.     pHashStr = NULL;
  6988.     if (XP_STRCHR(curaddress, '@') == NULL) {
  6989.         /* to make nicknames case-insensitive, we have to 
  6990.         ** change the string here to be all lowercase before
  6991.         ** passing it to the hash lookup function */
  6992.  
  6993.         /* first make sure the temporary buffer we have 
  6994.         ** is long enough for this string.  If not, make
  6995.         ** a new one that is long enough. */
  6996.         int curlen;
  6997.         curlen = XP_STRLEN(curaddress);
  6998.         if (!pTempStr || (curlen > tempBufLen)) {
  6999.             FREEIF(pTempStr);
  7000.             pTempStr = XP_STRDUP(curaddress);
  7001.             tempBufLen = curlen;
  7002.         } else {
  7003.             /* just copy the string into the existing buffer */
  7004.             XP_STRCPY(pTempStr, curaddress);
  7005.         }
  7006.         if (pTempStr) {
  7007.             /* now the buffer is loaded with the string, change the string to lowercase */
  7008.             for (j = 0; j < curlen; j++) {
  7009.                 if (isupper(pTempStr[j])) {
  7010.                     pTempStr[j] = (char)tolower(pTempStr[j]);
  7011.                 }
  7012.             }
  7013.             pHashStr = pTempStr;    /* use the temp str for the hash function */
  7014.         } else {
  7015.             pHashStr = curaddress;    /* use the old string if low on memory */
  7016.         }
  7017.     }
  7018.     if (pHashStr && (entry = XP_Gethash(f->nicknameTable, pHashStr, NULL)) != NULL) {
  7019.       found = TRUE;
  7020.       if (expandfull) {
  7021.         bm_append_fulladdress_string(context, entry, &result);
  7022.       } else {
  7023.         bm_append_address_string(context, entry, &result);
  7024.       }
  7025.     } else {
  7026.       if (result) NET_SACat(&result, ", ");
  7027.       if (*curname) {
  7028.         NET_SACat(&result, curname);
  7029.         NET_SACat(&result, " <");
  7030.       }
  7031.       NET_SACat(&result, curaddress);
  7032.       if (*curname) {
  7033.         NET_SACat(&result, ">");
  7034.       }
  7035.     }
  7036.     curname += XP_STRLEN(curname) + 1;
  7037.     curaddress += XP_STRLEN(curaddress) + 1;
  7038.   }
  7039.   FREEIF(name);
  7040.   FREEIF(address);
  7041.   FREEIF(pTempStr);
  7042.   if (!found) {
  7043.     FREEIF(result);                /* Note this sets also result to NULL. */
  7044.   }
  7045.   return result;
  7046. }
  7047.  
  7048.  
  7049.     
  7050.  
  7051.  
  7052.  
  7053. void BM_ObeyCommand(MWContext* context, BM_CommandType command)
  7054. {
  7055.   BM_Frame* f = GETFRAME(context);
  7056.   BM_Entry*        firstSelected;
  7057.  
  7058.   CHKCONTEXTVOID(context);
  7059.  
  7060.   if (!BM_FindCommandStatus(context, command)) return;
  7061.  
  7062.   firstSelected = BM_FirstSelectedItem(context);
  7063.  
  7064.   bm_start_batch(context);
  7065.  
  7066.   switch (command) {
  7067.   case BM_Cmd_Invalid:
  7068.     break;
  7069.  
  7070.   case BM_Cmd_Open:
  7071.     FE_PromptForFileName(context, XP_GetString(XP_BKMKS_OPEN_BKMKS_FILE),
  7072.                          0, TRUE, FALSE, bm_open_file, NULL);
  7073.     break;
  7074.  
  7075.   case BM_Cmd_ImportBookmarks:
  7076.     FE_PromptForFileName(context, 
  7077.         XP_GetString(context->type == MWContextAddressBook ?
  7078.                 XP_BKMKS_IMPORT_ADDRBOOK : XP_BKMKS_IMPORT_BKMKS_FILE),
  7079.                 0, TRUE, FALSE, bm_import_file, NULL);
  7080.     break;
  7081.  
  7082.   case BM_Cmd_SaveAs:
  7083.     FE_PromptForFileName(context, 
  7084.         XP_GetString(context->type == MWContextAddressBook ?
  7085.                     XP_BKMKS_SAVE_ADDRBOOK : XP_BKMKS_SAVE_BKMKS_FILE),
  7086.                     0, FALSE, FALSE, bm_save_as_file, NULL);
  7087.     break;
  7088.  
  7089.   case BM_Cmd_Close:
  7090.     BM_SaveBookmarks(context, f->gFile);
  7091.     /* ### Maybe need to do more? */
  7092.     break;
  7093.  
  7094.   case BM_Cmd_Undo:
  7095.     UNDO_EndBatch(f->undo, NULL, NULL);
  7096.     UNDO_DoUndo(f->undo);
  7097.     UNDO_StartBatch(f->undo);
  7098.     bm_refresh(context, 1, BM_LAST_CELL);
  7099.     bm_SyncCount(context);
  7100.     break;
  7101.  
  7102.   case BM_Cmd_Redo:
  7103.     UNDO_EndBatch(f->undo, NULL, NULL);
  7104.     UNDO_DoRedo(f->undo);
  7105.     UNDO_StartBatch(f->undo);
  7106.     bm_refresh(context, 1, BM_LAST_CELL);
  7107.     bm_SyncCount(context);
  7108.     break;
  7109.  
  7110.   case BM_Cmd_Cut:
  7111.     bm_cut(context);
  7112.     break;
  7113.  
  7114.   case BM_Cmd_Copy:
  7115.     bm_copy(context);
  7116.     break;
  7117.  
  7118.   case BM_Cmd_Paste:
  7119.     bm_paste(context);
  7120.     break;
  7121.  
  7122.   case BM_Cmd_Delete:
  7123.     bm_delete(context);
  7124.     break;
  7125.  
  7126.   case BM_Cmd_SelectAllBookmarks:
  7127.     BM_SelectAll(context, TRUE);
  7128.     break;
  7129.  
  7130.   case BM_Cmd_Find:
  7131.     bm_CloseLastFind(context);
  7132.     bm_BeginFindBookmark(context);
  7133.     break;
  7134.  
  7135.   case BM_Cmd_FindAgain:
  7136.     bm_CloseLastFind(context);
  7137.     BM_DoFindBookmark(context, f->gFindInfo);
  7138.     break;
  7139.  
  7140.   case BM_Cmd_BookmarkProps:
  7141.     if (firstSelected) {
  7142.       BMFE_OpenBookmarksWindow(context);
  7143.       BMFE_EditItem(context, firstSelected);
  7144.     }
  7145.     break;
  7146.  
  7147.   case BM_Cmd_GotoBookmark:
  7148.     if (context->type == MWContextAddressBook) {
  7149.       bm_ComposeMessageToSelected(context);
  7150.     } else if (firstSelected) {
  7151.       BM_GotoBookmark(context, firstSelected);
  7152.     }
  7153.     break;
  7154.  
  7155.   case BM_Cmd_Sort_Name:
  7156.   case BM_Cmd_Sort_Name_Asc:
  7157.   case BM_Cmd_Sort_Address:
  7158.   case BM_Cmd_Sort_Address_Asc:
  7159.   case BM_Cmd_Sort_AddDate:
  7160.   case BM_Cmd_Sort_AddDate_Asc:
  7161.   case BM_Cmd_Sort_LastVisit:
  7162.   case BM_Cmd_Sort_LastVisit_Asc:
  7163.   case BM_Cmd_Sort_Natural:
  7164.     bm_SortSelected( context, command-BM_Cmd_Sort_Name );
  7165.     break;
  7166.  
  7167.   case BM_Cmd_InsertBookmark:
  7168.     bm_BeginEditNewUrl(context);
  7169.     break;
  7170.  
  7171.   case BM_Cmd_InsertHeader:
  7172.     bm_BeginEditNewHeader(context);
  7173.     break;
  7174.  
  7175.   case BM_Cmd_InsertSeparator:
  7176.     if (firstSelected) {
  7177.       bm_InsertItemAfter(context, firstSelected, bm_NewSeparator(), TRUE);
  7178.       bm_refresh(context, BM_GetIndex(context, firstSelected) + 1,
  7179.                  BM_LAST_CELL);
  7180.     }
  7181.     break;
  7182.  
  7183.   case BM_Cmd_MakeAlias:
  7184.     BM_MakeAliases(context);
  7185.     break;
  7186.  
  7187.   case BM_Cmd_SetAddHeader:
  7188.     if (firstSelected) {
  7189.       BM_SetAddHeader(context, firstSelected);
  7190.     }
  7191.     break;
  7192.  
  7193.   case BM_Cmd_SetMenuHeader:
  7194.     if (firstSelected) {
  7195.       BM_SetMenuHeader(context, firstSelected);
  7196.     }
  7197.     break;
  7198.  
  7199.   default:
  7200.     XP_ASSERT(0);
  7201.  
  7202.   }
  7203.   bm_end_batch(context);
  7204. }
  7205.  
  7206.  
  7207.  
  7208. /* Make sure that the given entry is a real entry, and not a pointer that has
  7209.    since become invalid. */
  7210.  
  7211. static XP_Bool
  7212. bm_validate_entry(MWContext* context, BM_Entry* entry, BM_Entry* search)
  7213. {
  7214.   for (; entry ; entry = entry->next) {
  7215.     if (entry == search) return TRUE;
  7216.     if (BM_ISHEADER(entry)) {
  7217.       if (bm_validate_entry(context, entry->d.header.children, search)) {
  7218.         return TRUE;
  7219.       }
  7220.     }
  7221.   }
  7222.   return FALSE;
  7223. }
  7224.  
  7225.  
  7226. static void
  7227. bm_urlcheck_finished(URL_Struct* url_struct, int status, MWContext* context)
  7228. {
  7229.   BM_Frame* f = GETFRAME(context);
  7230.   time_t now;
  7231.   char timestr[40];
  7232.   if (f) {
  7233.     struct BM_WhatsChangedInfo* w = (struct BM_WhatsChangedInfo *)&(f->whatschanged);
  7234.     BM_Entry* entry = (BM_Entry*) url_struct->fe_data;
  7235.     if (bm_validate_entry(context, BM_GetRoot(context), entry)) {
  7236.       const char* url = BM_GetAddress(entry);
  7237.       int32 oldstate = BM_GetChangedState(entry);
  7238.       BM_CLEARFLAG(entry, BM_ATTR_CHECKING);
  7239.       if (status >= 0) {
  7240.         if (url && XP_STRCMP(url_struct->address, url) == 0) {
  7241.           w->numreached++;
  7242.           entry->d.url.last_modified = url_struct->last_modified;
  7243.           if (entry->d.url.last_modified > entry->d.url.last_visit) {
  7244.             w->numchanged++;
  7245.           }
  7246.         }
  7247.       } else {
  7248.         entry->d.url.last_modified = 0;
  7249.       }
  7250.       if (BM_GetChangedState(entry) != oldstate) {
  7251.         bm_entry_changed(context, entry);
  7252.         bm_SetModified(context, TRUE);
  7253.       }
  7254.  
  7255.       now = time ((time_t *) 0);
  7256.  
  7257.       if (w->numreached == 0) {
  7258.         XP_STRCPY(timestr, "???");
  7259.       } else {
  7260.         int32 estimate = (now - w->starttime) * (w->total - w->numreached) /
  7261.           w->numreached;
  7262.         if (estimate < 2 * 60) {
  7263.           PR_snprintf(timestr, sizeof(timestr), XP_GetString(XP_BKMKS_SECONDS), 
  7264.                       estimate);
  7265.         } else if (estimate < 2 * 60 * 60) {
  7266.           PR_snprintf(timestr, sizeof(timestr), XP_GetString(XP_BKMKS_MINUTES), 
  7267.                       estimate / 60);
  7268.         } else {
  7269.           PR_snprintf(timestr, sizeof(timestr), XP_GetString(XP_BKMKS_HOURS_MINUTES),
  7270.                       estimate / 3600, (estimate / 60) % 60);
  7271.         }
  7272.       }
  7273.       BMFE_UpdateWhatsChanged(context, url, w->numreached, w->total,
  7274.                               timestr);
  7275.     }
  7276.  
  7277.     /* Check to see if we're all done.  First check to see if we're in the
  7278.        middle of a batch operation; if we are, then we must be still setting
  7279.        things up and we got called here because we had an invalid bookmark
  7280.        and netlib called the exit routine immediately.  In that case, we
  7281.        don't want to say we're all done; we're probably still sending
  7282.        URLs to netlib.
  7283.  
  7284.        If we're not in the middle of a batch operation, then we're all done
  7285.        if there are no more outstanding connections on our context. */
  7286.     if (f->batch_depth == 0 &&
  7287.         !NET_AreThereActiveConnectionsForWindow(context)) {
  7288.       BMFE_FinishedWhatsChanged(context, w->total, w->numreached,
  7289.                                 w->numchanged);
  7290.     }
  7291.   }
  7292. }
  7293.  
  7294. #ifdef XP_WIN16
  7295. /* code segment is full, switch to a new segment */
  7296. #pragma code_seg("BKMKS2_TEXT","CODE")
  7297. #endif
  7298.  
  7299.  
  7300.  
  7301. static void
  7302. bm_urlcheck_start(MWContext* context, BM_Entry* entry)
  7303. {
  7304.   BM_Frame* f = GETFRAME(context);
  7305.   char* url;
  7306.   URL_Struct* url_struct;
  7307.  
  7308.   XP_ASSERT(entry);
  7309.  
  7310.   if (BM_ISALIAS(entry)) {
  7311.     entry = entry->d.alias.original;
  7312.   }
  7313.   if (!entry) return;
  7314.  
  7315.   if (entry->flags & BM_ATTR_CHECKING) return;
  7316.  
  7317.   url = BM_GetAddress(entry);
  7318.   if (!url) return;
  7319.   url_struct = NET_CreateURLStruct(url, NET_SUPER_RELOAD);
  7320.   if (!url_struct) return;
  7321.   BM_SETFLAG(entry, BM_ATTR_CHECKING);
  7322.   url_struct->method = URL_HEAD_METHOD;
  7323.   url_struct->fe_data = entry;
  7324.   f->whatschanged.total++;
  7325.   NET_GetURL(url_struct, FO_PRESENT, context, bm_urlcheck_finished);
  7326. }
  7327.  
  7328.  
  7329.  
  7330.  
  7331. static void
  7332. bm_start_whats_changed_1(MWContext* context, BM_Entry* entry,
  7333.                          XP_Bool do_only_selected)
  7334. {
  7335.   for ( ; entry ; entry = entry->next) {
  7336.     if (BM_ISURL(entry) || BM_ISALIAS(entry)) {
  7337.       if (!do_only_selected || BM_ISSELECTED(entry)) {
  7338.         bm_urlcheck_start(context, entry);
  7339.       } 
  7340.     } else if (BM_ISHEADER(entry)) {
  7341.       /* Recur through the children.  If we are selected and folded, then
  7342.          make sure we do all of our descendents. */
  7343.       bm_start_whats_changed_1(context, entry->d.header.children,
  7344.                                (BM_ISSELECTED(entry) && BM_ISFOLDED(entry)) ?
  7345.                                FALSE : do_only_selected);
  7346.     }
  7347.   }
  7348. }
  7349.  
  7350.  
  7351. static void
  7352. bm_clear_check_attr(MWContext* context, BM_Entry* entry, void* closure)
  7353. {
  7354.   BM_CLEARFLAG(entry, BM_ATTR_CHECKING);
  7355. }
  7356.  
  7357.  
  7358. int
  7359. BM_StartWhatsChanged(MWContext* context, XP_Bool do_only_selected)
  7360. {
  7361.   BM_Frame* f = GETFRAME(context);
  7362.   struct BM_WhatsChangedInfo* w;
  7363.   XP_ASSERT(context && context->type == MWContextBookmarks && f);
  7364.   if (!context || context->type != MWContextBookmarks || !f) return -1;
  7365.   w = &(f->whatschanged);
  7366.   BM_CancelWhatsChanged(context);
  7367.   XP_MEMSET(w, 0, sizeof(*w));
  7368.   w->starttime = time ((time_t *) 0);
  7369.  
  7370.   BM_EachEntryDo(context, bm_clear_check_attr, NULL);
  7371.  
  7372.   bm_start_batch(context);
  7373.   bm_start_whats_changed_1(context, BM_GetRoot(context), do_only_selected);
  7374.  
  7375. #if 0
  7376.   minutes = f->whatschanged.total * 35 / 60;
  7377.                                 /* Assumes a maximum timeout of 35 seconds per
  7378.                                    connection.  Need to not hard-code
  7379.                                    this... #### */
  7380.   if (minutes < 60) {
  7381.     /* Fix i18n ### */
  7382.     PR_snprintf(w->totaltime, sizeof(w->totaltime), "%ld minutes", minutes);
  7383.   } else {
  7384.     /* Fix i18n ### */
  7385.     PR_snprintf(w->totaltime, sizeof(w->totaltime), "%ld hours",
  7386.                 (minutes / 60) + 1);
  7387.   }
  7388. #endif
  7389.  
  7390.   BMFE_UpdateWhatsChanged(context, NULL, w->numreached, w->total,
  7391.                           "???"); /* Fix i18n ### */
  7392.   bm_refresh(context, 1, BM_LAST_CELL);
  7393.   bm_end_batch(context);
  7394.   if (!NET_AreThereActiveConnectionsForWindow(context)) {
  7395.     /* All done, already (probably because nothing was selected). */
  7396.     BMFE_FinishedWhatsChanged(context, w->total, w->numreached,
  7397.                               w->numchanged);
  7398.   }
  7399.   return 0;
  7400. }
  7401.  
  7402.  
  7403. int
  7404. BM_CancelWhatsChanged(MWContext* context)
  7405. {
  7406.   XP_InterruptContext(context);
  7407.   return 0;
  7408. }
  7409.  
  7410. void BM_ResetUndo(MWContext * context)
  7411. {
  7412.     BM_Frame * f = GETFRAME(context);
  7413.     UNDO_DiscardAll( f->undo );
  7414. }
  7415.  
  7416.