mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 14:15:30 +00:00
7429 lines
173 KiB
C
7429 lines
173 KiB
C
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
/* If you add code to this file, make sure it still builds on win16 debug - its
|
|
* code segment is too big.
|
|
*/
|
|
|
|
#include "bkmks.h"
|
|
#include "net.h"
|
|
#include "xp_mcom.h"
|
|
#include "client.h"
|
|
#include "msgcom.h"
|
|
#include "undo.h"
|
|
#include "xp_hash.h"
|
|
#include "xpgetstr.h"
|
|
#include "glhist.h"
|
|
#include "libi18n.h"
|
|
#include "xp_qsort.h"
|
|
#include "intl_csi.h"
|
|
|
|
#ifdef XP_MAC
|
|
#pragma warn_unusedarg off
|
|
#pragma warn_possunwant off
|
|
#endif
|
|
|
|
#define INTL_SORT 1
|
|
#ifdef INTL_SORT /* Added by ftang */
|
|
#include "xplocale.h"
|
|
#endif
|
|
|
|
extern int MK_OUT_OF_MEMORY;
|
|
|
|
extern int XP_BKMKS_BOOKMARKS_CHANGED;
|
|
extern int XP_BKMKS_ADDRESSBOOK_CHANGED;
|
|
extern int XP_BKMKS_BOOKMARKS_CONFLICT;
|
|
extern int XP_BKMKS_ADDRESSBOOK_CONFLICT;
|
|
extern int XP_BKMKS_CANT_WRITE_ADDRESSBOOK;
|
|
extern int XP_BKMKS_CANT_WRITE_BOOKMARKS;
|
|
|
|
/* added by L10N team */
|
|
extern int XP_BKMKS_HOURS_AGO;
|
|
extern int XP_BKMKS_DAYS_AGO;
|
|
extern int XP_BKMKS_COUNTALIASES_MANY;
|
|
extern int XP_BKMKS_COUNTALIASES_ONE;
|
|
extern int XP_BKMKS_COUNTALIASES_NONE;
|
|
extern int XP_BKMKS_INVALID_NICKNAME;
|
|
extern int XP_BKMKS_NICKNAME_ALREADY_EXISTS;
|
|
extern int XP_BKMKS_REMOVE_THIS_ITEMS_ALIASES;
|
|
extern int XP_BKMKS_REMOVE_SOME_ITEMS_ALIASES;
|
|
extern int XP_BKMKS_AUTOGENERATED_FILE;
|
|
extern int XP_BKMKS_READ_AND_OVERWRITE;
|
|
extern int XP_BKMKS_DO_NOT_EDIT;
|
|
extern int XP_BKMKS_NEW_HEADER;
|
|
extern int XP_BKMKS_NEW_BOOKMARK;
|
|
extern int XP_BKMKS_NOT_FOUND;
|
|
extern int XP_BKMKS_OPEN_BKMKS_FILE;
|
|
extern int XP_BKMKS_IMPORT_BKMKS_FILE;
|
|
extern int XP_BKMKS_IMPORT_ADDRBOOK;
|
|
extern int XP_BKMKS_SAVE_BKMKS_FILE;
|
|
extern int XP_BKMKS_SAVE_ADDRBOOK;
|
|
extern int XP_BKMKS_LESS_THAN_ONE_HOUR_AGO;
|
|
|
|
|
|
extern int XP_BKMKS_SOMEONE_S_BOOKMARKS; /* "%s%s's Bookmarks%s" */
|
|
extern int XP_BKMKS_PERSONAL_BOOKMARKS; /* "%sPersonal Bookmarks%s" */
|
|
extern int XP_BKMKS_SOMEONE_S_ADDRESSBOOK; /* "%s%s's Addressbook%s" */
|
|
extern int XP_BKMKS_PERSONAL_ADDRESSBOOK; /* "%sPersonal Addressbook%s" */
|
|
|
|
extern int XP_BKMKS_BOOKMARK;
|
|
extern int XP_BKMKS_ENTRY;
|
|
extern int XP_BKMKS_SECONDS;
|
|
extern int XP_BKMKS_MINUTES;
|
|
extern int XP_BKMKS_HOURS_MINUTES;
|
|
|
|
extern int XP_BKMKS_HEADER;
|
|
extern int XP_ADDRBOOK_HEADER;
|
|
|
|
|
|
#define SECONDS_PER_DAY 86400L
|
|
#define BMLIST_COOKIE "<!DOCTYPE NETSCAPE-Bookmark-file-1>"
|
|
#define BM_ADDR_LIST_COOKIE "<!DOCTYPE NETSCAPE-Addressbook-file-1>"
|
|
#define READ_BUFFER_SIZE 2048
|
|
#define WRITE_BUFFER_SIZE 1848 /* Make this a few hundred less than
|
|
read buffer so we can be sure to
|
|
read the bookmarks back properly */
|
|
#define DEF_NAME "Personal Bookmarks" /* L10N? This doesn't seem to be used. */
|
|
|
|
#ifdef FREEIF
|
|
#undef FREEIF
|
|
#endif
|
|
#define FREEIF(obj) do { if (obj) { XP_FREE (obj); obj = 0; }} while (0)
|
|
|
|
#define BM_ATTR_FOLDED 0x0001
|
|
#define BM_ATTR_SELECTED 0x0002
|
|
#define BM_ATTR_ISNEW 0x0004
|
|
#define BM_ATTR_FINDAFF 0x0008 /* This entry is temporarily unfolded only
|
|
as a result of the last find
|
|
operation. */
|
|
#define BM_ATTR_HASALIASES 0x0010 /* Actually, being set only means that
|
|
this entry *might* have aliases. But
|
|
if an entry has aliases, this bit is
|
|
definitely set. */
|
|
#define BM_ATTR_MARKED 0x0020 /* Random bit to temporarily mark a bunch of
|
|
items. */
|
|
#define BM_ATTR_CHECKING 0x0040 /* In the midst of checking whether this
|
|
entry has changed. */
|
|
|
|
|
|
#define BM_ISHEADER(bmStruct) \
|
|
((bmStruct) && ((bmStruct)->type == BM_TYPE_HEADER))
|
|
|
|
#define BM_ISURL(bmStruct) \
|
|
((bmStruct) && ((bmStruct)->type == BM_TYPE_URL))
|
|
|
|
#define BM_ISADDRESS(bmStruct) \
|
|
((bmStruct) && ((bmStruct)->type == BM_TYPE_ADDRESS))
|
|
|
|
#define BM_ISSEPARATOR(bmStruct) \
|
|
((bmStruct) && ((bmStruct)->type == BM_TYPE_SEPARATOR))
|
|
|
|
#define BM_ISALIAS(bmStruct) \
|
|
((bmStruct) && ((bmStruct)->type == BM_TYPE_ALIAS))
|
|
|
|
#define BM_ISFOLDED(bmStruct) \
|
|
((bmStruct) && ((bmStruct)->flags & BM_ATTR_FOLDED))
|
|
|
|
#define BM_ISSELECTED(bmStruct) \
|
|
((bmStruct) && ((bmStruct)->flags & BM_ATTR_SELECTED))
|
|
|
|
#define BM_SETFLAG(bmStruct, flag) \
|
|
((bmStruct) ? ((bmStruct)->flags |= (flag)) : 0)
|
|
|
|
#define BM_CLEARFLAG(bmStruct, flag) \
|
|
((bmStruct) ? ((bmStruct)->flags &= ~(flag)) : 0)
|
|
|
|
static int32 g_iNaturalIndexPool = 0;
|
|
|
|
struct BM_Entry_struct
|
|
{
|
|
BM_Type type;
|
|
|
|
uint16 flags;
|
|
BM_Entry* next;
|
|
BM_Entry* parent;
|
|
char* name;
|
|
char* description;
|
|
BM_Date addition_date;
|
|
int32 iNaturalIndex; /* Index for user arranged "sort" */
|
|
char* nickname; /* Used only by address book, alas. */
|
|
|
|
union {
|
|
struct BM_Header {
|
|
char* target; /* target */
|
|
BM_Entry* children; /* a linked list of my children */
|
|
uint32 childCount; /* the number of "children" */
|
|
BM_Entry* lastChild; /* the last child in "children" */
|
|
} header;
|
|
struct BM_Url {
|
|
char* address; /* address */
|
|
char* target; /* target */
|
|
char* content_type; /* content-type */
|
|
BM_Date last_visit; /* when last visited */
|
|
BM_Date last_modified; /* when the contents of the URL was last
|
|
modified. */
|
|
} url;
|
|
struct BM_Address {
|
|
char* address; /* e-mail address */
|
|
} address;
|
|
struct BM_Alias {
|
|
BM_Entry* original; /* the original bm */
|
|
} alias;
|
|
} d;
|
|
};
|
|
|
|
|
|
typedef struct BM_Frame {
|
|
BM_Entry* gBookmarks; /* root of the tree */
|
|
XP_Bool gBookmarksModified; /* TRUE if the tree is modified */
|
|
int32 gCount; /* number of entries in tree. If
|
|
negative, then need to be
|
|
recalculated. */
|
|
int32 gVisCount; /* number of visible entries. If
|
|
negative, then need to be
|
|
recalculated. */
|
|
int32 gSelectionCount; /* number of selected items. If
|
|
negative, then we're out of sync,
|
|
call bm_SyncSelection to
|
|
synchronize. */
|
|
uint32 gSelectionMask; /* what types of items are selected
|
|
(invalid if gSelectionCount is
|
|
negative.)*/
|
|
BM_FindInfo* gFindInfo; /* information for the find dialog */
|
|
char* gFile; /* the file this data is from */
|
|
void* gTemporary;
|
|
void* feData;
|
|
int32 max_depth; /* The number of levels in the heirarchy that
|
|
we currently display. (If zero, then we
|
|
don't know. */
|
|
UndoState* undo;
|
|
MWContext* next; /* Next bookmarks context in master linked list. */
|
|
XP_Bool unfoldedForFind; /* TRUE if some headers have been unfolded
|
|
as the result of a find operation. The
|
|
headers in question will have the
|
|
BM_ATTR_FINDAFF flag set.*/
|
|
|
|
BM_SortType enSortType; /* the sort field (a column or natural) */
|
|
XP_Bool bSorting;
|
|
XP_HashTable aliasTable;
|
|
XP_HashTable nicknameTable;
|
|
int aliasID;
|
|
XP_Bool errorSavingBookmarks;
|
|
|
|
int32 batch_depth;
|
|
int32 first_update_line;
|
|
int32 last_update_line;
|
|
|
|
void* savetimer;
|
|
|
|
BM_Entry* lastSelectedItem;
|
|
|
|
BM_Entry* menuheader; /* Which header to use as the menu bar. */
|
|
BM_Entry* addheader; /* Which entry to add new items into. */
|
|
|
|
XP_StatStruct laststat; /* Stat of the file when we loaded it in. */
|
|
|
|
|
|
|
|
struct BM_WhatsChangedInfo {
|
|
int32 total;
|
|
int32 numreached;
|
|
int32 numchanged;
|
|
time_t starttime;
|
|
} whatschanged;
|
|
|
|
} BM_Frame;
|
|
|
|
|
|
#define CHKCONTEXT(context) \
|
|
XP_ASSERT(context != NULL && (context->type == MWContextAddressBook || \
|
|
context->type == MWContextBookmarks) && \
|
|
context->bmframe != NULL); \
|
|
if (!context || (context->type != MWContextAddressBook && \
|
|
context->type != MWContextBookmarks) || \
|
|
!context->bmframe) return 0;
|
|
|
|
#define CHKCONTEXTVOID(context) \
|
|
XP_ASSERT(context != NULL && (context->type == MWContextAddressBook || \
|
|
context->type == MWContextBookmarks) && \
|
|
context->bmframe != NULL); \
|
|
if (!context || (context->type != MWContextAddressBook && \
|
|
context->type != MWContextBookmarks) || \
|
|
!context->bmframe) return;
|
|
|
|
|
|
/* Should probably be a macro, but putting the assert in the macro and
|
|
making this still be easy to use is just too painful. */
|
|
static BM_Frame*
|
|
GETFRAME(MWContext* context)
|
|
{
|
|
XP_ASSERT(context &&
|
|
(context->type == MWContextBookmarks ||
|
|
context->type == MWContextAddressBook) &&
|
|
context->bmframe != NULL);
|
|
return (context && (context->type == MWContextBookmarks ||
|
|
context->type == MWContextAddressBook))
|
|
? context->bmframe : NULL;
|
|
}
|
|
|
|
|
|
static void bm_CancelLastFind(MWContext* context);
|
|
static void bm_InsertItemAfter(MWContext* context, BM_Entry* insert_after,
|
|
BM_Entry* insertee, XP_Bool sync);
|
|
static void bm_SortSelected(MWContext* context, BM_SortType enSortType);
|
|
static void bm_SyncCount(MWContext* context);
|
|
static void bm_AddChildToHeaderSorted(MWContext* context, BM_Entry* parent,
|
|
BM_Entry* child);
|
|
static void bm_SortSilent(MWContext* context, BM_SortType enSortType );
|
|
|
|
|
|
|
|
XP_Bool
|
|
BM_IsHeader(BM_Entry* entry)
|
|
{
|
|
return BM_ISHEADER(entry);
|
|
}
|
|
|
|
XP_Bool
|
|
BM_IsUrl(BM_Entry* entry)
|
|
{
|
|
return BM_ISURL(entry);
|
|
}
|
|
|
|
XP_Bool
|
|
BM_IsAddress(BM_Entry* entry)
|
|
{
|
|
return BM_ISADDRESS(entry);
|
|
}
|
|
|
|
XP_Bool
|
|
BM_IsSeparator(BM_Entry* entry)
|
|
{
|
|
return BM_ISSEPARATOR(entry);
|
|
}
|
|
|
|
XP_Bool
|
|
BM_IsAlias(BM_Entry* entry)
|
|
{
|
|
return BM_ISALIAS(entry);
|
|
}
|
|
|
|
XP_Bool
|
|
BM_IsFolded(BM_Entry* entry)
|
|
{
|
|
return BM_ISFOLDED(entry);
|
|
}
|
|
|
|
XP_Bool
|
|
BM_IsSelected(BM_Entry* entry)
|
|
{
|
|
return BM_ISSELECTED(entry);
|
|
}
|
|
|
|
|
|
int32
|
|
BM_GetChangedState(BM_Entry* entry)
|
|
{
|
|
if (BM_ISALIAS(entry)) {
|
|
entry = entry->d.alias.original;
|
|
}
|
|
if (BM_ISURL(entry)) {
|
|
if (entry->d.url.last_modified == 0 || entry->flags & BM_ATTR_CHECKING) {
|
|
return BM_CHANGED_UNKNOWN;
|
|
}
|
|
if (entry->d.url.last_visit < entry->d.url.last_modified) {
|
|
return BM_CHANGED_YES;
|
|
}
|
|
}
|
|
return BM_CHANGED_NO;
|
|
}
|
|
|
|
/* globals */
|
|
static MWContext* ContextList = NULL;
|
|
|
|
|
|
void
|
|
BM_SetFEData(MWContext* context, void* data)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXTVOID(context);
|
|
f->feData = data;
|
|
}
|
|
|
|
void*
|
|
BM_GetFEData(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXT(context);
|
|
return f->feData;
|
|
}
|
|
|
|
|
|
/* creates a new empty bookmark entry */
|
|
static BM_Entry*
|
|
bm_NewEntry(int16 type)
|
|
{
|
|
BM_Entry* new_entry = XP_NEW_ZAP(BM_Entry);
|
|
if (!new_entry) return NULL;
|
|
XP_MEMSET(new_entry, 0, sizeof(BM_Entry));
|
|
new_entry->type = type;
|
|
return new_entry;
|
|
}
|
|
|
|
/* creates a new header bookmarks entry */
|
|
BM_Entry*
|
|
BM_NewHeader(const char* name)
|
|
{
|
|
BM_Entry* header;
|
|
header = bm_NewEntry(BM_TYPE_HEADER);
|
|
if (!header) return NULL;
|
|
StrAllocCopy(header->name, name);
|
|
return header;
|
|
}
|
|
|
|
/* creates a new URL bookmarks entry */
|
|
BM_Entry*
|
|
BM_NewUrl(const char* name, const char* address, const char* content_type,
|
|
BM_Date last_visit)
|
|
{
|
|
BM_Entry* url;
|
|
|
|
url = bm_NewEntry(BM_TYPE_URL);
|
|
|
|
if (!url) return NULL;
|
|
|
|
StrAllocCopy(url->name, name);
|
|
url->description = NULL;
|
|
StrAllocCopy(url->d.url.address, address);
|
|
StrAllocCopy(url->d.url.content_type, content_type);
|
|
url->d.url.last_visit = last_visit;
|
|
url->d.url.last_modified = last_visit; /* ### Is this right? */
|
|
url->addition_date = 0;
|
|
|
|
return url;
|
|
}
|
|
|
|
/* creates a new URL bookmarks entry */
|
|
static BM_Entry*
|
|
bm_NewAddress(const char* name, const char* address)
|
|
{
|
|
BM_Entry* add_struct;
|
|
|
|
add_struct = bm_NewEntry(BM_TYPE_ADDRESS);
|
|
|
|
if (!add_struct) return NULL;
|
|
|
|
StrAllocCopy(add_struct->name, name ? name : "");
|
|
StrAllocCopy(add_struct->d.address.address, address ? address : "");
|
|
|
|
return add_struct;
|
|
}
|
|
|
|
/* creates a new separator bookmarks entry */
|
|
static BM_Entry*
|
|
bm_NewSeparator(void)
|
|
{
|
|
return bm_NewEntry(BM_TYPE_SEPARATOR);
|
|
}
|
|
|
|
/* creates a new alias bookmarks entry */
|
|
static BM_Entry*
|
|
bm_NewAlias(BM_Entry* original)
|
|
{
|
|
BM_Entry* alias;
|
|
alias = bm_NewEntry(BM_TYPE_ALIAS);
|
|
alias->d.alias.original = original;
|
|
BM_SETFLAG(original, BM_ATTR_HASALIASES);
|
|
return alias;
|
|
}
|
|
|
|
extern BM_Entry* BM_CopyBookmark(MWContext* context, BM_Entry* original)
|
|
{
|
|
BM_Entry* copy;
|
|
BM_Entry* child;
|
|
BM_Entry* childCopy;
|
|
|
|
copy = NULL;
|
|
|
|
if(!original)
|
|
return NULL;
|
|
|
|
switch (original->type) {
|
|
case BM_TYPE_URL:
|
|
copy = BM_NewUrl(original->name, original->d.url.address,
|
|
original->d.url.content_type, original->d.url.last_visit);
|
|
break;
|
|
case BM_TYPE_ADDRESS:
|
|
copy = bm_NewAddress(original->name, original->d.address.address);
|
|
break;
|
|
case BM_TYPE_SEPARATOR:
|
|
copy = bm_NewSeparator();
|
|
break;
|
|
case BM_TYPE_ALIAS:
|
|
copy = bm_NewAlias(original->d.alias.original);
|
|
break;
|
|
case BM_TYPE_HEADER:
|
|
copy = BM_NewHeader(original->name);
|
|
|
|
child = original->d.header.children;
|
|
|
|
while (child) {
|
|
childCopy = BM_CopyBookmark(context, child);
|
|
if(childCopy)
|
|
BM_AppendToHeader(context, copy, childCopy);
|
|
child = child->next;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if(copy)
|
|
{
|
|
StrAllocCopy(copy->description, original->description);
|
|
StrAllocCopy(copy->nickname, original->nickname);
|
|
}
|
|
return copy;
|
|
}
|
|
|
|
/* gets the top node of the bmlist */
|
|
BM_Entry*
|
|
BM_GetRoot(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXT(context);
|
|
if (!f) return NULL;
|
|
if (!f->gBookmarks) {
|
|
f->gBookmarks = BM_NewHeader(context->type == MWContextBookmarks ?
|
|
XP_GetString(XP_BKMKS_HEADER) : XP_GetString(XP_ADDRBOOK_HEADER));
|
|
if (context->type == MWContextBookmarks) {
|
|
f->menuheader = f->addheader = f->gBookmarks;
|
|
}
|
|
bm_SyncCount(context);
|
|
} else {
|
|
if (f->gBookmarks->parent != NULL)
|
|
XP_ASSERT(f->gBookmarks->next == NULL); /* Stupid consistancy test;
|
|
dunno what action to take if
|
|
this isn't so. */
|
|
}
|
|
return f->gBookmarks;
|
|
}
|
|
|
|
static void
|
|
bm_EachEntryDo_1(MWContext* context, BM_Entry* at, EntryFunc func, void* closure)
|
|
{
|
|
BM_Entry* nextChild;
|
|
BM_Entry* children;
|
|
|
|
while (at) {
|
|
nextChild = at->next;
|
|
if (BM_ISHEADER(at)) {
|
|
children = at->d.header.children;
|
|
} else {
|
|
children = NULL;
|
|
}
|
|
|
|
(*func)(context, at, closure);
|
|
|
|
if (children) {
|
|
bm_EachEntryDo_1(context, children, func, closure);
|
|
}
|
|
|
|
at = nextChild;
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_EachSelectedEntryDo_1(MWContext* context, BM_Entry* at, EntryFunc func,
|
|
void* closure)
|
|
{
|
|
BM_Entry* nextChild;
|
|
BM_Entry* children;
|
|
|
|
while (at) {
|
|
nextChild = at->next;
|
|
if (BM_ISHEADER(at)) {
|
|
children = at->d.header.children;
|
|
} else {
|
|
children = NULL;
|
|
}
|
|
|
|
if (BM_ISSELECTED(at)) (*func)(context, at, closure);
|
|
|
|
if (children) {
|
|
bm_EachSelectedEntryDo_1(context, children, func, closure);
|
|
}
|
|
|
|
at = nextChild;
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_EachProperSelectedEntryDo_1(MWContext* context, BM_Entry* at, EntryFunc func,
|
|
void* closure, struct BM_Entry_Focus* bmFocus)
|
|
{
|
|
BM_Entry* child;
|
|
BM_Entry* nextChild;
|
|
|
|
XP_ASSERT(at);
|
|
if (!at) return;
|
|
|
|
XP_ASSERT(BM_ISHEADER(at));
|
|
child = at->d.header.children;
|
|
|
|
while (child) {
|
|
nextChild = child->next;
|
|
switch (child->type) {
|
|
case BM_TYPE_URL:
|
|
case BM_TYPE_ADDRESS:
|
|
case BM_TYPE_SEPARATOR:
|
|
case BM_TYPE_ALIAS:
|
|
if (BM_ISSELECTED(child)) {
|
|
if ((bmFocus != NULL) && !bmFocus->foundSelection)
|
|
bmFocus->foundSelection = TRUE;
|
|
(*func)(context, child, closure);
|
|
}
|
|
else if (bmFocus && !bmFocus->foundSelection) {
|
|
bmFocus->saveFocus = child;
|
|
}
|
|
break;
|
|
case BM_TYPE_HEADER:
|
|
if (BM_ISSELECTED(child)) {
|
|
if ((bmFocus != NULL) && !bmFocus->foundSelection)
|
|
bmFocus->foundSelection = TRUE;
|
|
(*func)(context, child, closure);
|
|
}
|
|
else {
|
|
if (bmFocus && !bmFocus->foundSelection)
|
|
bmFocus->saveFocus = child;
|
|
if (! BM_ISFOLDED(child)) {
|
|
bm_EachProperSelectedEntryDo_1(context, child, func, closure, bmFocus);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
child = nextChild;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
BM_EachEntryDo(MWContext* context, EntryFunc func, void* closure)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
bm_EachEntryDo_1(context, BM_GetRoot(context), func, closure);
|
|
}
|
|
|
|
void
|
|
BM_EachSelectedEntryDo(MWContext* context, EntryFunc func, void* closure)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
bm_EachSelectedEntryDo_1(context, BM_GetRoot(context), func, closure);
|
|
}
|
|
|
|
void
|
|
BM_EachProperSelectedEntryDo(MWContext* context, EntryFunc func, void* closure,
|
|
struct BM_Entry_Focus* bmFocus)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
bm_EachProperSelectedEntryDo_1(context, BM_GetRoot(context), func, closure,
|
|
bmFocus);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
bm_ClearMarkEverywhere_1(MWContext* context, BM_Entry* entry, void* closure)
|
|
{
|
|
BM_CLEARFLAG(entry, BM_ATTR_MARKED);
|
|
}
|
|
|
|
|
|
static void
|
|
bm_ClearMarkEverywhere(MWContext* context)
|
|
{
|
|
BM_EachEntryDo(context, bm_ClearMarkEverywhere_1, NULL);
|
|
}
|
|
|
|
|
|
static MWContext*
|
|
bm_GetContextForEntry(BM_Entry* entry)
|
|
{
|
|
MWContext* result;
|
|
BM_Frame* f;
|
|
while (entry->parent) entry = entry->parent;
|
|
for (result = ContextList ; result ; result = f->next) {
|
|
f = GETFRAME(result);
|
|
if (f->gBookmarks == entry) return result;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
BM_Type
|
|
BM_GetType(BM_Entry* entry)
|
|
{
|
|
XP_ASSERT(entry);
|
|
return entry ? entry->type : 0;
|
|
}
|
|
|
|
|
|
/* return the "name" for item -- may vary depending on its type */
|
|
char*
|
|
BM_GetName(BM_Entry* entry)
|
|
{
|
|
XP_ASSERT(entry);
|
|
if (!entry) return NULL;
|
|
|
|
switch (entry->type) {
|
|
case BM_TYPE_URL:
|
|
case BM_TYPE_HEADER:
|
|
case BM_TYPE_ADDRESS:
|
|
return entry->name;
|
|
case BM_TYPE_ALIAS:
|
|
if (entry->d.alias.original)
|
|
return BM_GetName(entry->d.alias.original);
|
|
else
|
|
return entry->name;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* return the "address" for item -- may vary depending
|
|
on its type */
|
|
char*
|
|
BM_GetAddress(BM_Entry* entry)
|
|
{
|
|
XP_ASSERT(entry);
|
|
if (!entry) return NULL;
|
|
|
|
switch (entry->type) {
|
|
case BM_TYPE_URL:
|
|
return entry->d.url.address;
|
|
case BM_TYPE_ADDRESS:
|
|
return entry->d.address.address;
|
|
case BM_TYPE_HEADER:
|
|
case BM_TYPE_SEPARATOR:
|
|
case BM_TYPE_ALIAS:
|
|
if (entry->d.alias.original) return BM_GetAddress(entry->d.alias.original);
|
|
return NULL;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* return the "target" for item -- may vary depending
|
|
on its type */
|
|
char*
|
|
BM_GetTarget(BM_Entry* entry, XP_Bool recurse)
|
|
{
|
|
XP_ASSERT(entry);
|
|
if (!entry) return NULL;
|
|
|
|
switch (entry->type) {
|
|
case BM_TYPE_URL:
|
|
if ((recurse)&&(entry->d.url.target == NULL)&&(entry->parent != NULL))
|
|
{
|
|
return BM_GetTarget(entry->parent, recurse);
|
|
}
|
|
else
|
|
{
|
|
return entry->d.url.target;
|
|
}
|
|
case BM_TYPE_HEADER:
|
|
if ((recurse)&&(entry->d.header.target == NULL)&&(entry->parent != NULL))
|
|
{
|
|
return BM_GetTarget(entry->parent, recurse);
|
|
}
|
|
else
|
|
{
|
|
return entry->d.header.target;
|
|
}
|
|
case BM_TYPE_ADDRESS:
|
|
case BM_TYPE_SEPARATOR:
|
|
return NULL;
|
|
case BM_TYPE_ALIAS:
|
|
if (entry->d.alias.original) return BM_GetTarget(entry->d.alias.original, recurse);
|
|
return NULL;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* return the "description" for item -- may vary depending on its type */
|
|
char*
|
|
BM_GetDescription(BM_Entry* entry)
|
|
{
|
|
XP_ASSERT(entry);
|
|
if (!entry) return NULL;
|
|
|
|
switch (entry->type) {
|
|
case BM_TYPE_URL:
|
|
case BM_TYPE_HEADER:
|
|
case BM_TYPE_ADDRESS:
|
|
return entry->description;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
char*
|
|
BM_GetNickName(BM_Entry* entry)
|
|
{
|
|
XP_ASSERT(bm_GetContextForEntry(entry)->type == MWContextAddressBook);
|
|
if (BM_ISALIAS(entry)) return BM_GetNickName(entry->d.alias.original);
|
|
else return entry->nickname;
|
|
}
|
|
|
|
|
|
PRIVATE int32
|
|
bm_CountAliases_1(BM_Entry* at, BM_Entry* forEntry)
|
|
{
|
|
int32 count = 0;
|
|
for ( ; at ; at = at->next) {
|
|
if (BM_ISHEADER(at)) {
|
|
count += bm_CountAliases_1(at->d.header.children, forEntry);
|
|
} else if (BM_ISALIAS(at)) {
|
|
if (at->d.alias.original == forEntry) count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
PUBLIC int32
|
|
BM_CountAliases(MWContext* context, BM_Entry* entry)
|
|
{
|
|
int32 result;
|
|
CHKCONTEXT(context);
|
|
result = bm_CountAliases_1(BM_GetRoot(context), entry);
|
|
if (result) {
|
|
XP_ASSERT(entry->flags & BM_ATTR_HASALIASES);
|
|
BM_SETFLAG(entry, BM_ATTR_HASALIASES);
|
|
} else {
|
|
BM_CLEARFLAG(entry, BM_ATTR_HASALIASES);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
BM_Date
|
|
BM_GetLastVisited(BM_Entry *entry)
|
|
{
|
|
XP_ASSERT(entry);
|
|
if (!entry || (entry->type != BM_TYPE_URL)) return 0;
|
|
|
|
return entry->d.url.last_visit;
|
|
}
|
|
|
|
BM_Date
|
|
BM_GetAdditionDate(BM_Entry *entry)
|
|
{
|
|
XP_ASSERT(entry);
|
|
|
|
if (!entry) return 0;
|
|
|
|
return entry->addition_date;
|
|
}
|
|
|
|
|
|
/* pretty print the last visited date ### fix i18n */
|
|
char*
|
|
BM_PrettyLastVisitedDate(BM_Entry* entry)
|
|
{
|
|
static char buffer[200];
|
|
|
|
buffer[0] = 0;
|
|
|
|
XP_ASSERT(entry);
|
|
if (!entry) return NULL;
|
|
|
|
if (entry->type == BM_TYPE_URL) {
|
|
time_t lastVisited;
|
|
time_t today;
|
|
time_t elapsed;
|
|
|
|
lastVisited = entry->d.url.last_visit;
|
|
if (lastVisited == 0) return "";
|
|
today = XP_TIME();
|
|
|
|
elapsed = today - lastVisited;
|
|
|
|
if (elapsed < SECONDS_PER_DAY) {
|
|
int32 hours = (elapsed + 1800L) / 3600L;
|
|
if (hours < 1) {
|
|
return XP_GetString(XP_BKMKS_LESS_THAN_ONE_HOUR_AGO);
|
|
}
|
|
sprintf(buffer, XP_GetString(XP_BKMKS_HOURS_AGO), hours);
|
|
} else if (elapsed < (SECONDS_PER_DAY * 31)) {
|
|
sprintf(buffer, XP_GetString(XP_BKMKS_DAYS_AGO),
|
|
(elapsed + (SECONDS_PER_DAY / 2)) / SECONDS_PER_DAY);
|
|
} else {
|
|
struct tm* tmp;
|
|
tmp = localtime(&lastVisited);
|
|
|
|
sprintf(buffer, asctime(tmp));
|
|
}
|
|
return buffer;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* pretty print the added on date */
|
|
char*
|
|
BM_PrettyAddedOnDate(BM_Entry* entry)
|
|
{
|
|
static char buffer[200];
|
|
struct tm* tmp;
|
|
|
|
XP_ASSERT(entry);
|
|
|
|
if (entry && entry->addition_date != 0) {
|
|
tmp = localtime(&(entry->addition_date));
|
|
sprintf(buffer, asctime(tmp));
|
|
return buffer;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
char*
|
|
BM_PrettyAliasCount(MWContext* context, BM_Entry* entry)
|
|
{
|
|
static char buffer[100];
|
|
int32 count;
|
|
char* name = context->type == MWContextBookmarks ?
|
|
XP_GetString(XP_BKMKS_BOOKMARK) : XP_GetString(XP_BKMKS_ENTRY);
|
|
CHKCONTEXT(context);
|
|
XP_ASSERT(entry);
|
|
if (!entry) return NULL;
|
|
|
|
count = BM_CountAliases(context, entry);
|
|
buffer[0] = 0;
|
|
|
|
if (count > 1) {
|
|
sprintf(buffer, XP_GetString(XP_BKMKS_COUNTALIASES_MANY), count, name);
|
|
} else if (count == 1) {
|
|
sprintf(buffer, XP_GetString(XP_BKMKS_COUNTALIASES_ONE), name);
|
|
} else {
|
|
sprintf(buffer, XP_GetString(XP_BKMKS_COUNTALIASES_NONE), name);
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
|
|
BM_Entry*
|
|
BM_GetChildren(BM_Entry* entry)
|
|
{
|
|
if (BM_ISHEADER(entry)) return entry->d.header.children;
|
|
return NULL;
|
|
}
|
|
|
|
BM_Entry*
|
|
BM_GetNext(BM_Entry* entry)
|
|
{
|
|
XP_ASSERT(entry);
|
|
return entry ? entry->next : NULL;
|
|
}
|
|
|
|
|
|
BM_Entry*
|
|
BM_GetParent(BM_Entry* entry)
|
|
{
|
|
XP_ASSERT(entry);
|
|
return entry ? entry->parent : NULL;
|
|
}
|
|
|
|
|
|
XP_Bool
|
|
BM_HasNext(BM_Entry* entry)
|
|
{
|
|
XP_ASSERT(entry);
|
|
return entry ? (entry->next != NULL) : FALSE;
|
|
}
|
|
|
|
|
|
XP_Bool
|
|
BM_HasPrev(BM_Entry* entry)
|
|
{
|
|
BM_Entry* parent = entry->parent;
|
|
if (parent) {
|
|
XP_ASSERT(BM_ISHEADER(parent));
|
|
return parent->d.header.children != entry;
|
|
} else {
|
|
return GETFRAME(bm_GetContextForEntry(entry))->gBookmarks != entry;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
bm_flush_updates(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXTVOID(context);
|
|
if (f->first_update_line > 0) {
|
|
BMFE_RefreshCells(context, f->first_update_line, f->last_update_line,
|
|
FALSE);
|
|
f->first_update_line = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_start_batch(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
|
|
#ifdef XP_UNIX
|
|
BMFE_StartBatch(context);
|
|
#endif
|
|
|
|
CHKCONTEXTVOID(context);
|
|
if (f->undo) UNDO_StartBatch(f->undo);
|
|
f->batch_depth++;
|
|
}
|
|
|
|
static void
|
|
bm_end_batch(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXTVOID(context);
|
|
f->batch_depth--;
|
|
XP_ASSERT(f->batch_depth >= 0);
|
|
if (f->batch_depth == 0) {
|
|
bm_flush_updates(context);
|
|
}
|
|
if (f->undo) UNDO_EndBatch(f->undo, NULL, NULL);
|
|
|
|
#ifdef XP_UNIX
|
|
BMFE_EndBatch(context);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
bm_refresh(MWContext* context, int32 first, int32 last)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(first >= 1 && first <= last);
|
|
if (first < 1 || first > last) {
|
|
/* Something bogus got passed in; just repaint everything to
|
|
be safe. */
|
|
first = 1;
|
|
last = BM_LAST_CELL;
|
|
}
|
|
if (f->first_update_line <= 0 ||
|
|
first > f->last_update_line + 1 ||
|
|
last + 1 < f->first_update_line) {
|
|
bm_flush_updates(context);
|
|
f->first_update_line = first;
|
|
f->last_update_line = last;
|
|
} else {
|
|
if (f->first_update_line > first) f->first_update_line = first;
|
|
if (f->last_update_line < last) f->last_update_line = last;
|
|
}
|
|
if (f->batch_depth == 0) bm_flush_updates(context);
|
|
}
|
|
|
|
|
|
/* Handy routine to detect if we're already going to refresh everything.
|
|
If so, then the caller knows there's no need to refresh any more... */
|
|
static XP_Bool
|
|
bm_refreshing_all(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
return (f != NULL &&
|
|
f->first_update_line == 1 &&
|
|
f->last_update_line == BM_LAST_CELL);
|
|
}
|
|
|
|
|
|
static void
|
|
bm_entry_changed_2(MWContext* context, BM_Entry* entry)
|
|
{
|
|
int32 index = BM_GetIndex(context, entry);
|
|
if (index < 1) return;
|
|
if (context->type == MWContextBookmarks || entry->parent == NULL) {
|
|
bm_refresh(context, index, index);
|
|
} else {
|
|
/* Changing the entry might have messed up the sorting order. Better
|
|
go resort it. What a hack...*/
|
|
BM_Entry* parent = entry->parent;
|
|
BM_RemoveChildFromHeader(context, parent, entry);
|
|
bm_AddChildToHeaderSorted(context, parent, entry);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_entry_changed_1(MWContext* context, BM_Entry* entry, BM_Entry* find)
|
|
{
|
|
for (; entry ; entry = entry->next) {
|
|
if (BM_ISALIAS(entry) && entry->d.alias.original == find) {
|
|
bm_entry_changed_2(context, entry);
|
|
} else if (BM_ISHEADER(entry)) {
|
|
bm_entry_changed_1(context, entry->d.header.children, find);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_entry_changed(MWContext* context, BM_Entry* entry)
|
|
{
|
|
XP_ASSERT(!BM_ISALIAS(entry));
|
|
if (entry->flags & BM_ATTR_HASALIASES) {
|
|
bm_entry_changed_1(context, BM_GetRoot(context), entry);
|
|
}
|
|
bm_entry_changed_2(context, entry);
|
|
}
|
|
|
|
|
|
static void
|
|
bm_save_timer(void* closure)
|
|
{
|
|
MWContext* context = (MWContext*) closure;
|
|
BM_Frame* f = GETFRAME(context);
|
|
f->savetimer = NULL;
|
|
BM_SaveBookmarks(context, NULL);
|
|
}
|
|
|
|
/* The bookmarks have been modified somehow. Set or reset a timer to cause
|
|
them to be saved.*/
|
|
static void
|
|
bm_SetModified(MWContext* context, XP_Bool mod)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
f->gBookmarksModified = mod;
|
|
f->max_depth = 0;
|
|
if (f->savetimer) {
|
|
FE_ClearTimeout(f->savetimer);
|
|
f->savetimer = NULL;
|
|
}
|
|
if (mod) {
|
|
f->savetimer = FE_SetTimeout(bm_save_timer, context,
|
|
60000L); /* ### hard-coding... */
|
|
if (!f->savetimer) BM_SaveBookmarks(context, NULL);
|
|
}
|
|
}
|
|
|
|
/* give LI the ability to set the modified to false */
|
|
void
|
|
BM_SetModified(MWContext* context, XP_Bool mod)
|
|
{
|
|
bm_SetModified(context, mod);
|
|
}
|
|
|
|
typedef struct bm_setheader_info {
|
|
MWContext* context;
|
|
BM_Entry* entry;
|
|
XP_Bool isadd;
|
|
} bm_setheader_info;
|
|
|
|
static void
|
|
bm_setheader_freeit(void* closure)
|
|
{
|
|
XP_FREE((bm_setheader_info*) closure);
|
|
}
|
|
|
|
static int bm_setheader_undo(void* closure);
|
|
|
|
static void
|
|
bm_SetMenuOrAddHeader(MWContext* context, BM_Entry* entry, XP_Bool isadd)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
XP_ASSERT(context->type == MWContextBookmarks);
|
|
XP_ASSERT(BM_ISHEADER(entry));
|
|
if (context->type == MWContextBookmarks && f && BM_ISHEADER(entry)) {
|
|
if (f->undo) {
|
|
bm_setheader_info* info = XP_NEW_ZAP(bm_setheader_info);
|
|
if (!info) {
|
|
UNDO_DiscardAll(f->undo);
|
|
} else {
|
|
info->context = context;
|
|
info->entry = isadd ? f->addheader : f->menuheader;
|
|
info->isadd = isadd;
|
|
UNDO_LogEvent(f->undo, bm_setheader_undo, bm_setheader_freeit, info, NULL, NULL);
|
|
}
|
|
}
|
|
bm_start_batch(context);
|
|
bm_entry_changed(context, isadd ? f->addheader : f->menuheader);
|
|
if (isadd) f->addheader = entry;
|
|
else f->menuheader = entry;
|
|
bm_entry_changed(context, entry);
|
|
if (!isadd) BMFE_BookmarkMenuInvalid(context);
|
|
bm_SetModified(context, TRUE);
|
|
bm_end_batch(context);
|
|
}
|
|
}
|
|
|
|
int bm_setheader_undo(void* closure)
|
|
{
|
|
bm_setheader_info* info = (bm_setheader_info*) closure;
|
|
bm_SetMenuOrAddHeader(info->context, info->entry, info->isadd);
|
|
return 0;
|
|
}
|
|
|
|
BM_Entry*
|
|
BM_GetMenuHeader(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
XP_ASSERT(context->type == MWContextBookmarks);
|
|
return f ? f->menuheader : NULL;
|
|
}
|
|
|
|
void BM_SetMenuHeader(MWContext* context, BM_Entry* entry)
|
|
{
|
|
bm_SetMenuOrAddHeader(context, entry, FALSE);
|
|
}
|
|
|
|
|
|
BM_Entry*
|
|
BM_GetAddHeader(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
XP_ASSERT(context->type == MWContextBookmarks);
|
|
return f ? f->addheader : NULL;
|
|
}
|
|
|
|
void BM_SetAddHeader(MWContext* context, BM_Entry* entry)
|
|
{
|
|
bm_SetMenuOrAddHeader(context, entry, TRUE);
|
|
}
|
|
|
|
|
|
typedef struct bm_copy_string_info {
|
|
MWContext* context;
|
|
BM_Entry* entry;
|
|
char** string;
|
|
char* value;
|
|
} bm_copy_string_info;
|
|
|
|
|
|
static void
|
|
bm_copy_string_freeit(void* closure)
|
|
{
|
|
bm_copy_string_info* info = (bm_copy_string_info*) closure;
|
|
FREEIF(info->value);
|
|
XP_FREE(info);
|
|
}
|
|
|
|
static int bm_copy_string_undo(void* closure);
|
|
|
|
static int
|
|
bm_CopyStringWithUndo(MWContext* context, BM_Entry* entry, char** string,
|
|
const char* value)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
int status = 0;
|
|
bm_SetModified(context, TRUE);
|
|
if (f->undo) {
|
|
bm_copy_string_info* info = XP_NEW_ZAP(bm_copy_string_info);
|
|
if (!info) {
|
|
UNDO_DiscardAll(f->undo);
|
|
status = MK_OUT_OF_MEMORY;
|
|
} else {
|
|
info->context = context;
|
|
info->entry = entry;
|
|
info->string = string;
|
|
info->value = *string ? XP_STRDUP(*string) : NULL;
|
|
UNDO_LogEvent(f->undo, bm_copy_string_undo, bm_copy_string_freeit,
|
|
info, NULL, NULL);
|
|
}
|
|
}
|
|
if (*string) XP_FREE(*string);
|
|
*string = value ? XP_STRDUP(value) : NULL;
|
|
bm_entry_changed(context, entry);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bm_copy_string_undo(void* closure)
|
|
{
|
|
bm_copy_string_info* info = (bm_copy_string_info*) closure;
|
|
if (info->string == &(info->entry->nickname)) {
|
|
/* Have to use BM_SetNickName to get side effect of changing hashtable. */
|
|
BM_SetNickName(info->context, info->entry, info->value);
|
|
} else {
|
|
bm_CopyStringWithUndo(info->context, info->entry, info->string,
|
|
info->value);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* sets the name for a bm entry */
|
|
void
|
|
BM_SetName(MWContext* context, BM_Entry* entry, const char* newName)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(entry);
|
|
if (!entry) return;
|
|
BM_CLEARFLAG(entry, BM_ATTR_ISNEW);
|
|
|
|
switch (entry->type) {
|
|
case BM_TYPE_URL:
|
|
case BM_TYPE_HEADER:
|
|
case BM_TYPE_ADDRESS:
|
|
if (entry->name == NULL || XP_STRCMP(entry->name, newName) != 0) {
|
|
bm_CopyStringWithUndo(context, entry, &entry->name, newName);
|
|
BMFE_BookmarkMenuInvalid(context);
|
|
}
|
|
break;
|
|
case BM_TYPE_ALIAS:
|
|
BM_SetName(context, entry->d.alias.original, newName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* sets the location field of a bm_url bookmarks entry */
|
|
void
|
|
BM_SetAddress(MWContext* context, BM_Entry* entry, const char* newAddress)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(entry);
|
|
if (!entry) return;
|
|
BM_CLEARFLAG(entry, BM_ATTR_ISNEW);
|
|
|
|
switch (entry->type) {
|
|
case BM_TYPE_URL:
|
|
if (entry->d.url.address == NULL ||
|
|
XP_STRCMP(entry->d.url.address, newAddress) != 0) {
|
|
bm_CopyStringWithUndo(context, entry, &entry->d.url.address, newAddress);
|
|
}
|
|
break;
|
|
case BM_TYPE_ADDRESS:
|
|
if (entry->d.address.address == NULL ||
|
|
XP_STRCMP(entry->d.address.address, newAddress) != 0) {
|
|
bm_CopyStringWithUndo(context, entry, &entry->d.address.address,
|
|
newAddress);
|
|
}
|
|
break;
|
|
case BM_TYPE_ALIAS:
|
|
BM_SetAddress(context, entry->d.alias.original, newAddress);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* sets the target field of a bm_url bookmarks entry */
|
|
void
|
|
BM_SetTarget(MWContext* context, BM_Entry* entry, const char* newTarget)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(entry);
|
|
if (!entry) return;
|
|
BM_CLEARFLAG(entry, BM_ATTR_ISNEW);
|
|
|
|
switch (entry->type) {
|
|
case BM_TYPE_URL:
|
|
if (entry->d.url.target == NULL ||
|
|
XP_STRCMP(entry->d.url.target, newTarget) != 0) {
|
|
bm_CopyStringWithUndo(context, entry, &entry->d.url.target, newTarget);
|
|
if (entry->d.url.target[0] == '\0') {
|
|
entry->d.url.target = NULL;
|
|
}
|
|
}
|
|
break;
|
|
case BM_TYPE_HEADER:
|
|
if (entry->d.header.target == NULL ||
|
|
XP_STRCMP(entry->d.header.target, newTarget) != 0) {
|
|
bm_CopyStringWithUndo(context, entry, &entry->d.header.target, newTarget);
|
|
if (entry->d.header.target[0] == '\0') {
|
|
entry->d.header.target = NULL;
|
|
}
|
|
}
|
|
break;
|
|
case BM_TYPE_ADDRESS:
|
|
break;
|
|
case BM_TYPE_ALIAS:
|
|
BM_SetAddress(context, entry->d.alias.original, newTarget);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* sets the description field of an entry */
|
|
PUBLIC void
|
|
BM_SetDescription(MWContext* context, BM_Entry* entry, const char* newDesc)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(entry);
|
|
if (!entry) return;
|
|
BM_CLEARFLAG(entry, BM_ATTR_ISNEW);
|
|
|
|
switch (entry->type) {
|
|
case BM_TYPE_URL:
|
|
case BM_TYPE_HEADER:
|
|
case BM_TYPE_ADDRESS:
|
|
if (entry->description == NULL ||
|
|
XP_STRCMP(entry->description, newDesc)) {
|
|
bm_CopyStringWithUndo(context, entry, &entry->description, newDesc);
|
|
}
|
|
break;
|
|
case BM_TYPE_ALIAS:
|
|
BM_SetDescription(context, entry->d.alias.original, newDesc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* BM_SetNickName returns FALSE if it reported an error to the user .
|
|
* It returns TRUE if everything worked fine.
|
|
* 5-16-95 jefft
|
|
* Passing in NULL value removes the entry from the hash table.
|
|
*/
|
|
|
|
XP_Bool
|
|
BM_SetNickName(MWContext* context, BM_Entry* entry, const char* value)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
char* pName;
|
|
CHKCONTEXT(context);
|
|
XP_ASSERT(context->type == MWContextAddressBook);
|
|
if (!entry) return(TRUE);
|
|
if (!value) {
|
|
/* 5-16-95 jefft -- bug#: 20808, remove entry from the hash table */
|
|
if (entry->nickname && *entry->nickname) {
|
|
XP_Remhash(f->nicknameTable, entry->nickname);
|
|
FREEIF(entry->nickname);
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
/* allocate a copy of the string so we can modify it to be a legal alias */
|
|
/* But only if value is non-null */
|
|
pName = (value) ? XP_STRDUP(value) : NULL;
|
|
if (value && !pName) return(FALSE);
|
|
|
|
BM_CLEARFLAG(entry, BM_ATTR_ISNEW);
|
|
|
|
if (BM_ISALIAS(entry)) {
|
|
XP_Bool retVal;
|
|
retVal = BM_SetNickName(context, entry->d.alias.original, value);
|
|
XP_FREE(pName);
|
|
return(retVal);
|
|
} else {
|
|
if (entry->nickname == NULL || value == NULL || XP_STRCMP(entry->nickname, value)) {
|
|
if (pName != NULL)
|
|
{
|
|
char* ptr;
|
|
for (ptr = pName ; *ptr ; ptr++) {
|
|
if (!isalnum(*ptr) && (*ptr != '-') && (*ptr != '_')) {
|
|
FE_Alert(context, XP_GetString(XP_BKMKS_INVALID_NICKNAME));
|
|
XP_FREE(pName);
|
|
return(FALSE);
|
|
}
|
|
/* convert to lowercase */
|
|
if (isupper(*ptr)) {
|
|
*ptr = (char)tolower(*ptr);
|
|
}
|
|
}
|
|
if (XP_Gethash(f->nicknameTable, pName, NULL)) {
|
|
FE_Alert(context, XP_GetString(XP_BKMKS_NICKNAME_ALREADY_EXISTS));
|
|
FREEIF(pName);
|
|
return(FALSE);
|
|
}
|
|
}
|
|
}
|
|
if (entry->nickname && *entry->nickname) {
|
|
XP_Remhash(f->nicknameTable, entry->nickname);
|
|
}
|
|
bm_CopyStringWithUndo(context, entry, &entry->nickname, pName);
|
|
if (entry->nickname && *entry->nickname) {
|
|
XP_Puthash(f->nicknameTable, entry->nickname, entry);
|
|
}
|
|
}
|
|
FREEIF(pName);
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
BM_CancelEdit(MWContext* context, BM_Entry* entry)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
bm_start_batch(context);
|
|
if (entry && (entry->flags & BM_ATTR_ISNEW) &&
|
|
!(entry->flags & BM_ATTR_HASALIASES) && entry->parent) {
|
|
BM_RemoveChildFromHeader(context, entry->parent, entry);
|
|
BM_FreeEntry(context, entry);
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
}
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
|
|
/* returns the number of children parent has
|
|
if visible is TRUE, only visible children are counted,
|
|
otherwise all children are counted */
|
|
static int32
|
|
bm_CountChildren(BM_Entry* parent, XP_Bool visible)
|
|
{
|
|
BM_Entry* child;
|
|
int32 count = 1;
|
|
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(BM_ISHEADER(parent));
|
|
|
|
if (!parent || !BM_ISHEADER(parent)) return 0;
|
|
|
|
if (!visible || !(parent->flags & BM_ATTR_FOLDED)) {
|
|
child = parent->d.header.children;
|
|
while (child) {
|
|
if (BM_ISHEADER(child)) {
|
|
count += bm_CountChildren(child, visible);
|
|
} else {
|
|
count++;
|
|
}
|
|
child = child->next;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
static void
|
|
bm_WidestEntry_1(MWContext* context, BM_Entry* parent, BM_Entry** widest,
|
|
uint32* widestWidth)
|
|
{
|
|
BM_Entry* child;
|
|
uint32 width;
|
|
uint32 height;
|
|
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(BM_ISHEADER(parent));
|
|
XP_ASSERT(widestWidth);
|
|
XP_ASSERT(widest);
|
|
|
|
BMFE_MeasureEntry(context, parent, &width, &height);
|
|
|
|
if (width > *widestWidth) {
|
|
*widestWidth = width;
|
|
*widest = parent;
|
|
}
|
|
|
|
if (!(BM_ISFOLDED(parent))) {
|
|
child = parent->d.header.children;
|
|
while (child) {
|
|
if (BM_ISHEADER(child)) {
|
|
bm_WidestEntry_1(context, child, widest, widestWidth);
|
|
} else {
|
|
BMFE_MeasureEntry(context, child, &width, &height);
|
|
if (width > *widestWidth) {
|
|
*widestWidth = width;
|
|
*widest = child;
|
|
}
|
|
}
|
|
child = child->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* returns the widest visible entry in the tree
|
|
(this uses a FE function to measure the width) */
|
|
PUBLIC BM_Entry*
|
|
BM_WidestEntry(MWContext* context)
|
|
{
|
|
BM_Entry* widest = NULL;
|
|
uint32 widestWidth = 0;
|
|
CHKCONTEXT(context);
|
|
|
|
bm_WidestEntry_1(context, BM_GetRoot(context), &widest, &widestWidth);
|
|
return widest;
|
|
}
|
|
|
|
|
|
static void
|
|
bm_SyncCount(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
if (f) {
|
|
f->gCount = -1;
|
|
f->gVisCount = -1;
|
|
}
|
|
BMFE_SyncDisplay(context);
|
|
}
|
|
|
|
PRIVATE void
|
|
bm_SyncSelection_1(BM_Entry* parent, int32* count, uint32* selectionMask)
|
|
{
|
|
BM_Entry* child;
|
|
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(BM_ISHEADER(parent));
|
|
|
|
if (parent->flags & BM_ATTR_SELECTED) {
|
|
*selectionMask |= BM_TYPE_HEADER;
|
|
(*count)++;
|
|
}
|
|
|
|
child = parent->d.header.children;
|
|
while (child) {
|
|
if (BM_ISHEADER(child)) {
|
|
bm_SyncSelection_1(child, count, selectionMask);
|
|
} else {
|
|
if (BM_ISSELECTED(child)) {
|
|
*selectionMask |= child->type;
|
|
(*count)++;
|
|
}
|
|
}
|
|
child = child->next;
|
|
}
|
|
}
|
|
|
|
/* synchronizes the selection mask and the selection count with
|
|
what is actually selected
|
|
this is necessary when items become deselected because
|
|
we don't have a global selection list, only a count and
|
|
mask
|
|
*/
|
|
static void
|
|
bm_SyncSelection(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
f->gSelectionCount = 0;
|
|
f->gSelectionMask = 0;
|
|
|
|
bm_SyncSelection_1(BM_GetRoot(context), &(f->gSelectionCount),
|
|
&(f->gSelectionMask));
|
|
}
|
|
|
|
/* return the index number of item in cur_count with regards
|
|
to the BM_ATTR_FOLDED flag */
|
|
PRIVATE int32
|
|
bm_GetIndexNum(BM_Entry* parent, BM_Entry* item, int32* cur_count)
|
|
{
|
|
BM_Entry* child;
|
|
int32 rv = 0;
|
|
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(BM_ISHEADER(parent));
|
|
|
|
child = parent->d.header.children;
|
|
|
|
if (parent == item) return *cur_count;
|
|
|
|
while (child) {
|
|
(*cur_count)++;
|
|
|
|
if (child == item) {
|
|
return *cur_count;
|
|
}
|
|
|
|
/* if it's a header and it's unfolded, traverse it's children */
|
|
if (child->type == BM_TYPE_HEADER && !BM_ISFOLDED(child)) {
|
|
rv = bm_GetIndexNum(child, item, cur_count);
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
child = child->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* return the index number of item in cur_count without regards to
|
|
the BM_ATTR_FOLDED flag */
|
|
PRIVATE int32
|
|
bm_GetUnfoldedIndexNum(BM_Entry* parent, BM_Entry* item, int32* cur_count)
|
|
{
|
|
BM_Entry* child;
|
|
int32 rv = 0;
|
|
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(parent->type == BM_TYPE_HEADER);
|
|
|
|
if (parent == item) return *cur_count;
|
|
|
|
for (child = parent->d.header.children; child; child = child->next) {
|
|
(*cur_count)++;
|
|
|
|
if (child == item) return *cur_count;
|
|
|
|
|
|
if (child->type == BM_TYPE_HEADER) {
|
|
rv = bm_GetUnfoldedIndexNum(child, item, cur_count);
|
|
if (rv) return rv;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* returns the child url entry of parent whose address is the same as
|
|
url_address */
|
|
PRIVATE void
|
|
bm_FindItemStub(MWContext *context, BM_Entry* parent, char* url_address, EntryFunc pf, void *pClosure)
|
|
{
|
|
BM_Entry* child;
|
|
|
|
if (!parent) { /* Eric made me do it */
|
|
return;
|
|
}
|
|
|
|
for (child = parent->d.header.children; child; child = child->next) {
|
|
if (child->type == BM_TYPE_URL && child->d.url.address &&
|
|
!XP_STRCMP(child->d.url.address, url_address)) {
|
|
(*pf)(context, child, pClosure);
|
|
}
|
|
if (child->type == BM_TYPE_HEADER) {
|
|
bm_FindItemStub(context, child, url_address, pf, pClosure);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
PRIVATE int32
|
|
bm_GetDepth(BM_Entry* parent, BM_Entry* item)
|
|
{
|
|
int32 rv = 0;
|
|
BM_Entry* next;
|
|
|
|
if (!item) return -1;
|
|
|
|
next = item;
|
|
while (next && next->parent) { /* I think extra "next &&" is
|
|
necessary for Win16 busted
|
|
optimizer... */
|
|
rv++;
|
|
next = next->parent;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
bm_simple_freeit(void* closure)
|
|
{
|
|
XP_FREE(closure);
|
|
}
|
|
|
|
|
|
typedef struct bm_delete_child_info {
|
|
MWContext* context;
|
|
BM_Entry* parent;
|
|
BM_Entry* child;
|
|
} bm_delete_child_info;
|
|
|
|
|
|
static int
|
|
bm_delete_child_doit(void* closure)
|
|
{
|
|
bm_delete_child_info* info = (bm_delete_child_info*) closure;
|
|
BM_RemoveChildFromHeader(info->context, info->parent, info->child);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
bm_LogDeleteChild(MWContext* context, BM_Entry* parent, BM_Entry* child)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
bm_delete_child_info* info;
|
|
|
|
/* Magic side effect -- if a child has just been added, and it doesn't have
|
|
an addition date set, set it to be now. */
|
|
if (child->addition_date == 0) {
|
|
child->addition_date = XP_TIME();
|
|
}
|
|
|
|
if (!f || !f->undo) return;
|
|
bm_SetModified(context, TRUE);
|
|
info = XP_NEW_ZAP(bm_delete_child_info);
|
|
if (!info) {
|
|
UNDO_DiscardAll(f->undo);
|
|
} else {
|
|
info->context = context;
|
|
info->parent = parent;
|
|
info->child = child;
|
|
UNDO_LogEvent(f->undo, bm_delete_child_doit, bm_simple_freeit, info, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
/* appends a child item to a parent at the end of the
|
|
parents child list */
|
|
static void
|
|
bm_AppendChildToHeader(MWContext* context, BM_Entry* parent, BM_Entry* child)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* lastChild;
|
|
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(BM_ISHEADER(parent));
|
|
XP_ASSERT(child);
|
|
XP_ASSERT(child != parent);
|
|
|
|
f->gCount = -1;
|
|
f->gVisCount = -1;
|
|
|
|
lastChild = parent->d.header.lastChild;
|
|
if (lastChild) {
|
|
lastChild->next = child;
|
|
parent->d.header.lastChild = child;
|
|
} else {
|
|
parent->d.header.children = child;
|
|
parent->d.header.lastChild = child;
|
|
}
|
|
|
|
parent->d.header.childCount++;
|
|
child->parent = parent;
|
|
|
|
if( !f->bSorting )
|
|
child->iNaturalIndex = g_iNaturalIndexPool++;
|
|
|
|
if (context) {
|
|
BMFE_BookmarkMenuInvalid(context);
|
|
bm_LogDeleteChild(context, parent, child);
|
|
}
|
|
}
|
|
|
|
void
|
|
BM_AppendToHeader(MWContext* context, BM_Entry* parent, BM_Entry* child)
|
|
{
|
|
int index;
|
|
bm_start_batch(context);
|
|
bm_AppendChildToHeader(context, parent, child);
|
|
index = BM_GetIndex(context, child);
|
|
if (index > 0) bm_refresh(context, index, BM_LAST_CELL);
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
/* Add a child item to a parent at the beginning of the
|
|
parents child list */
|
|
void
|
|
BM_PrependChildToHeader(MWContext* context, BM_Entry* parent, BM_Entry* child)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* firstChild;
|
|
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(parent->type == BM_TYPE_HEADER);
|
|
XP_ASSERT(child);
|
|
XP_ASSERT(child != parent);
|
|
|
|
f->gCount = -1;
|
|
f->gVisCount = -1;
|
|
firstChild = parent->d.header.children;
|
|
if (!firstChild) {
|
|
bm_AppendChildToHeader(context, parent, child);
|
|
} else {
|
|
child->next = firstChild;
|
|
parent->d.header.children = child;
|
|
|
|
parent->d.header.childCount++;
|
|
child->parent = parent;
|
|
|
|
if( !f->bSorting )
|
|
child->iNaturalIndex = g_iNaturalIndexPool++;
|
|
|
|
if (context) {
|
|
BMFE_BookmarkMenuInvalid(context);
|
|
bm_LogDeleteChild(context, parent, child);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
bm_SortAddressBook(const void* obj1, const void* obj2)
|
|
{
|
|
const BM_Entry* entry1 = (const BM_Entry*) obj1;
|
|
const BM_Entry* entry2 = (const BM_Entry*) obj2;
|
|
|
|
if (BM_ISALIAS(entry1)) {
|
|
entry1 = entry1->d.alias.original;
|
|
XP_ASSERT(!BM_ISALIAS(entry1));
|
|
}
|
|
if (BM_ISALIAS(entry2)) {
|
|
entry2 = entry2->d.alias.original;
|
|
XP_ASSERT(!BM_ISALIAS(entry2));
|
|
}
|
|
XP_ASSERT(BM_ISHEADER(entry1) || BM_ISADDRESS(entry1));
|
|
XP_ASSERT(BM_ISHEADER(entry2) || BM_ISADDRESS(entry2));
|
|
if (entry1 == entry2) return 0; /* Can happen with two aliases to the same
|
|
thing... */
|
|
if (BM_ISHEADER(entry1)) {
|
|
if (BM_ISHEADER(entry2)) {
|
|
#ifdef INTL_SORT
|
|
return XP_StrColl(entry1->name, entry2->name);
|
|
#else
|
|
return XP_STRCMP(entry1->name, entry2->name);
|
|
#endif
|
|
} else {
|
|
return 1;
|
|
}
|
|
} else {
|
|
if (BM_ISHEADER(entry2)) {
|
|
return -1;
|
|
} else {
|
|
#ifdef INTL_SORT
|
|
return XP_StrColl(entry1->name, entry2->name);
|
|
#else
|
|
return XP_STRCMP(entry1->name, entry2->name);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static BM_Entry*
|
|
bm_RealEntry(BM_Entry* entry)
|
|
{
|
|
if (BM_ISALIAS(entry)) return entry->d.alias.original;
|
|
else return entry;
|
|
}
|
|
|
|
|
|
/* Adds a child item to a parent, sorting it according to address book sorting
|
|
rules. */
|
|
static void
|
|
bm_AddChildToHeaderSorted(MWContext* context, BM_Entry* parent,
|
|
BM_Entry* child)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* entry;
|
|
BM_Entry* previous = NULL;
|
|
XP_ASSERT(context->type == MWContextAddressBook);
|
|
XP_ASSERT(BM_ISHEADER(parent));
|
|
if (!BM_ISALIAS(child)) parent = BM_GetRoot(context);
|
|
if (parent->d.header.lastChild &&
|
|
bm_SortAddressBook(parent->d.header.lastChild, child) < 0) {
|
|
/* Ah, the most common case (especially when loading from a file). This
|
|
kid goes last. */
|
|
previous = parent->d.header.lastChild;
|
|
bm_AppendChildToHeader(context, parent, child);
|
|
} else {
|
|
for (entry = parent->d.header.children ; entry ; entry = entry->next) {
|
|
int value = bm_SortAddressBook(entry, child);
|
|
if (value > 0) break;
|
|
if (value == 0) {
|
|
/* Hmm. Let's not allow any duplicate aliases to the same thing
|
|
in the same header. */
|
|
if (bm_RealEntry(entry) == bm_RealEntry(child)) {
|
|
if (BM_ISALIAS(child)) {
|
|
BM_FreeEntry(context, child);
|
|
} else {
|
|
XP_ASSERT(BM_ISALIAS(entry));
|
|
BM_RemoveChildFromHeader(context, parent, entry);
|
|
bm_AddChildToHeaderSorted(context, parent, child);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
previous = entry;
|
|
}
|
|
if (previous == NULL) {
|
|
BM_PrependChildToHeader(context, parent, child);
|
|
previous = parent;
|
|
} else {
|
|
bm_InsertItemAfter(context, previous, child, FALSE);
|
|
}
|
|
}
|
|
f->gCount = -1;
|
|
f->gVisCount = -1;
|
|
if (!BM_ISFOLDED(parent) && !bm_refreshing_all(context)) {
|
|
int index = BM_GetIndex(context, previous);
|
|
if (index > 0) {
|
|
f->gVisCount++;
|
|
bm_refresh(context, index + 1, BM_LAST_CELL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static BM_Entry*
|
|
bm_get_previous(BM_Entry* entry)
|
|
{
|
|
BM_Entry* child;
|
|
BM_Entry* previous = NULL;
|
|
|
|
if (entry && entry->parent) {
|
|
child = entry->parent->d.header.children;
|
|
previous = NULL;
|
|
while (child && child != entry) {
|
|
previous = child;
|
|
child = child->next;
|
|
}
|
|
}
|
|
|
|
if (child == NULL) previous = NULL;
|
|
|
|
return previous;
|
|
}
|
|
|
|
|
|
|
|
typedef struct bm_add_child_info {
|
|
MWContext* context;
|
|
BM_Entry* parent;
|
|
BM_Entry* previous;
|
|
BM_Entry* child;
|
|
} bm_add_child_info;
|
|
|
|
|
|
static int
|
|
bm_add_child_doit(void* closure)
|
|
{
|
|
bm_add_child_info* info = (bm_add_child_info*) closure;
|
|
XP_ASSERT(info->previous == NULL || info->previous->parent == info->parent);
|
|
if (info->previous) {
|
|
bm_InsertItemAfter(info->context, info->previous, info->child, FALSE);
|
|
} else {
|
|
BM_PrependChildToHeader(info->context, info->parent, info->child);
|
|
}
|
|
BM_ClearAllSelection(info->context, FALSE);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
void BM_RemoveChildFromHeader(MWContext* context, BM_Entry* parent, BM_Entry* child)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* previous;
|
|
|
|
XP_ASSERT(BM_ISHEADER(parent));
|
|
if (!BM_ISHEADER(parent)) return;
|
|
XP_ASSERT(child);
|
|
if (!child) return;
|
|
XP_ASSERT(child != parent);
|
|
if (child == parent) return;
|
|
XP_ASSERT(child->parent == parent);
|
|
if (child->parent != parent) return;
|
|
|
|
if (context && (child->flags & BM_ATTR_SELECTED)) {
|
|
BM_SelectItem(context, child, TRUE, TRUE, FALSE);
|
|
}
|
|
previous = bm_get_previous(child);
|
|
|
|
if (previous) previous->next = child->next;
|
|
|
|
if (parent->d.header.children == child) {
|
|
parent->d.header.children = child->next;
|
|
}
|
|
|
|
if (parent->d.header.lastChild == child) {
|
|
parent->d.header.lastChild = previous;
|
|
}
|
|
|
|
f->gCount = -1;
|
|
f->gVisCount = -1;
|
|
|
|
parent->d.header.childCount--;
|
|
|
|
if (context) {
|
|
BM_Frame* f = GETFRAME(context);
|
|
bm_add_child_info* info;
|
|
BMFE_BookmarkMenuInvalid(context);
|
|
bm_SetModified(context, TRUE);
|
|
if (f->undo) {
|
|
info = XP_NEW_ZAP(bm_add_child_info);
|
|
if (!info) {
|
|
UNDO_DiscardAll(f->undo);
|
|
} else {
|
|
info->context = context;
|
|
info->parent = parent;
|
|
info->previous = previous;
|
|
info->child = child;
|
|
UNDO_LogEvent(f->undo, bm_add_child_doit, bm_simple_freeit, info, NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
child->parent = NULL;
|
|
child->next = NULL;
|
|
}
|
|
|
|
|
|
|
|
#define BM_HEADER_BEGIN 0xD000
|
|
#define BM_HEADER_END 0xE000
|
|
#define BM_UNKNOWN 0xF000
|
|
|
|
static uint16
|
|
bm_tokenize_line(MWContext* context, char* buffer, char** ptr)
|
|
{
|
|
if ((*ptr = strcasestr(buffer, "HREF=\""))) {
|
|
return context->type == MWContextBookmarks ? BM_TYPE_URL : BM_TYPE_ADDRESS;
|
|
} else if ((*ptr = strcasestr(buffer, "<H")) && isdigit(*(*ptr + 2))) {
|
|
return BM_TYPE_HEADER;
|
|
} else if ((*ptr = strcasestr(buffer, "<HR>"))) {
|
|
return BM_TYPE_SEPARATOR;
|
|
} else if (strcasestr(buffer, "</UL>") ||
|
|
strcasestr(buffer, "</MENU>") ||
|
|
strcasestr(buffer, "</DL>")) {
|
|
return BM_HEADER_END;
|
|
} else if (strcasestr(buffer, "<UL>") ||
|
|
strcasestr(buffer, "<MENU>") ||
|
|
strcasestr(buffer, "<DL>")) {
|
|
return BM_HEADER_BEGIN;
|
|
} else {
|
|
return BM_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
/* parse out the folded state in buffer */
|
|
static XP_Bool
|
|
bm_is_folded(char* buffer)
|
|
{
|
|
XP_ASSERT(buffer);
|
|
return strcasestr(buffer, "FOLDED") != NULL;
|
|
}
|
|
|
|
/* parse out the addition date in buffer */
|
|
static time_t
|
|
bm_addition_date(char* buffer)
|
|
{
|
|
char* ptr;
|
|
char* end;
|
|
time_t add_date = 0;
|
|
|
|
XP_ASSERT(buffer);
|
|
|
|
ptr = strcasestr(buffer, "ADD_DATE=\"");
|
|
if (ptr)
|
|
{
|
|
/* find the end of the addition date */
|
|
end = XP_STRCHR(ptr + 10, '"');
|
|
if (end)
|
|
{
|
|
/* temporarily stick a NULL in the buffer */
|
|
*end = '\0';
|
|
|
|
add_date = (time_t)atol(ptr + 10);
|
|
|
|
/* replace the quote */
|
|
*end = '"';
|
|
}
|
|
}
|
|
return add_date;
|
|
}
|
|
|
|
/* parse out the last visited or last modified date in buffer */
|
|
static time_t
|
|
bm_last_date(char* buffer, XP_Bool ismodified)
|
|
{
|
|
char* ptr;
|
|
char* start;
|
|
char* end;
|
|
time_t result = 0;
|
|
|
|
ptr = strcasestr(buffer,
|
|
ismodified ? "LAST_MODIFIED=\"": "LAST_VISIT=\"");
|
|
if (ptr) {
|
|
start = ptr + (ismodified ? 15 : 12);
|
|
end = XP_STRCHR(start, '"');
|
|
if (end) {
|
|
/* temporarily stick a NULL in the buffer */
|
|
*end = '\0';
|
|
|
|
result = (time_t)atol(start);
|
|
|
|
/* replace the quote */
|
|
*end = '"';
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/* parse out the target string in buffer */
|
|
static char *
|
|
bm_target(char* buffer, XP_Bool ismodified)
|
|
{
|
|
char* ptr;
|
|
char* start;
|
|
char* end;
|
|
char *result = NULL;
|
|
|
|
ptr = strcasestr(buffer, "TARGET=\"");
|
|
if (ptr) {
|
|
start = ptr + 8;
|
|
end = XP_STRCHR(start, '"');
|
|
if (end) {
|
|
/* temporarily stick a NULL in the buffer */
|
|
*end = '\0';
|
|
|
|
result = (start) ? XP_STRDUP(start) : NULL;
|
|
|
|
/* replace the quote */
|
|
*end = '"';
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
typedef struct bm_alias_info {
|
|
char* id; /* String to use for this alias in the file. */
|
|
char* key; /* Key to use to lookup this alias in the table */
|
|
BM_Entry* entry;
|
|
} bm_alias_info;
|
|
|
|
|
|
static bm_alias_info*
|
|
bm_find_alias_info(MWContext* context, const char* ptr, XP_Bool create)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
bm_alias_info* info = NULL;
|
|
if (f->aliasTable) {
|
|
info = (bm_alias_info *)XP_Gethash(f->aliasTable, ptr, NULL);
|
|
XP_ASSERT(info == NULL || XP_STRCMP(ptr, info->key) == 0);
|
|
if (!info && create) {
|
|
info = XP_NEW_ZAP(bm_alias_info);
|
|
if (info) {
|
|
info->key = XP_STRDUP(ptr);
|
|
XP_Puthash(f->aliasTable, info->key, info);
|
|
}
|
|
}
|
|
}
|
|
return info;
|
|
}
|
|
|
|
static bm_alias_info*
|
|
bm_find_writealias_info(MWContext* context, BM_Entry* entry)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
static char key[20];
|
|
bm_alias_info* info;
|
|
XP_SPRINTF(key, "%ld", (long) entry);
|
|
info = bm_find_alias_info(context, key, TRUE);
|
|
if (info && info->id == NULL) {
|
|
info->id = (char *)XP_ALLOC(10);
|
|
if (info->id) {
|
|
XP_SPRINTF(info->id, "%d", f->aliasID++);
|
|
}
|
|
}
|
|
return info;
|
|
}
|
|
|
|
static XP_Bool
|
|
bm_free_alias_info(XP_HashTable table, const void* key, void* value,
|
|
void* closure)
|
|
{
|
|
bm_alias_info* info = (bm_alias_info*) value;
|
|
FREEIF(info->id);
|
|
FREEIF(info->key);
|
|
XP_FREE(info);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static int
|
|
bm_string_cmp (const void *obj1, const void *obj2)
|
|
{
|
|
XP_ASSERT (obj1 && obj2);
|
|
return XP_STRCMP((char*) obj1, (char*) obj2);
|
|
}
|
|
|
|
static void
|
|
bm_clear_alias_info(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
if (f->aliasTable) {
|
|
XP_Maphash(f->aliasTable, bm_free_alias_info, NULL);
|
|
XP_Clrhash(f->aliasTable);
|
|
} else {
|
|
f->aliasTable = XP_HashTableNew(100, XP_StringHash, bm_string_cmp);
|
|
}
|
|
f->aliasID = 0;
|
|
}
|
|
|
|
|
|
|
|
/* Checks if the given item is an alias to another item, or has aliases to it.
|
|
Takes care of all the required tree mucking, and updating of the alias
|
|
table. The return value is the item for the caller to insert into the
|
|
tree; it is usually but not always the item passed in. */
|
|
static BM_Entry*
|
|
bm_check_read_alias(MWContext* context, BM_Entry* new_item, char* parseString)
|
|
{
|
|
char* ptr;
|
|
char* end = NULL;
|
|
ptr = strcasestr(parseString, "ALIASID=\"");
|
|
if (ptr) {
|
|
ptr += 9;
|
|
end = XP_STRCHR(ptr, '"');
|
|
if (end) {
|
|
bm_alias_info* info;
|
|
*end = '\0';
|
|
info = bm_find_alias_info(context, ptr, TRUE);
|
|
if (info) {
|
|
if (info->entry) {
|
|
/* Sigh. We have the definition of an alias, but there
|
|
have already been some references to this alias, and we don't
|
|
really want to go chasing the references down. Instead, we'll
|
|
just copy our data into the existing record. Yikes. */
|
|
BM_Entry* tmp = XP_NEW(BM_Entry);
|
|
if (tmp) {
|
|
XP_MEMCPY(tmp, info->entry, sizeof(BM_Entry));
|
|
XP_MEMCPY(info->entry, new_item, sizeof(BM_Entry));
|
|
BM_FreeEntry(context, tmp);
|
|
XP_FREE(new_item);
|
|
new_item = info->entry;
|
|
}
|
|
} else {
|
|
info->entry = new_item;
|
|
}
|
|
}
|
|
BM_SETFLAG(new_item, BM_ATTR_HASALIASES);
|
|
}
|
|
} else {
|
|
ptr = strcasestr(parseString, "ALIASOF=\"");
|
|
if (ptr) {
|
|
ptr += 9;
|
|
end = XP_STRCHR(ptr, '"');
|
|
if (end) {
|
|
bm_alias_info* info;
|
|
*end = '\0';
|
|
info = bm_find_alias_info(context, ptr, TRUE);
|
|
if (info) {
|
|
if (info->entry == NULL) {
|
|
/* Even though this entry is probably not complete, it will
|
|
do better than nothing. If something better comes along,
|
|
this one will get replaced. If not, we'll insert this
|
|
item into the main tree at the end. */
|
|
info->entry = new_item;
|
|
} else {
|
|
BM_FreeEntry(context, new_item);
|
|
}
|
|
new_item = bm_NewAlias(info->entry);
|
|
if (!new_item) return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* replace the quotes */
|
|
if (end) *end = '"';
|
|
return new_item;
|
|
}
|
|
|
|
static void
|
|
bm_check_nickname(MWContext* context, BM_Entry* entry, char* str)
|
|
{
|
|
char* ptr = strcasestr(str, "NICKNAME=\"");
|
|
char* end;
|
|
if (ptr) {
|
|
ptr += 10;
|
|
end = XP_STRCHR(ptr, '"');
|
|
if (end) {
|
|
*end = '\0';
|
|
BM_SetNickName(context, entry, ptr);
|
|
*end = '"';
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
// Replace all occurances of escaped quotes (%22) with explicit quotes (").
|
|
// Do not replace beyond the end of the " delimited string.
|
|
//
|
|
// Return a ptr to the position after the last occurance of an escaped quote.
|
|
*/
|
|
static char *bm_explicit_quotes( char *pszSource )
|
|
{
|
|
char *pszCsr = NULL;
|
|
char *pszLast = NULL;
|
|
|
|
if( !pszSource )
|
|
{
|
|
return pszSource;
|
|
}
|
|
|
|
|
|
pszLast = XP_STRCHR( pszSource, '"' );
|
|
|
|
while( (pszCsr = strstr( pszSource, "%22" )) && (pszCsr < pszLast) )
|
|
{
|
|
*pszCsr = '"';
|
|
pszSource = pszCsr + 1;
|
|
XP_MEMMOVE( pszSource, pszSource+2, XP_STRLEN(pszSource+2)+1 );
|
|
}
|
|
|
|
return pszSource;
|
|
}
|
|
|
|
|
|
static BM_Entry*
|
|
bm_read_url(MWContext* context, XP_File fp, char* buffer, char* ptr,
|
|
const char* relative_url)
|
|
{
|
|
char* endQuote;
|
|
char* gtr_than;
|
|
char* parseString;
|
|
char* end;
|
|
char* url;
|
|
char *pszAfterLastEscapedQuote;
|
|
BM_Entry* new_item = NULL;
|
|
|
|
/* find next quote */
|
|
parseString = ptr + 6;
|
|
|
|
/* Replace escaped quotes with explicit ones */
|
|
pszAfterLastEscapedQuote = bm_explicit_quotes( parseString );
|
|
|
|
endQuote = XP_STRCHR(pszAfterLastEscapedQuote, '"');
|
|
|
|
if (endQuote) {
|
|
|
|
/* temporarily terminate */
|
|
*endQuote = '\0';
|
|
|
|
url = NET_MakeAbsoluteURL((char*)relative_url, parseString);
|
|
if (url) {
|
|
new_item = BM_NewUrl(NULL, url, NULL, 0);
|
|
XP_FREE(url);
|
|
}
|
|
if (!new_item) return NULL;
|
|
|
|
/* find '>' and the name will be right after it */
|
|
gtr_than = XP_STRCHR(endQuote + 1, '>');
|
|
if (gtr_than) {
|
|
/* find the end of the name */
|
|
end = strcasestr(gtr_than, "</A>");
|
|
if (end) {
|
|
*end = '\0';
|
|
} else {
|
|
/* This must be a long title.
|
|
* Don't bother using the rest if it.
|
|
*/
|
|
char *dumpbuf = (char*) XP_ALLOC(READ_BUFFER_SIZE);
|
|
while (XP_FileReadLine(dumpbuf, READ_BUFFER_SIZE, fp)) {
|
|
if (strchr(dumpbuf, '\n'))
|
|
break;
|
|
}
|
|
XP_FREE(dumpbuf);
|
|
}
|
|
StrAllocCopy(new_item->name, XP_StripLine(gtr_than + 1));
|
|
|
|
/* terminate at beginning of name since there
|
|
is nothing interesting after that */
|
|
*gtr_than = '\0';
|
|
}
|
|
|
|
parseString = endQuote + 1;
|
|
|
|
new_item->d.url.target = bm_target(parseString, FALSE);
|
|
|
|
new_item->addition_date = bm_addition_date(parseString);
|
|
|
|
new_item->d.url.last_visit = bm_last_date(parseString, FALSE);
|
|
new_item->d.url.last_modified = bm_last_date(parseString, TRUE);
|
|
if (new_item->d.url.last_modified == 0) {
|
|
new_item->d.url.last_modified = new_item->d.url.last_visit;
|
|
}
|
|
|
|
new_item = bm_check_read_alias(context, new_item, parseString);
|
|
|
|
/* replace the quotes */
|
|
*endQuote = '"';
|
|
}
|
|
|
|
return new_item;
|
|
}
|
|
|
|
|
|
static BM_Entry*
|
|
bm_read_address(MWContext* context, XP_File fp, char* buffer, char* ptr)
|
|
{
|
|
char* endQuote;
|
|
char* gtr_than;
|
|
char* parseString;
|
|
char* end;
|
|
char* url;
|
|
BM_Entry* new_item = NULL;
|
|
|
|
XP_ASSERT(context->type == MWContextAddressBook);
|
|
|
|
/* find next quote */
|
|
parseString = ptr + 6;
|
|
|
|
endQuote = XP_STRCHR(parseString, '"');
|
|
if (endQuote) {
|
|
*endQuote = '\0';
|
|
|
|
url = parseString;
|
|
|
|
if (strncasecomp(url, "mailto:", 7) == 0) {
|
|
url += 7;
|
|
}
|
|
|
|
new_item = bm_NewAddress(NULL, url);
|
|
if (!new_item) return NULL;
|
|
|
|
/* find '>' and the name will be right after it */
|
|
gtr_than = XP_STRCHR(endQuote + 1, '>');
|
|
if (gtr_than) {
|
|
*gtr_than++ = '\0';
|
|
/* find the end of the name */
|
|
end = strcasestr(gtr_than, "</A>");
|
|
if (end) {
|
|
*end = '\0';
|
|
StrAllocCopy(new_item->name, XP_StripLine(gtr_than));
|
|
}
|
|
}
|
|
|
|
parseString = endQuote + 1;
|
|
|
|
new_item = bm_check_read_alias(context, new_item, parseString);
|
|
|
|
if (!BM_ISALIAS(new_item)) {
|
|
bm_check_nickname(context, new_item, parseString);
|
|
}
|
|
}
|
|
|
|
return new_item;
|
|
}
|
|
|
|
|
|
/* ptr should point to the chars "<Hx" */
|
|
static BM_Entry*
|
|
bm_read_header(MWContext* context, char* buffer, char* ptr)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
char* gtr_than;
|
|
char* end;
|
|
BM_Entry* new_item = NULL;
|
|
|
|
|
|
/* find the beginning of the name */
|
|
gtr_than = XP_STRCHR(ptr + 3, '>');
|
|
|
|
/* find the end of the name */
|
|
if (gtr_than) {
|
|
end = strcasestr(gtr_than, "</H");
|
|
}
|
|
|
|
if (gtr_than && end) {
|
|
/* temporarily NULL the name string */
|
|
*end = '\0';
|
|
|
|
new_item = BM_NewHeader(gtr_than + 1);
|
|
|
|
if (!new_item)
|
|
return NULL;
|
|
|
|
*gtr_than = '\0';
|
|
|
|
new_item->d.header.target = bm_target(buffer, FALSE);
|
|
|
|
new_item->addition_date = bm_addition_date(buffer);
|
|
|
|
if (bm_is_folded(buffer))
|
|
BM_SETFLAG(new_item, BM_ATTR_FOLDED);
|
|
else
|
|
BM_CLEARFLAG(new_item, BM_ATTR_FOLDED);
|
|
|
|
new_item = bm_check_read_alias(context, new_item, buffer);
|
|
|
|
if (!BM_ISALIAS(new_item)) {
|
|
if (context->type == MWContextAddressBook) {
|
|
bm_check_nickname(context, new_item, buffer);
|
|
} else {
|
|
if (strcasestr(buffer, "MENUHEADER")) f->menuheader = new_item;
|
|
if (strcasestr(buffer, "NEWITEMHEADER")) f->addheader = new_item;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return new_item;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
bm_read_description(BM_Entry* new_item, char* buffer )
|
|
{
|
|
char* ptr;
|
|
char* end;
|
|
int length;
|
|
|
|
/* assume the rest is descriptions; ignore if item is not a reasonable
|
|
type */
|
|
|
|
if (!new_item || !buffer) return;
|
|
|
|
switch (new_item->type) {
|
|
case BM_TYPE_HEADER:
|
|
case BM_TYPE_URL:
|
|
case BM_TYPE_ADDRESS:
|
|
|
|
length = XP_STRLEN(buffer);
|
|
|
|
/* skip <DL> if present */
|
|
if (*buffer == '<') {
|
|
buffer += 4;
|
|
length -= 4;
|
|
}
|
|
|
|
if (length <= 0) return;
|
|
|
|
end = buffer + length - 1;
|
|
|
|
/* check for <BR> on the end and remove it also add a return */
|
|
if (*end == '>') {
|
|
end -= 3;
|
|
XP_STRCPY(end, LINEBREAK);
|
|
end += LINEBREAK_LEN;
|
|
*end = '\0';
|
|
} else {
|
|
end++;
|
|
XP_STRCPY(end, LINEBREAK);
|
|
end += LINEBREAK_LEN;
|
|
*end = '\0';
|
|
}
|
|
|
|
/* go through and turn < into '<' */
|
|
for (ptr = buffer, end = buffer; *end != '\0'; end++) {
|
|
if (!strncasecomp(end, "<", 4)) {
|
|
end += 3;
|
|
*ptr++ = '<';
|
|
} else {
|
|
*ptr++ = *end;
|
|
}
|
|
}
|
|
*ptr = '\0'; /* terminate */
|
|
StrAllocCat(new_item->description, buffer);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Find the next entry after this one, where "next" means "the one that would
|
|
show up on the next line if we didn't fold any headers". Also, this will
|
|
wrap around from the end back to the beginning. In other words, it will
|
|
never return NULL. */
|
|
static BM_Entry*
|
|
bm_GetNextSpanningWrapping(MWContext* context, BM_Entry* at)
|
|
{
|
|
if (BM_ISHEADER(at) && at->d.header.children) return at->d.header.children;
|
|
if (at->next) return at->next;
|
|
do {
|
|
at = at->parent;
|
|
if (at && at->next) {
|
|
return at->next;
|
|
}
|
|
} while (at);
|
|
return BM_GetRoot(context);
|
|
}
|
|
|
|
|
|
|
|
static XP_Bool
|
|
bm_StringMatches(MWContext* context, BM_FindInfo* findInfo, const char* str) {
|
|
char* ptr;
|
|
INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(context);
|
|
if (!str) return FALSE;
|
|
if (findInfo->matchCase) {
|
|
ptr = INTL_Strstr(INTL_GetCSIWinCSID(c), str, findInfo->textToFind);
|
|
} else {
|
|
ptr = INTL_Strcasestr(INTL_GetCSIWinCSID(c), str, findInfo->textToFind);
|
|
}
|
|
if (!ptr) return FALSE;
|
|
if (findInfo->matchWholeWord) {
|
|
XP_ASSERT(ptr >= str);
|
|
XP_ASSERT(ptr + XP_STRLEN(findInfo->textToFind) <= str + XP_STRLEN(str));
|
|
if (ptr != str && !isspace(ptr[-1]) && !ispunct(ptr[-1])) return FALSE;
|
|
ptr += XP_STRLEN(findInfo->textToFind);
|
|
if (*ptr != '\0' && !isspace(*ptr) && !ispunct(*ptr)) return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static XP_Bool
|
|
bm_IsMatch(MWContext* context, BM_Entry* entry, BM_FindInfo* findInfo)
|
|
{
|
|
|
|
if(!context)
|
|
return FALSE;
|
|
|
|
if (BM_ISALIAS(entry)) entry = entry->d.alias.original;
|
|
if (findInfo->checkNickname) {
|
|
if (bm_StringMatches(context, findInfo, entry->nickname)) return TRUE;
|
|
}
|
|
if (findInfo->checkName) {
|
|
if (bm_StringMatches(context, findInfo, BM_GetName(entry))) return TRUE;
|
|
}
|
|
if (findInfo->checkLocation && BM_ISURL(entry)) {
|
|
if (bm_StringMatches(context, findInfo, entry->d.url.address)) return TRUE;
|
|
}
|
|
if (findInfo->checkDescription) {
|
|
if (bm_StringMatches(context, findInfo, entry->description)) return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static BM_Entry*
|
|
bm_DoFindBookmark_1(MWContext* context, BM_Entry* at, BM_FindInfo* findInfo) {
|
|
BM_Entry* start = at;
|
|
if (!at) return NULL;
|
|
do {
|
|
if (bm_IsMatch(context, at, findInfo)) return at;
|
|
at = bm_GetNextSpanningWrapping(context, at);
|
|
} while (at != start);
|
|
return NULL;
|
|
}
|
|
|
|
/* reads an item from fp using the specified buffer and relative_url */
|
|
static void
|
|
bm_ReadFromHTML(MWContext* context,
|
|
XP_File fp,
|
|
BM_Entry* item,
|
|
char* buffer,
|
|
const char* relative_url)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* new_item = NULL;
|
|
char* buffer_ptr;
|
|
char* ptr;
|
|
uint16 type;
|
|
|
|
/* read loop */
|
|
while (XP_FileReadLine(buffer, READ_BUFFER_SIZE, fp)) {
|
|
buffer_ptr = XP_StripLine(buffer);
|
|
|
|
type = bm_tokenize_line(context, buffer_ptr, &ptr);
|
|
|
|
switch (type) {
|
|
case BM_TYPE_URL:
|
|
new_item = bm_read_url(context, fp, buffer_ptr, ptr, relative_url);
|
|
break;
|
|
|
|
case BM_TYPE_ADDRESS:
|
|
new_item = bm_read_address(context, fp, buffer_ptr, ptr);
|
|
break;
|
|
|
|
case BM_TYPE_HEADER:
|
|
new_item = bm_read_header(context, buffer_ptr, ptr);
|
|
break;
|
|
|
|
case BM_TYPE_SEPARATOR:
|
|
if (context->type == MWContextBookmarks) {
|
|
new_item = bm_NewSeparator();
|
|
}
|
|
break;
|
|
|
|
case BM_HEADER_END:
|
|
if (item != f->gBookmarks) return;
|
|
break;
|
|
|
|
case BM_UNKNOWN:
|
|
if (new_item)
|
|
bm_read_description(new_item, buffer_ptr);
|
|
else if (item)
|
|
bm_read_description(item, buffer_ptr);
|
|
break;
|
|
}
|
|
|
|
/* test for insertable item -- nb you'll need
|
|
to update this if you add new types/items */
|
|
if (new_item && (type == BM_TYPE_URL ||
|
|
type == BM_TYPE_HEADER ||
|
|
type == BM_TYPE_SEPARATOR ||
|
|
type == BM_TYPE_ADDRESS)) {
|
|
if (!item) {
|
|
if (!f->gBookmarks) {
|
|
if (new_item->type == BM_TYPE_HEADER) {
|
|
f->gBookmarks = new_item;
|
|
if (context->type == MWContextBookmarks) {
|
|
f->menuheader = f->addheader = new_item;
|
|
}
|
|
goto SKIP;
|
|
} else {
|
|
(void) BM_GetRoot(context); /* Has side effect of creating
|
|
root header. */
|
|
if (!f->gBookmarks) return;
|
|
}
|
|
}
|
|
item = f->gBookmarks;
|
|
}
|
|
|
|
if (context->type == MWContextBookmarks) {
|
|
bm_AppendChildToHeader(context, item, new_item);
|
|
} else {
|
|
bm_AddChildToHeaderSorted(context, item, new_item);
|
|
}
|
|
|
|
SKIP:
|
|
/* if it's a header, recurse */
|
|
if (new_item->type == BM_TYPE_HEADER) {
|
|
bm_ReadFromHTML(context, fp, new_item, buffer, relative_url);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
bm_WriteAsHTML(MWContext* context, XP_File fp, BM_Entry* item, int32 level,
|
|
XP_Bool isalias);
|
|
|
|
|
|
static int
|
|
bm_write_ok(const char* str, int length, XP_File fp)
|
|
{
|
|
if (length < 0)
|
|
length = XP_STRLEN(str);
|
|
if (length > WRITE_BUFFER_SIZE)
|
|
length = WRITE_BUFFER_SIZE;
|
|
if ((int) XP_FileWrite(str, length, fp) < length)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define WRITE(str, length, fp) \
|
|
if (bm_write_ok((str), (length), (fp)) < 0) return -1
|
|
|
|
static int
|
|
bm_write_alias_info(MWContext* context, XP_File fp, BM_Entry* entry,
|
|
XP_Bool isalias)
|
|
{
|
|
bm_alias_info* info;
|
|
XP_ASSERT(!isalias || (entry->flags & BM_ATTR_HASALIASES));
|
|
if (entry->flags & BM_ATTR_HASALIASES) {
|
|
if (!isalias) {
|
|
/* Well, we think we have some aliases, but we can't be sure. Let's
|
|
make sure. */
|
|
if (BM_CountAliases(context, entry) == 0) return 0;
|
|
}
|
|
info = bm_find_writealias_info(context, entry);
|
|
WRITE(isalias ? " ALIASOF=\"" : " ALIASID=\"", -1, fp);
|
|
WRITE(info->id, -1, fp);
|
|
WRITE("\"", -1, fp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
bm_write_nickname(MWContext* context, XP_File fp, BM_Entry* entry,
|
|
XP_Bool isalias)
|
|
{
|
|
if (context->type == MWContextAddressBook && !isalias &&
|
|
entry->nickname && *entry->nickname) {
|
|
WRITE(" NICKNAME=\"", -1, fp);
|
|
WRITE(entry->nickname, -1, fp);
|
|
WRITE("\"", -1, fp);
|
|
}
|
|
return 0; /* XXX This was left out. Is is 0 right? */
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
bm_write_html_header(MWContext* context, XP_File fp, BM_Entry* item,
|
|
int32 level, XP_Bool isalias)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
char buffer[16];
|
|
int32 i;
|
|
BM_Entry* child;
|
|
char* target;
|
|
int status;
|
|
|
|
XP_ASSERT(BM_ISHEADER(item));
|
|
|
|
target = BM_GetTarget(item, FALSE);
|
|
|
|
if (level != 0) {
|
|
if (item->name) {
|
|
WRITE("<DT><H3", -1, fp);
|
|
/* write folded state */
|
|
if (item->flags & BM_ATTR_FOLDED) {
|
|
WRITE(" FOLDED", -1, fp);
|
|
}
|
|
|
|
if (item == f->menuheader) {
|
|
WRITE(" MENUHEADER", -1, fp);
|
|
}
|
|
if (item == f->addheader) {
|
|
WRITE(" NEWITEMHEADER", -1, fp);
|
|
}
|
|
|
|
status = bm_write_alias_info(context, fp, item, isalias);
|
|
if (status < 0) return status;
|
|
|
|
status = bm_write_nickname(context, fp, item, isalias);
|
|
if (status < 0) return status;
|
|
|
|
/* write target */
|
|
if ((target)&&(target[0] != '\0'))
|
|
{
|
|
WRITE(" TARGET=\"", -1, fp);
|
|
WRITE(target, -1, fp);
|
|
WRITE("\"", -1, fp);
|
|
}
|
|
|
|
if (context->type == MWContextBookmarks) {
|
|
/* write addition date */
|
|
WRITE(" ADD_DATE=\"", -1, fp);
|
|
XP_SPRINTF(buffer, "%ld\"", item->addition_date);
|
|
WRITE(buffer, XP_STRLEN(buffer), fp);
|
|
}
|
|
WRITE(">", -1, fp);
|
|
|
|
/* write name */
|
|
WRITE(item->name, XP_STRLEN(item->name), fp);
|
|
WRITE("</H3>", -1, fp);
|
|
WRITE(LINEBREAK, LINEBREAK_LEN, fp);
|
|
}
|
|
}
|
|
|
|
/* write description if there is one */
|
|
if (item->description) {
|
|
char *ptr = XP_StripLine(item->description);
|
|
|
|
WRITE("<DD>", -1, fp);
|
|
|
|
for (; *ptr != '\0'; ptr++) {
|
|
if (*ptr == '<') {
|
|
WRITE("<", -1, fp);
|
|
} else if (*ptr == '\n') {
|
|
WRITE("<BR>", -1, fp);
|
|
WRITE(LINEBREAK, LINEBREAK_LEN, fp);
|
|
} else {
|
|
WRITE(ptr, 1, fp);
|
|
}
|
|
}
|
|
WRITE(LINEBREAK, LINEBREAK_LEN, fp);
|
|
}
|
|
|
|
if (!isalias) {
|
|
/* write children out */
|
|
for (i = 0; i < level; i++) {
|
|
WRITE(" ", -1, fp); /* indent */
|
|
}
|
|
WRITE("<DL><p>" LINEBREAK, -1, fp);
|
|
|
|
for (child = item->d.header.children; child ; child = child->next) {
|
|
bm_WriteAsHTML(context, fp, child, level + 1, FALSE);
|
|
}
|
|
|
|
for (i = 0; i < level; i++) {
|
|
WRITE(" ", -1, fp);
|
|
}
|
|
|
|
WRITE("</DL><p>" LINEBREAK, -1, fp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bm_write_separator(XP_File fp)
|
|
{
|
|
WRITE("<HR>", -1, fp);
|
|
WRITE(LINEBREAK, LINEBREAK_LEN, fp);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bm_write_address( char *pszAddress, XP_File fp )
|
|
{
|
|
/*
|
|
// Replace explicit quotes with escaped quotes before writing the address.
|
|
// For example:
|
|
// javascript:netscape.plugin.composer.Document.editDocument("http://myserver.com/docs/schedule.html")
|
|
// is converted to:
|
|
// javascript:netscape.plugin.composer.Document.editDocument(%20http://myserver.com/docs/schedule.html%20)
|
|
*/
|
|
|
|
int iBufPos, iLen = 0;
|
|
char * pszCsr = pszAddress;
|
|
char * pszBuf = NULL;
|
|
|
|
if( !pszAddress || !fp )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if( !XP_STRCHR( pszAddress, '"' ) )
|
|
{
|
|
/* No quotes to convert, so just write it and return. */
|
|
|
|
WRITE( pszAddress, -1, fp );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
// Calculate the size of the new string.
|
|
*/
|
|
iLen = XP_STRLEN( pszAddress );
|
|
while( *pszCsr )
|
|
{
|
|
if( *pszCsr == '"' )
|
|
{
|
|
iLen += 2;
|
|
}
|
|
pszCsr++;
|
|
}
|
|
|
|
pszBuf = (char *)XP_ALLOC( iLen+1 );
|
|
if( !pszBuf )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
// Copy the url while converting explicit quotes to escaped quotes.
|
|
*/
|
|
iBufPos = 0;
|
|
pszCsr = pszAddress;
|
|
while( *pszCsr )
|
|
{
|
|
if( *pszCsr == '"' )
|
|
{
|
|
pszBuf[iBufPos] = '%';
|
|
pszBuf[++iBufPos] = '2';
|
|
pszBuf[++iBufPos] = '2';
|
|
}
|
|
else
|
|
{
|
|
pszBuf[iBufPos] = *pszCsr;
|
|
}
|
|
|
|
iBufPos++;
|
|
pszCsr++;
|
|
}
|
|
pszBuf[iBufPos] = 0;
|
|
|
|
/* Finally write out the converted address */
|
|
|
|
WRITE( pszBuf, -1, fp );
|
|
|
|
XP_FREE( pszBuf );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* writes out a URL entry to look like:
|
|
*
|
|
* <DT><A HREF="http://www.ncsa.uiuc.edu/radio/radio.html" \
|
|
* ADD_DATE="777240414" LAST_VISIT="802992591">Internet Talk Radio</A>
|
|
*
|
|
*/
|
|
static int
|
|
bm_write_url_or_address(MWContext* context, XP_File fp, BM_Entry* item,
|
|
XP_Bool isalias)
|
|
{
|
|
char buffer[16];
|
|
char* address;
|
|
char* target;
|
|
int status;
|
|
|
|
address = BM_GetAddress(item);
|
|
target = BM_GetTarget(item, FALSE);
|
|
|
|
if (address) {
|
|
WRITE("<DT>", -1, fp);
|
|
|
|
/* write address */
|
|
WRITE("<A HREF=\"", -1, fp);
|
|
if (context->type == MWContextAddressBook) {
|
|
WRITE("mailto:", -1, fp);
|
|
}
|
|
bm_write_address(address, fp);
|
|
WRITE("\"", -1, fp);
|
|
|
|
/* write target */
|
|
if ((target)&&(target[0] != '\0'))
|
|
{
|
|
WRITE(" TARGET=\"", -1, fp);
|
|
WRITE(target, -1, fp);
|
|
WRITE("\"", -1, fp);
|
|
}
|
|
|
|
status = bm_write_alias_info(context, fp, item, isalias);
|
|
if (status < 0) return status;
|
|
|
|
status = bm_write_nickname(context, fp, item, isalias);
|
|
if (status < 0) return status;
|
|
|
|
if (BM_ISURL(item)) {
|
|
/* write the addition date */
|
|
WRITE(" ADD_DATE=\"", -1, fp);
|
|
XP_SPRINTF(buffer, "%ld", item->addition_date);
|
|
WRITE(buffer, -1, fp);
|
|
WRITE("\"", -1, fp);
|
|
|
|
/* write the last visited date */
|
|
WRITE(" LAST_VISIT=\"", -1, fp);
|
|
XP_SPRINTF(buffer, "%ld\"", item->d.url.last_visit);
|
|
WRITE(buffer, -1, fp);
|
|
|
|
/* write the last modified date */
|
|
WRITE(" LAST_MODIFIED=\"", -1, fp);
|
|
XP_SPRINTF(buffer, "%ld\"", item->d.url.last_modified);
|
|
WRITE(buffer, -1, fp);
|
|
}
|
|
WRITE(">", -1, fp);
|
|
|
|
/* write the name */
|
|
|
|
if (item->name) {
|
|
WRITE(item->name, -1, fp);
|
|
} else {
|
|
if (BM_ISURL(item)) {
|
|
WRITE(item->d.url.address, -1, fp);
|
|
} else {
|
|
XP_ASSERT(BM_ISADDRESS(item));
|
|
WRITE(item->d.address.address, -1, fp);
|
|
}
|
|
}
|
|
|
|
WRITE("</A>", -1, fp);
|
|
WRITE(LINEBREAK, LINEBREAK_LEN, fp);
|
|
|
|
/* write description if there is one */
|
|
if (item->description) {
|
|
char *ptr = XP_StripLine(item->description);
|
|
|
|
WRITE("<DD>", -1, fp);
|
|
|
|
for (; *ptr != '\0'; ptr++) {
|
|
if (*ptr == '<') {
|
|
WRITE("<", -1, fp);
|
|
} else if (*ptr == '\n') {
|
|
WRITE("<BR>", -1, fp);
|
|
WRITE(LINEBREAK, LINEBREAK_LEN, fp);
|
|
} else {
|
|
WRITE(ptr, 1, fp);
|
|
}
|
|
}
|
|
WRITE(LINEBREAK, LINEBREAK_LEN, fp);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* writes an item into fp at the specified indentation level */
|
|
static int
|
|
bm_WriteAsHTML(MWContext* context, XP_File fp, BM_Entry* item, int32 level,
|
|
XP_Bool isalias)
|
|
{
|
|
int32 i;
|
|
int status = 0;
|
|
|
|
/* indent */
|
|
if (!isalias) {
|
|
for (i = 0; i < level; i++) {
|
|
WRITE(" ", -1, fp);
|
|
}
|
|
}
|
|
|
|
switch (item->type) {
|
|
case BM_TYPE_HEADER:
|
|
status = bm_write_html_header(context, fp, item, level, isalias);
|
|
break;
|
|
|
|
case BM_TYPE_SEPARATOR:
|
|
status = bm_write_separator(fp);
|
|
break;
|
|
|
|
case BM_TYPE_URL:
|
|
case BM_TYPE_ADDRESS:
|
|
status = bm_write_url_or_address(context, fp, item, isalias);
|
|
break;
|
|
|
|
case BM_TYPE_ALIAS:
|
|
XP_ASSERT(!isalias);
|
|
status = bm_WriteAsHTML(context, fp, item->d.alias.original, level, TRUE);
|
|
break;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/* clears the selected state of parent and all of it's children
|
|
if refresh is TRUE, the FE is called to redraw necessary items
|
|
count should match the index of parent in the visible tree. count will
|
|
be NULL if a parent is folded. */
|
|
PRIVATE void
|
|
bm_ClearSelection(MWContext* context, BM_Entry* parent, XP_Bool refresh, int32* count)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* child;
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(BM_ISHEADER(parent));
|
|
|
|
if (parent->flags & BM_ATTR_SELECTED) {
|
|
BM_CLEARFLAG(parent, BM_ATTR_SELECTED);
|
|
if (refresh && count) bm_refresh(context, *count, *count);
|
|
f->gSelectionCount = -9999;
|
|
}
|
|
|
|
if (count) (*count)++;
|
|
|
|
if (BM_ISFOLDED(parent)) count = NULL;
|
|
|
|
for (child = parent->d.header.children ; child ; child = child->next) {
|
|
if (child->type != BM_TYPE_HEADER) {
|
|
if (child->flags & BM_ATTR_SELECTED) {
|
|
BM_CLEARFLAG(child, BM_ATTR_SELECTED);
|
|
if (refresh && count) bm_refresh(context, *count, *count);
|
|
f->gSelectionCount = -9999;
|
|
}
|
|
if (count) (*count)++;
|
|
} else {
|
|
bm_ClearSelection(context, child, refresh, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
PRIVATE void
|
|
bm_SelectAll(MWContext* context, BM_Entry* parent)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* child;
|
|
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(parent->type == BM_TYPE_HEADER);
|
|
|
|
child = parent->d.header.children;
|
|
if (!BM_ISSELECTED(parent))
|
|
{
|
|
BM_SETFLAG(parent, BM_ATTR_SELECTED);
|
|
f->gSelectionCount++;
|
|
f->gSelectionMask |= BM_TYPE_HEADER;
|
|
}
|
|
|
|
while (child)
|
|
{
|
|
if (BM_ISHEADER(child))
|
|
bm_SelectAll(context, child);
|
|
else
|
|
{
|
|
if (!(child->flags & BM_ATTR_SELECTED))
|
|
{
|
|
BM_SETFLAG(child, BM_ATTR_SELECTED);
|
|
f->gSelectionCount++;
|
|
f->gSelectionMask |= child->type;
|
|
}
|
|
}
|
|
child = child->next;
|
|
}
|
|
}
|
|
|
|
PUBLIC void
|
|
BM_SelectAll(MWContext* context, XP_Bool refresh)
|
|
{
|
|
BM_Entry* root;
|
|
CHKCONTEXTVOID(context);
|
|
|
|
root = BM_GetRoot(context);
|
|
|
|
if (root)
|
|
{
|
|
bm_SelectAll(context, root);
|
|
if (refresh)
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_TellGoingAway(MWContext* context, BM_Entry* entry, void* closure)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
if (entry == f->menuheader) {
|
|
BM_SetMenuHeader(context, BM_GetRoot(context));
|
|
}
|
|
if (entry == f->addheader) {
|
|
BM_SetAddHeader(context, BM_GetRoot(context));
|
|
}
|
|
BMFE_EntryGoingAway(context, entry);
|
|
if (entry->nickname) {
|
|
BM_SetNickName(context, entry, NULL); /* Causes the nickname hash to be
|
|
cleared, adding undo stuff to
|
|
bring it back if this entry
|
|
somehow gets brought back. */
|
|
XP_ASSERT(entry->nickname == NULL);
|
|
}
|
|
#ifdef DEBUG
|
|
/* Confirm that we are not deleting any dangling aliases. */
|
|
if (entry->flags & BM_ATTR_HASALIASES) {
|
|
int32 count = BM_CountAliases(context, entry);
|
|
if (count) {
|
|
BM_Entry* deleteroot = (BM_Entry*) closure;
|
|
if (BM_ISHEADER(deleteroot)) {
|
|
/* Reduce the count by the number of aliases that we're going to
|
|
delete. */
|
|
count -= bm_CountAliases_1(deleteroot, entry);
|
|
}
|
|
XP_ASSERT(count == 0);
|
|
}
|
|
}
|
|
#endif /* DEBUG */
|
|
}
|
|
|
|
/* free's a bmlist entry */
|
|
PRIVATE void
|
|
bm_ShallowFreeEntry(BM_Entry* entry)
|
|
{
|
|
if (entry) {
|
|
XP_ASSERT(entry->next == NULL);
|
|
XP_ASSERT(entry->nickname == NULL);
|
|
|
|
FREEIF(entry->name);
|
|
FREEIF(entry->description);
|
|
switch (entry->type) {
|
|
case BM_TYPE_HEADER:
|
|
XP_ASSERT(entry->d.header.children == NULL);
|
|
break;
|
|
|
|
case BM_TYPE_URL:
|
|
FREEIF(entry->d.url.address);
|
|
FREEIF(entry->d.url.content_type);
|
|
break;
|
|
|
|
case BM_TYPE_ADDRESS:
|
|
FREEIF(entry->d.address.address);
|
|
break;
|
|
}
|
|
XP_FREE(entry);
|
|
}
|
|
}
|
|
|
|
PRIVATE void
|
|
bm_ReallyFreeEntry(void* data)
|
|
{
|
|
BM_Entry* entry = (BM_Entry*) data;
|
|
while (entry) {
|
|
BM_Entry* next = entry->next;
|
|
entry->next = NULL;
|
|
if (BM_ISHEADER(entry)) {
|
|
/* free all the children */
|
|
bm_ReallyFreeEntry(entry->d.header.children);
|
|
entry->d.header.children = NULL;
|
|
}
|
|
bm_ShallowFreeEntry(entry);
|
|
entry = next;
|
|
}
|
|
}
|
|
|
|
|
|
typedef struct bm_free_info {
|
|
BM_Entry* entry;
|
|
XP_Bool usedFromUndo;
|
|
} bm_free_info;
|
|
|
|
static int
|
|
bm_cancel_free(void* closure)
|
|
{
|
|
bm_free_info* info = (bm_free_info*) closure;
|
|
info->usedFromUndo = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
bm_free_freeit(void* closure)
|
|
{
|
|
bm_free_info* info = (bm_free_info*) closure;
|
|
if (!info->usedFromUndo) bm_ReallyFreeEntry(info->entry);
|
|
XP_FREE(info);
|
|
}
|
|
|
|
|
|
/* free's a BM_Entry and all of its succeeding siblings... if it's a
|
|
header, it frees all of its children. However, actually this
|
|
does nothing, but logs an event in the undo queue. When the event gets
|
|
freed, then we know that nothing in the undo chain needs this thing,
|
|
so *then* we can free it.*/
|
|
void
|
|
BM_FreeEntry(MWContext* context, BM_Entry* entry)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
if (!entry) return;
|
|
|
|
bm_start_batch(context);
|
|
bm_EachEntryDo_1(context, entry, bm_TellGoingAway, entry);
|
|
|
|
if (f->undo) {
|
|
bm_free_info* info = XP_NEW_ZAP(bm_free_info);
|
|
if (!info) {
|
|
UNDO_DiscardAll(f->undo);
|
|
bm_ReallyFreeEntry(entry);
|
|
} else {
|
|
info->entry = entry;
|
|
UNDO_LogEvent(f->undo, bm_cancel_free, bm_free_freeit, info, NULL, NULL);
|
|
}
|
|
} else {
|
|
bm_ReallyFreeEntry(entry);
|
|
}
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
|
|
int
|
|
BM_InitializeBookmarksContext(MWContext* context)
|
|
{
|
|
BM_Frame* f;
|
|
XP_ASSERT(context);
|
|
if (!context) return -1; /* ### Need better error code? */
|
|
f = XP_NEW_ZAP(BM_Frame);
|
|
if (!f) return MK_OUT_OF_MEMORY;
|
|
XP_ASSERT(context->bmframe == NULL);
|
|
f->undo = UNDO_Create(10);
|
|
if (!f->undo) goto FAIL;
|
|
f->nicknameTable = XP_HashTableNew(100, XP_StringHash, bm_string_cmp);
|
|
if (!f->nicknameTable) goto FAIL;
|
|
f->errorSavingBookmarks = FALSE;
|
|
f->enSortType = BM_Sort_Natural;
|
|
f->bSorting = FALSE;
|
|
context->bmframe = f;
|
|
f->next = ContextList;
|
|
ContextList = context;
|
|
(void) BM_GetRoot(context); /* Has side effect of creating root header. */
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
return 0;
|
|
FAIL:
|
|
if (f->undo) UNDO_Destroy(f->undo);
|
|
if (f->nicknameTable) XP_HashTableDestroy(f->nicknameTable);
|
|
XP_FREE(f);
|
|
return MK_OUT_OF_MEMORY;
|
|
}
|
|
|
|
void
|
|
BM_CleanupBookmarksContext(MWContext* context)
|
|
{
|
|
BM_Frame* f;
|
|
MWContext** tmp;
|
|
CHKCONTEXTVOID(context);
|
|
BM_SaveBookmarks(context, NULL);
|
|
|
|
/* This cleanup code can be slow and
|
|
inefficient. Since we're gonna exit soon
|
|
anyway, let's not bother doing this stuff.
|
|
### - DMB - Let's, at least for debug
|
|
detection of meory leaks. How slow could it
|
|
be? */
|
|
f = GETFRAME(context);
|
|
UNDO_Destroy(f->undo);
|
|
f->undo = NULL;
|
|
BM_FreeEntry(context, f->gBookmarks);
|
|
if (f->savetimer) {
|
|
FE_ClearTimeout(f->savetimer);
|
|
f->savetimer = NULL;
|
|
}
|
|
|
|
f->gBookmarks = NULL;
|
|
if (f->aliasTable) {
|
|
bm_clear_alias_info(context);
|
|
XP_HashTableDestroy(f->aliasTable);
|
|
}
|
|
if (f->nicknameTable) {
|
|
XP_HashTableDestroy(f->nicknameTable);
|
|
}
|
|
|
|
for (tmp = &ContextList ; *tmp ; tmp = &(f->next)) {
|
|
f = GETFRAME(*tmp);
|
|
if (*tmp == context) {
|
|
(*tmp)->bmframe = NULL;
|
|
*tmp = f->next;
|
|
XP_FREE(f);
|
|
return;
|
|
}
|
|
}
|
|
XP_ASSERT(0);
|
|
}
|
|
|
|
|
|
/* returns TRUE if the bookmarks have been modified since
|
|
the file was read, FALSE otherwise */
|
|
PUBLIC XP_Bool
|
|
BM_Modified(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXT(context);
|
|
return f ? f->gBookmarksModified : FALSE;
|
|
}
|
|
|
|
static void
|
|
bm_UpdateTimeStamp(MWContext* context, BM_Entry* entry, void* closure)
|
|
{
|
|
BM_Date cur_time = *(BM_Date *)closure;
|
|
BM_Frame* f = GETFRAME(context);
|
|
int32 oldstate;
|
|
|
|
if (!entry) return;
|
|
|
|
oldstate = BM_GetChangedState(entry);
|
|
entry->d.url.last_visit = cur_time;
|
|
if (entry->d.url.last_modified == 0) {
|
|
/* Well, this current visitation is a good enough estimate for the modification time. */
|
|
entry->d.url.last_modified = cur_time;
|
|
}
|
|
|
|
/* Deliberately *don't* call bm_SetModified here; we don't want to cause
|
|
the file to be saved soon for this trivial change. Just turn on the
|
|
modified bit so that we know things will be saved eventually. */
|
|
f->gBookmarksModified = TRUE;
|
|
|
|
if (BM_GetChangedState(entry) != oldstate) {
|
|
bm_entry_changed(context, entry);
|
|
}
|
|
}
|
|
|
|
/* checks the bmlist for a url and updates the last accessed time */
|
|
PUBLIC void
|
|
BM_UpdateBookmarksTime(URL_Struct* URL_s, BM_Date cur_time)
|
|
{
|
|
MWContext* context;
|
|
BM_Frame* f;
|
|
|
|
if (!URL_s) return;
|
|
|
|
for (context = ContextList ; context ; context = f->next) {
|
|
f = GETFRAME(context);
|
|
if (context->type != MWContextBookmarks) continue;
|
|
bm_FindItemStub(context, BM_GetRoot(context), URL_s->address, bm_UpdateTimeStamp, (void *)&cur_time);
|
|
}
|
|
}
|
|
|
|
|
|
/* returns the total number of items in the tree */
|
|
PUBLIC int32
|
|
BM_GetCount(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXT(context);
|
|
if (!f) return 0;
|
|
if (f->gCount <= 0) {
|
|
f->gCount = bm_CountChildren(BM_GetRoot(context), FALSE);
|
|
}
|
|
return f->gCount;
|
|
}
|
|
|
|
/* returns the number of items in the tree that are presently
|
|
visible */
|
|
PUBLIC int32
|
|
BM_GetVisibleCount(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXT(context);
|
|
if (!f) return 0;
|
|
if (f->gVisCount <= 0) {
|
|
f->gVisCount = bm_CountChildren(BM_GetRoot(context), TRUE);
|
|
}
|
|
return f->gVisCount;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
bm_InsertItemAfter(MWContext* context, BM_Entry* insert_after,
|
|
BM_Entry* insertee, XP_Bool sync)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXTVOID(context);
|
|
|
|
XP_ASSERT(insertee);
|
|
if (!insertee) return;
|
|
|
|
/* insert after the item if specified */
|
|
if (insert_after)
|
|
{
|
|
BM_Entry* tmp;
|
|
BM_Entry* parent;
|
|
|
|
if (insert_after->parent == NULL)
|
|
{
|
|
/* insert as first child, displayed below header */
|
|
tmp = insert_after->d.header.children;
|
|
parent = insert_after;
|
|
parent->d.header.childCount++;
|
|
parent->d.header.children = insertee;
|
|
}
|
|
else
|
|
{
|
|
tmp = insert_after->next;
|
|
parent = insert_after->parent;
|
|
|
|
if (parent)
|
|
{
|
|
parent->d.header.childCount++;
|
|
if (!tmp)
|
|
parent->d.header.lastChild = insertee;
|
|
}
|
|
insert_after->next = insertee;
|
|
}
|
|
insertee->next = tmp;
|
|
insertee->parent = parent;
|
|
|
|
if( !f->bSorting )
|
|
insertee->iNaturalIndex = g_iNaturalIndexPool++;
|
|
|
|
BMFE_BookmarkMenuInvalid(context);
|
|
bm_LogDeleteChild(context, parent, insertee);
|
|
}
|
|
else
|
|
bm_AppendChildToHeader(context, BM_GetRoot(context), insertee);
|
|
|
|
bm_SetModified(context, TRUE);
|
|
if (sync)
|
|
bm_SyncCount(context);
|
|
}
|
|
|
|
/* insert an item after another item in the bmlist
|
|
if the insert_after item is NULL the item
|
|
will be inserted at the end of the bookmarks */
|
|
PUBLIC void
|
|
BM_InsertItemAfter(MWContext* context, BM_Entry* insert_after, BM_Entry* insertee)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
bm_start_batch(context);
|
|
bm_InsertItemAfter(context, insert_after, insertee, TRUE);
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
/* insert an item in a header if "insert_after" is a
|
|
Header type, or after the item if "insert after" is
|
|
not a header type.
|
|
if the insert_after item is NULL or not found the item
|
|
will be inserted at the end of the bookmarks */
|
|
PUBLIC void
|
|
BM_InsertItemInHeaderOrAfterItem( MWContext* context,
|
|
BM_Entry* insert_after,
|
|
BM_Entry* insertee)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(insertee);
|
|
|
|
bm_start_batch(context);
|
|
if (insert_after && insert_after->type == BM_TYPE_HEADER)
|
|
bm_AppendChildToHeader(context, insert_after, insertee);
|
|
else
|
|
BM_InsertItemAfter(context, insert_after, insertee);
|
|
bm_SyncCount(context);
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
static void remove_to(MWContext* context, BM_Entry* entry, void* to)
|
|
{
|
|
BM_Entry* moveTo = (BM_Entry*)to;
|
|
BM_Entry* parent;
|
|
|
|
XP_ASSERT(entry);
|
|
|
|
parent = entry->parent;
|
|
if (parent)
|
|
BM_RemoveChildFromHeader(context, parent, entry);
|
|
|
|
bm_AppendChildToHeader(context, moveTo, entry);
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
bm_get_max_depth_1(BM_Entry* entry)
|
|
{
|
|
int result = 0;
|
|
for (; entry ; entry = entry->next) {
|
|
if (BM_ISHEADER(entry) && !BM_ISFOLDED(entry)) {
|
|
int value = bm_get_max_depth_1(entry->d.header.children);
|
|
if (result < value) result = value;
|
|
}
|
|
}
|
|
return result + 1;
|
|
}
|
|
|
|
int
|
|
BM_GetMaxDepth(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXT(context);
|
|
if (!f) return 0;
|
|
if (f->max_depth == 0) {
|
|
f->max_depth = bm_get_max_depth_1(f->gBookmarks);
|
|
}
|
|
return f->max_depth;
|
|
}
|
|
|
|
|
|
PUBLIC XP_Bool
|
|
BM_IsDragEffectBox(MWContext* context, int line, XP_Bool under)
|
|
{
|
|
BM_Entry* entry;
|
|
CHKCONTEXT(context);
|
|
if (line <= 0) return FALSE;
|
|
entry = BM_AtIndex(context, line);
|
|
if (!entry) return FALSE; /* ### */
|
|
if (BM_ISHEADER(entry)) {
|
|
if (under && (BM_ISFOLDED(entry) || entry->d.header.childCount == 0)) {
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
PUBLIC void
|
|
BM_DoDrop(MWContext* context, int line, XP_Bool under)
|
|
{
|
|
BM_Entry* dest = BM_AtIndex(context, line);
|
|
BM_Entry* tmp;
|
|
BM_Entry* parent;
|
|
BM_Entry* entry;
|
|
CHKCONTEXTVOID(context);
|
|
if (!dest) return;
|
|
for (parent = dest ; parent ; parent = parent->parent) {
|
|
if (BM_ISSELECTED(parent)) return;
|
|
}
|
|
tmp = BM_NewHeader("");
|
|
if (!tmp) return;
|
|
bm_start_batch(context);
|
|
if (BM_ISHEADER(dest) && BM_IsDragEffectBox(context, line, under)) {
|
|
parent = dest;
|
|
dest = NULL;
|
|
} else {
|
|
parent = dest->parent;
|
|
}
|
|
BM_EachProperSelectedEntryDo(context, remove_to, tmp, NULL);
|
|
entry = tmp->d.header.children;
|
|
if (entry) {
|
|
BM_ClearAllSelection(context, FALSE);
|
|
while ((entry = tmp->d.header.children)) {
|
|
BM_RemoveChildFromHeader(context, tmp, entry);
|
|
BM_CLEARFLAG(entry, BM_ATTR_SELECTED);
|
|
if (context->type == MWContextBookmarks) {
|
|
if (dest) {
|
|
bm_InsertItemAfter(context, dest, entry, FALSE);
|
|
} else {
|
|
BM_PrependChildToHeader(context, parent, entry);
|
|
}
|
|
} else {
|
|
if (BM_ISALIAS(entry)) {
|
|
bm_AddChildToHeaderSorted(context, parent, entry);
|
|
} else {
|
|
bm_AddChildToHeaderSorted(context, BM_GetRoot(context), entry);
|
|
entry = bm_NewAlias(entry);
|
|
bm_AddChildToHeaderSorted(context, parent, entry);
|
|
}
|
|
}
|
|
if (!BM_ISHEADER(parent) || !BM_ISFOLDED(parent))
|
|
BM_SelectItem(context, entry, FALSE, TRUE, TRUE);
|
|
dest = entry;
|
|
entry = tmp->d.header.children;
|
|
}
|
|
if (BM_ISHEADER(parent) && BM_ISFOLDED(parent))
|
|
BM_SelectItem(context, parent, FALSE, TRUE, TRUE);
|
|
BMFE_BookmarkMenuInvalid(context);
|
|
bm_SyncCount(context);
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
}
|
|
BM_FreeEntry(context, tmp);
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
|
|
typedef struct bm_goingaway_info {
|
|
XP_Bool userasked;
|
|
XP_Bool userconfirmed;
|
|
BM_Entry* entry;
|
|
int count;
|
|
} bm_goingaway_info;
|
|
|
|
|
|
static void
|
|
bm_subtract_alias_for(MWContext* context, BM_Entry* entry, void* closure)
|
|
{
|
|
bm_goingaway_info* info = (bm_goingaway_info*) closure;
|
|
if (BM_ISALIAS(entry) && entry->d.alias.original == info->entry) {
|
|
info->count--;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
bm_delete_alias_for(MWContext* context, BM_Entry* entry, void* closure)
|
|
{
|
|
BM_Entry* base = (BM_Entry*) closure;
|
|
if (BM_ISALIAS(entry) && entry->d.alias.original == base) {
|
|
BM_RemoveChildFromHeader(context, entry->parent, entry);
|
|
BM_FreeEntry(context, entry);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
bm_check_dangling_aliases(MWContext* context, BM_Entry* entry, void* closure)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
bm_goingaway_info* info = (bm_goingaway_info*) closure;
|
|
if (entry->flags & BM_ATTR_HASALIASES) {
|
|
info->count = BM_CountAliases(context, entry);
|
|
if (info->count) {
|
|
/* Reduce the count by the number of aliases that we're going to
|
|
delete. */
|
|
info->entry = entry;
|
|
BM_EachProperSelectedEntryDo(context, bm_subtract_alias_for, info, NULL);
|
|
XP_ASSERT(info->count >= 0);
|
|
if (info->count) {
|
|
if (!info->userasked) {
|
|
if (f->gSelectionCount < 0) bm_SyncSelection(context);
|
|
if (f->gSelectionCount == 1) {
|
|
char* buf = (char*) XP_ALLOC(512);
|
|
if (buf) {
|
|
XP_SPRINTF(buf,
|
|
XP_GetString(XP_BKMKS_REMOVE_THIS_ITEMS_ALIASES), info->count);
|
|
info->userconfirmed = FE_Confirm(context, buf);
|
|
XP_FREE(buf);
|
|
} else {
|
|
info->userconfirmed =
|
|
FE_Confirm
|
|
(context,
|
|
XP_GetString(XP_BKMKS_REMOVE_SOME_ITEMS_ALIASES) );
|
|
}
|
|
info->userasked = TRUE;
|
|
}
|
|
if (info->userconfirmed) {
|
|
BM_EachEntryDo(context, bm_delete_alias_for, entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_delete(MWContext* context)
|
|
{
|
|
BM_Entry* tmp;
|
|
struct BM_Entry_Focus bmFocus;
|
|
bm_goingaway_info info;
|
|
|
|
tmp = BM_NewHeader("");
|
|
XP_ASSERT(tmp);
|
|
if (!tmp) return;
|
|
|
|
XP_MEMSET(&info, 0, sizeof(info));
|
|
BM_EachProperSelectedEntryDo(context, bm_check_dangling_aliases, &info,
|
|
NULL);
|
|
if (info.userasked && !info.userconfirmed) return;
|
|
|
|
bmFocus.saveFocus = (BM_Entry*) NULL;
|
|
bmFocus.foundSelection = FALSE;
|
|
BM_EachProperSelectedEntryDo(context, remove_to, tmp, &bmFocus);
|
|
|
|
BM_FreeEntry(context, tmp);
|
|
bm_SyncCount(context);
|
|
|
|
if (bmFocus.saveFocus == NULL)
|
|
bmFocus.saveFocus = BM_GetRoot(context);
|
|
if (bmFocus.saveFocus)
|
|
BM_SETFLAG(bmFocus.saveFocus, BM_ATTR_SELECTED);
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
}
|
|
|
|
|
|
static void
|
|
bm_copy(MWContext* context)
|
|
{
|
|
char* block;
|
|
int32 length;
|
|
|
|
block = BM_ConvertSelectionsToBlock(context, TRUE, &length);
|
|
BMFE_SetClipContents(context, (void*)block, length);
|
|
|
|
XP_FREE(block);
|
|
}
|
|
|
|
static void
|
|
bm_cut(MWContext* context)
|
|
{
|
|
bm_copy(context);
|
|
bm_delete(context);
|
|
}
|
|
|
|
|
|
static void
|
|
bm_paste(MWContext* context)
|
|
{
|
|
BM_Entry* firstSelected;
|
|
char* buffer;
|
|
int32 length;
|
|
|
|
firstSelected = BM_FirstSelectedItem(context);
|
|
buffer = (char*)BMFE_GetClipContents(context, &length);
|
|
if (buffer)
|
|
{
|
|
BM_InsertBlockAt(context, buffer, firstSelected, TRUE, length);
|
|
bm_SyncCount(context);
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
}
|
|
}
|
|
|
|
/* Insert a block of long-format entries */
|
|
PUBLIC void
|
|
BM_DropBlockL( MWContext *pContext, char *pData, BM_Entry *firstSelected )
|
|
{
|
|
int32 length;
|
|
|
|
if( !firstSelected )
|
|
{
|
|
firstSelected = BM_FirstSelectedItem( pContext );
|
|
}
|
|
|
|
if( pData )
|
|
{
|
|
/* Length is stored at byte 0 as int32 */
|
|
XP_MEMCPY( &length, pData, sizeof(int32) );
|
|
pData += sizeof(int32);
|
|
|
|
BM_InsertBlockAt( pContext, pData, firstSelected, TRUE, length );
|
|
bm_SyncCount( pContext );
|
|
bm_refresh( pContext, 1, BM_LAST_CELL );
|
|
}
|
|
}
|
|
|
|
/* returns an integer index of the item in the visible tree */
|
|
int32
|
|
BM_GetIndex(MWContext* context, BM_Entry* item)
|
|
{
|
|
int32 count = 1;
|
|
|
|
CHKCONTEXT(context);
|
|
|
|
if (BM_GetRoot(context))
|
|
return bm_GetIndexNum(BM_GetRoot(context), item, &count);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* returns an integer index of the item in the list and does not pay
|
|
attention to the BM_ATTR_FOLDED value */
|
|
int32
|
|
BM_GetUnfoldedIndex(MWContext* context, BM_Entry* item)
|
|
{
|
|
int32 count = 1;
|
|
|
|
BM_Entry* root = BM_GetRoot(context);
|
|
|
|
CHKCONTEXT(context);
|
|
|
|
if (root)
|
|
return bm_GetUnfoldedIndexNum(root, item, &count);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* returns TRUE if the second argument is a direct
|
|
descendent of the first argument.
|
|
returns FALSE otherwise */
|
|
PUBLIC XP_Bool
|
|
BM_IsDescendent(MWContext* context, BM_Entry* parent, BM_Entry* possible_child)
|
|
{
|
|
int32 count = 1;
|
|
CHKCONTEXT(context);
|
|
|
|
if ( parent &&
|
|
parent->type == BM_TYPE_HEADER &&
|
|
bm_GetUnfoldedIndexNum(parent, possible_child, &count))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* returns an integer depth of the item in the list starting at zero */
|
|
PUBLIC int32
|
|
BM_GetDepth(MWContext* context, BM_Entry* item)
|
|
{
|
|
CHKCONTEXT(context);
|
|
if (BM_GetRoot(context))
|
|
return bm_GetDepth(BM_GetRoot(context), item);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* returns the item at "count" visible indexes below "item" */
|
|
static BM_Entry*
|
|
bm_AtIndex(BM_Entry* item, int32* count)
|
|
{
|
|
XP_ASSERT(item);
|
|
XP_ASSERT(item->type == BM_TYPE_HEADER);
|
|
|
|
(*count)--;
|
|
|
|
/* first check to see if parent is the node we are looking for */
|
|
if (*count <= 0)
|
|
return item;
|
|
|
|
if (!BM_ISFOLDED(item))
|
|
{
|
|
BM_Entry* child;
|
|
|
|
child = item->d.header.children;
|
|
while (child)
|
|
{
|
|
if (child->type == BM_TYPE_HEADER)
|
|
{
|
|
BM_Entry* rv = NULL;
|
|
|
|
rv = bm_AtIndex(child, count);
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
else
|
|
{
|
|
(*count)--;
|
|
if (*count <= 0)
|
|
return child;
|
|
}
|
|
child = child->next;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* returns the object associated with the index returned by BM_GetIndex() */
|
|
BM_Entry*
|
|
BM_AtIndex(MWContext* context, int32 index)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
static BM_Frame* last_f = NULL;
|
|
static BM_Entry* last_item = NULL;
|
|
static int32 last_index = -1;
|
|
|
|
int32 count = index;
|
|
|
|
CHKCONTEXT(context);
|
|
|
|
/* only used the cached items if last_item is non-NULL and the
|
|
requested index is one more than the last requested index */
|
|
if (last_f == f && last_item && (index == (last_index + 1)))
|
|
{
|
|
/* if we're not a header
|
|
or we're a header but folded,
|
|
or we're a header but have no children,
|
|
just go to the next item and set the local cache */
|
|
if ( (last_item->type != BM_TYPE_HEADER) ||
|
|
(last_item->flags & BM_ATTR_FOLDED) ||
|
|
(! last_item->d.header.children))
|
|
{
|
|
last_item = last_item->next;
|
|
if (last_item)
|
|
{
|
|
last_index = index;
|
|
return last_item;
|
|
}
|
|
else
|
|
{
|
|
last_index = -1;
|
|
return BM_AtIndex(context, index);
|
|
}
|
|
}
|
|
else
|
|
/* we're a header, we're unfolded, and we have children */
|
|
{
|
|
last_item = last_item->d.header.children;
|
|
last_index = index;
|
|
return last_item;
|
|
}
|
|
}
|
|
|
|
if (BM_GetRoot(context) && index > 0)
|
|
{
|
|
last_item = bm_AtIndex(BM_GetRoot(context), &count);
|
|
if (last_item)
|
|
{
|
|
last_f = f;
|
|
last_index = index;
|
|
return last_item;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
PRIVATE BM_Entry*
|
|
bm_GetUnfoldedIndex(BM_Entry* parent, int32* index)
|
|
{
|
|
BM_Entry* child;
|
|
BM_Entry* rv = 0;
|
|
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(parent->type == BM_TYPE_HEADER);
|
|
child = parent->d.header.children;
|
|
|
|
while (child)
|
|
{
|
|
*(index) -= 1;
|
|
|
|
if (*index <= 0)
|
|
return child;
|
|
|
|
if (child->type == BM_TYPE_HEADER)
|
|
{
|
|
rv = bm_GetUnfoldedIndex(child, index);
|
|
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
child = child->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* returns the object associated with the index returned by BM_GetUnfoldedIndex() */
|
|
PUBLIC BM_Entry*
|
|
BM_AtUnfoldedIndex(MWContext* context, int32 index)
|
|
{
|
|
CHKCONTEXT(context);
|
|
if (BM_GetRoot(context) && index > 0)
|
|
return bm_GetUnfoldedIndex(BM_GetRoot(context), &index);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
bm_fold_header_all(MWContext* context, BM_Entry* entry, XP_Bool fold,
|
|
XP_Bool refresh)
|
|
{
|
|
if (BM_ISHEADER(entry)) {
|
|
BM_FoldHeader(context, entry, fold, refresh, FALSE);
|
|
for (entry = entry->d.header.children ; entry ; entry = entry->next) {
|
|
bm_fold_header_all(context, entry, fold, refresh);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* folds the header bm
|
|
if fold is TRUE, the item becomes folded
|
|
else the item is unfolded
|
|
if refresh is TRUE, the FE is called to
|
|
redraw necessary items
|
|
if foldAll is TRUE, all headers appearing
|
|
below bm in the tree are folded or unfolded
|
|
according to "fold"
|
|
*/
|
|
PUBLIC void
|
|
BM_FoldHeader(MWContext* context, BM_Entry* bm, XP_Bool fold, XP_Bool refresh, XP_Bool foldAll)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
int32 firstChangedCell = 0;
|
|
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(bm);
|
|
if (!bm) return;
|
|
|
|
bm_CancelLastFind(context);
|
|
|
|
f->max_depth = 0;
|
|
if (foldAll)
|
|
{
|
|
bm_start_batch(context);
|
|
bm_fold_header_all(context, bm, fold, refresh);
|
|
bm_end_batch(context);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
|
|
if (BM_ISFOLDED(bm) != fold)
|
|
{
|
|
int32 count;
|
|
firstChangedCell = BM_GetIndex(context, bm);
|
|
|
|
count = firstChangedCell;
|
|
|
|
if (bm)
|
|
{
|
|
if (fold)
|
|
{
|
|
if (firstChangedCell != 0)
|
|
BM_ClearAllChildSelection(context, bm, FALSE);
|
|
BM_SETFLAG(bm, BM_ATTR_FOLDED);
|
|
}
|
|
else
|
|
BM_CLEARFLAG(bm, BM_ATTR_FOLDED);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
bm_SetModified(context, TRUE);
|
|
bm_SyncCount(context);
|
|
|
|
if (foldAll)
|
|
firstChangedCell = MIN(1, firstChangedCell);
|
|
|
|
if (refresh && (firstChangedCell != 0))
|
|
bm_refresh(context, firstChangedCell, BM_LAST_CELL);
|
|
}
|
|
|
|
|
|
|
|
/* clears the selection state of all items in the tree
|
|
if refresh is TRUE, the FE is called to redraw items
|
|
which need to be redrawn
|
|
*/
|
|
PUBLIC void
|
|
BM_ClearAllSelection(MWContext* context, XP_Bool refresh)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
int32 t = 1;
|
|
CHKCONTEXTVOID(context);
|
|
|
|
bm_ClearSelection(context, BM_GetRoot(context), refresh, &t);
|
|
f->gSelectionCount = 0;
|
|
f->gSelectionMask = 0;
|
|
}
|
|
|
|
/* Clears the selection state of all children of the item passed.
|
|
if refresh is TRUE, the FE is called to redraw items
|
|
which need to be redrawn
|
|
*/
|
|
PUBLIC void
|
|
BM_ClearAllChildSelection(MWContext* context, BM_Entry* at, XP_Bool refresh)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
if (!at) return;
|
|
if (at->type != BM_TYPE_HEADER)
|
|
return;
|
|
at = at->d.header.children;
|
|
while (at)
|
|
{
|
|
if (BM_ISSELECTED(at))
|
|
BM_SelectItem(context, at, refresh, TRUE, FALSE);
|
|
if ((at->type == BM_TYPE_HEADER) && (at->d.header.children))
|
|
BM_ClearAllChildSelection(context, at->d.header.children, refresh);
|
|
at = at->next;
|
|
}
|
|
}
|
|
|
|
/* selects the item
|
|
if refresh is TRUE, the FE is called to redraw the item
|
|
if extend is TRUE, the item is added to the selection
|
|
else the selection is cleared and the item becomes
|
|
the selection
|
|
if select is TRUE, the item is selected
|
|
else it is deselected
|
|
|
|
if extend is FALSE and select is FALSE, the selection
|
|
becomes empty
|
|
*/
|
|
PUBLIC void
|
|
BM_SelectItem(MWContext* context, BM_Entry* item, XP_Bool refresh,
|
|
XP_Bool extend, XP_Bool select)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(item);
|
|
if (!item) return;
|
|
bm_start_batch(context);
|
|
bm_CancelLastFind(context);
|
|
if (!extend) {
|
|
BM_ClearAllSelection(context, refresh);
|
|
if (select) f->lastSelectedItem = item;
|
|
}
|
|
if (select) {
|
|
if (!BM_ISSELECTED(item)) f->gSelectionCount++;
|
|
f->gSelectionMask |= item->type;
|
|
BM_SETFLAG(item, BM_ATTR_SELECTED);
|
|
} else {
|
|
BM_CLEARFLAG(item, BM_ATTR_SELECTED);
|
|
f->gSelectionCount = -9999;
|
|
}
|
|
if (refresh) {
|
|
int32 index = BM_GetIndex(context, item);
|
|
if (index) bm_refresh(context, index, index);
|
|
}
|
|
if (!extend) BMFE_EditItem(context, item);
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
|
|
static BM_Entry*
|
|
bm_validate_selected_item(MWContext* context, BM_Entry* item)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* result;
|
|
for (; item ; item = item->next) {
|
|
if (item == f->lastSelectedItem && BM_ISSELECTED(item)) return item;
|
|
if (BM_ISHEADER(item) && !BM_ISFOLDED(item)) {
|
|
result = bm_validate_selected_item(context, item->d.header.children);
|
|
if (result) return result;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
bm_select_range(MWContext* context, BM_Entry* item, int32 min, int32 max,
|
|
int32* cur)
|
|
{
|
|
for (; item ; item = item->next) {
|
|
if (*cur >= min) {
|
|
if (*cur > max) return;
|
|
BM_SelectItem(context, item, FALSE, TRUE, TRUE);
|
|
}
|
|
(*cur)++;
|
|
if (BM_ISHEADER(item) && !BM_ISFOLDED(item)) {
|
|
bm_select_range(context, item->d.header.children, min, max, cur);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
BM_SelectRangeTo(MWContext* context, BM_Entry* item)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
int32 min;
|
|
int32 max;
|
|
int32 cur;
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(item);
|
|
if (!item) return;
|
|
/* First very carefully validate the lastSelectedItem pointer. That item
|
|
might have been deleted or something, and the code in question may not
|
|
have updated the lastSelectedItem pointer. So, we make sure that it still
|
|
points to a valid item, and that the item is selected. */
|
|
f->lastSelectedItem = bm_validate_selected_item(context,
|
|
BM_GetRoot(context));
|
|
if (!f->lastSelectedItem) {
|
|
BM_SelectItem(context, item, TRUE, FALSE, TRUE);
|
|
XP_ASSERT(f->lastSelectedItem == item); /* Not that we can do much if
|
|
this fails...*/
|
|
return;
|
|
}
|
|
min = BM_GetIndex(context, f->lastSelectedItem);
|
|
max = BM_GetIndex(context, item);
|
|
if (min < 1 || max < 1) return;
|
|
if (min > max) {
|
|
int32 tmp = min;
|
|
min = max;
|
|
max = tmp;
|
|
}
|
|
bm_start_batch(context);
|
|
BM_ClearAllSelection(context, TRUE);
|
|
cur = 1;
|
|
bm_select_range(context, BM_GetRoot(context), min, max, &cur);
|
|
XP_ASSERT(BM_ISSELECTED(item)); /* More sanity checking; not */
|
|
XP_ASSERT(BM_ISSELECTED(f->lastSelectedItem)); /* really much we can do if
|
|
these assertions fail.*/
|
|
bm_refresh(context, min, max);
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
/* toggles the selected state of the item
|
|
(see BM_SelectItem) */
|
|
PUBLIC void
|
|
BM_ToggleItem(MWContext* context, BM_Entry* item, XP_Bool refresh, XP_Bool extend)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(item);
|
|
if (!item) return;
|
|
|
|
if (item->flags & BM_ATTR_SELECTED)
|
|
BM_SelectItem(context, item, refresh, extend, FALSE);
|
|
else
|
|
BM_SelectItem(context, item, refresh, extend, TRUE);
|
|
}
|
|
|
|
|
|
|
|
static XP_Bool
|
|
bm_ConfirmSave(MWContext* context)
|
|
{
|
|
/* XP_Bool doSave = FALSE;
|
|
char* msg = "Save changes to %s?\n";
|
|
|
|
sprintf(msg, f->gFile);
|
|
|
|
if (BM_Modified(context))
|
|
doSave = FE_SimpleConfirm(msg);
|
|
return doSave;
|
|
*/
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
const char*
|
|
BM_GetFileName(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
return f->gFile;
|
|
}
|
|
|
|
|
|
|
|
static XP_Bool
|
|
bm_insert_bogus_aliases(XP_HashTable table, const void* key, void* value,
|
|
void* closure)
|
|
{
|
|
MWContext* context = (MWContext*) closure;
|
|
bm_alias_info* info = (bm_alias_info*) value;
|
|
if (info->entry->parent == NULL) {
|
|
/* This was an alias that was made up and inserted because we never could
|
|
find the real entry for it. So, now we had better insert it into the
|
|
tree. */
|
|
if (context->type == MWContextBookmarks) {
|
|
bm_AppendChildToHeader(context, BM_GetRoot(context), info->entry);
|
|
} else {
|
|
bm_AddChildToHeaderSorted(context, BM_GetRoot(context), info->entry);
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* Make sure the address book is sorted. */
|
|
static void
|
|
bm_resort_headers(MWContext* context, BM_Entry* header)
|
|
{
|
|
XP_Bool needssort;
|
|
BM_Entry* entry;
|
|
BM_Entry* prev;
|
|
for ( ; header ; header = header->next) {
|
|
if (BM_ISHEADER(header)) {
|
|
prev = NULL;
|
|
needssort = FALSE;
|
|
for (entry = header->d.header.children ; entry ; entry = entry->next) {
|
|
if (BM_ISHEADER(entry)) bm_resort_headers(context, entry);
|
|
if (prev && bm_SortAddressBook(prev, entry) > 0) {
|
|
needssort = TRUE;
|
|
}
|
|
prev = entry;
|
|
}
|
|
if (needssort) {
|
|
BM_SelectItem(context, header, FALSE, FALSE, TRUE);
|
|
bm_SortSelected(context, BM_Sort_Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* read bmlist file from disk
|
|
pass in a file url */
|
|
PUBLIC void
|
|
BM_ReadBookmarksFromDisk(MWContext* context, const char* filename,
|
|
const char* relative_url)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
XP_File fp;
|
|
char* buffer;
|
|
UndoState* undo;
|
|
|
|
CHKCONTEXTVOID(context);
|
|
|
|
undo = f->undo;
|
|
if (BM_Modified(context)) {
|
|
if (!bm_ConfirmSave(context)) return;
|
|
if (BM_SaveBookmarks(context, f->gFile) < 0) return;
|
|
}
|
|
if (f->gBookmarks) BM_FreeEntry(context, f->gBookmarks);
|
|
f->gBookmarks = NULL;
|
|
|
|
buffer = (char*) XP_ALLOC(READ_BUFFER_SIZE);
|
|
if (!buffer) return;
|
|
|
|
/* don't kill ourselves */
|
|
if (f->gFile != filename)
|
|
StrAllocCopy(f->gFile, filename);
|
|
|
|
XP_ASSERT(f->gFile != NULL);
|
|
|
|
|
|
if (XP_Stat(filename, &(f->laststat), xpBookmarks) != 0) {
|
|
XP_MEMSET(&(f->laststat), 0, sizeof(f->laststat));
|
|
}
|
|
|
|
fp = XP_FileOpen(filename, xpBookmarks, XP_FILE_READ);
|
|
|
|
if (!fp) {
|
|
XP_FREE(buffer);
|
|
return;
|
|
}
|
|
|
|
/* read in the first line */
|
|
XP_FileReadLine(buffer, READ_BUFFER_SIZE, fp);
|
|
|
|
/* DONT REQUIRE THE COOKIE FOR NOW
|
|
*
|
|
* if(XP_STRNCMP(buffer, BMLIST_COOKIE, strlen(BMLIST_COOKIE)
|
|
&& XP_STRNCMP(buffer, BM_ADDR_LIST_COOKIE, strlen(BM_ADDR_LIST_COOKIE))
|
|
* {
|
|
* TRACEMSG(("ERROR! - Hotlist cookie not found in bmlist file"));
|
|
* XP_FREE(buffer);
|
|
* return;
|
|
* }
|
|
*/
|
|
|
|
f->undo = NULL; /* No need to log all this stuff... */
|
|
bm_start_batch(context);
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
|
|
bm_clear_alias_info(context);
|
|
|
|
/* gBookmarks shouldn't exist yet! */
|
|
bm_ReadFromHTML(context, fp, NULL, buffer, relative_url);
|
|
bm_SyncCount(context);
|
|
|
|
XP_FileClose(fp);
|
|
|
|
XP_Maphash(f->aliasTable, bm_insert_bogus_aliases, context);
|
|
|
|
if (context->type == MWContextAddressBook) {
|
|
bm_resort_headers(context, BM_GetRoot(context));
|
|
}
|
|
|
|
bm_SetModified(context, FALSE);
|
|
|
|
XP_FREE(buffer);
|
|
|
|
bm_end_batch(context);
|
|
f->undo = undo;
|
|
UNDO_DiscardAll(undo);
|
|
}
|
|
|
|
PUBLIC int32
|
|
BM_SaveBookmarks(MWContext* context, const char* filename)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
XP_File fp = NULL;
|
|
const char* bm_list_name;
|
|
XP_FileType tmptype;
|
|
char* tmpname = NULL;
|
|
XP_StatStruct curstat;
|
|
XP_Bool defaultFile;
|
|
BM_SortType enSortType = f->enSortType;
|
|
/* recognize if we're saving the current bookmarks file */
|
|
defaultFile = (filename == NULL || (f->gFile && XP_STRCMP(filename, f->gFile) == 0));
|
|
|
|
CHKCONTEXT(context);
|
|
|
|
if (filename == NULL) {
|
|
filename = f->gFile;
|
|
if (filename == NULL) return -1; /* ### */
|
|
if (XP_Stat(filename, &curstat, xpBookmarks) != 0) {
|
|
/* The stat failed. Treat it as if the stat gave the same thing as last
|
|
time (i.e., make sure to *not* whine about the file changing from us;
|
|
most likely, the user just removed it.) */
|
|
XP_MEMCPY(&curstat, &(f->laststat), sizeof(curstat));
|
|
}
|
|
if (curstat.st_mtime != f->laststat.st_mtime ||
|
|
curstat.st_size != f->laststat.st_size) {
|
|
if (f->gBookmarksModified) {
|
|
if (FE_Confirm(context,
|
|
XP_GetString(context->type == MWContextAddressBook ?
|
|
XP_BKMKS_ADDRESSBOOK_CONFLICT :
|
|
XP_BKMKS_BOOKMARKS_CONFLICT))) {
|
|
f->gBookmarksModified = FALSE; /* Prevent BM_ReadBookmarksFromDisk
|
|
from calling us back again. */
|
|
BM_ReadBookmarksFromDisk(context, filename, NULL);
|
|
return 0;
|
|
}
|
|
} else {
|
|
FE_Alert(context,
|
|
XP_GetString(context->type == MWContextAddressBook ?
|
|
XP_BKMKS_ADDRESSBOOK_CHANGED :
|
|
XP_BKMKS_BOOKMARKS_CHANGED));
|
|
BM_ReadBookmarksFromDisk(context, filename, NULL);
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (!f->gBookmarksModified) return 0; /* No changes need to be saved. */
|
|
}
|
|
}
|
|
|
|
/* Save the natural (user arranged) sort */
|
|
if (defaultFile && (enSortType != BM_Sort_Natural)) {
|
|
bm_SortSilent( context, BM_Sort_Natural );
|
|
}
|
|
|
|
bm_list_name = FE_UsersFullName();
|
|
if (!bm_list_name) bm_list_name = FE_UsersMailAddress();
|
|
|
|
tmpname = FE_GetTempFileFor(NULL, filename, xpBookmarks, &tmptype);
|
|
if (!tmpname || tmpname[0] == 0) goto FAIL;
|
|
|
|
fp = XP_FileOpen(tmpname, tmptype, XP_FILE_WRITE);
|
|
|
|
if (!fp) goto FAIL;
|
|
|
|
/* write cookie */
|
|
if (context->type == MWContextBookmarks) {
|
|
if (bm_write_ok(BMLIST_COOKIE, -1, fp) < 0) goto FAIL;
|
|
} else {
|
|
if (bm_write_ok(BM_ADDR_LIST_COOKIE, -1, fp) < 0) goto FAIL;
|
|
}
|
|
if (bm_write_ok(LINEBREAK, LINEBREAK_LEN, fp) < 0) goto FAIL;
|
|
|
|
if (bm_write_ok(XP_GetString(XP_BKMKS_AUTOGENERATED_FILE), -1, fp) < 0) {
|
|
goto FAIL;
|
|
}
|
|
if (bm_write_ok(LINEBREAK, LINEBREAK_LEN, fp) < 0) goto FAIL;
|
|
if (bm_write_ok(XP_GetString(XP_BKMKS_READ_AND_OVERWRITE), -1, fp) < 0) goto FAIL;
|
|
if (bm_write_ok(LINEBREAK, LINEBREAK_LEN, fp) < 0) goto FAIL;
|
|
if (bm_write_ok(XP_GetString(XP_BKMKS_DO_NOT_EDIT), -1, fp) < 0) goto FAIL;
|
|
if (bm_write_ok(LINEBREAK, LINEBREAK_LEN, fp) < 0) goto FAIL;
|
|
|
|
if(context->type == MWContextBookmarks) {
|
|
if(bm_list_name) {
|
|
XP_FilePrintf(fp, XP_GetString(XP_BKMKS_SOMEONE_S_BOOKMARKS),
|
|
"<TITLE>",
|
|
bm_list_name,
|
|
"</TITLE>" LINEBREAK);
|
|
XP_FilePrintf(fp, XP_GetString(XP_BKMKS_SOMEONE_S_BOOKMARKS),
|
|
"<H1>",
|
|
bm_list_name,
|
|
"</H1>\n" LINEBREAK);
|
|
} else {
|
|
XP_FilePrintf(fp, XP_GetString(XP_BKMKS_PERSONAL_BOOKMARKS),
|
|
"<TITLE>",
|
|
"</TITLE>" LINEBREAK);
|
|
XP_FilePrintf(fp, XP_GetString(XP_BKMKS_PERSONAL_BOOKMARKS),
|
|
"<H1>",
|
|
"</H1>\n" LINEBREAK);
|
|
}
|
|
} else {
|
|
if(bm_list_name) {
|
|
XP_FilePrintf(fp, XP_GetString(XP_BKMKS_SOMEONE_S_ADDRESSBOOK),
|
|
"<TITLE>",
|
|
bm_list_name,
|
|
"</TITLE>" LINEBREAK);
|
|
XP_FilePrintf(fp, XP_GetString(XP_BKMKS_SOMEONE_S_ADDRESSBOOK),
|
|
"<H1>",
|
|
bm_list_name,
|
|
"</H1>\n" LINEBREAK);
|
|
} else {
|
|
XP_FilePrintf(fp, XP_GetString(XP_BKMKS_PERSONAL_ADDRESSBOOK),
|
|
"<TITLE>",
|
|
"</TITLE>" LINEBREAK);
|
|
XP_FilePrintf(fp, XP_GetString(XP_BKMKS_PERSONAL_ADDRESSBOOK),
|
|
"<H1>",
|
|
"</H1>\n" LINEBREAK);
|
|
}
|
|
}
|
|
|
|
bm_clear_alias_info(context);
|
|
|
|
if (BM_GetRoot(context)) {
|
|
if (bm_WriteAsHTML(context, fp, BM_GetRoot(context), 0, FALSE) < 0) {
|
|
goto FAIL;
|
|
}
|
|
} else {
|
|
XP_TRACE(("No bmlist to write!"));
|
|
}
|
|
|
|
if (XP_FileClose(fp) != 0) {
|
|
fp = NULL;
|
|
goto FAIL;
|
|
}
|
|
fp = NULL;
|
|
XP_FileRename(tmpname, tmptype, filename, xpBookmarks);
|
|
XP_FREE(tmpname);
|
|
tmpname = NULL;
|
|
|
|
#ifdef XP_UNIX
|
|
/* If we write the bookmarks with at different uid or gid
|
|
* than what it had, try to change it back.
|
|
* Fix for 67572, bookmarks.html get wiped out.
|
|
*/
|
|
if (curstat.st_uid != getuid()
|
|
|| curstat.st_gid != getgid()) {
|
|
chown (filename, curstat.st_uid, curstat.st_gid);
|
|
}
|
|
#endif /* XP_UNIX */
|
|
|
|
/* only update global mod date if we saved the current bookmk file */
|
|
if (defaultFile) {
|
|
|
|
if (XP_Stat(filename, &(f->laststat), xpBookmarks) != 0) {
|
|
XP_MEMSET(&(f->laststat), 0, sizeof(f->laststat));
|
|
}
|
|
|
|
bm_SetModified(context, FALSE);
|
|
|
|
/* Reset the previous sort order */
|
|
if (enSortType != BM_Sort_Natural) {
|
|
bm_SortSilent( context, enSortType );
|
|
}
|
|
|
|
}
|
|
|
|
if (context->type == MWContextBookmarks)
|
|
f->errorSavingBookmarks = FALSE;
|
|
|
|
return 1;
|
|
|
|
FAIL:
|
|
if (fp) XP_FileClose(fp);
|
|
if (tmpname) {
|
|
XP_FileRemove(tmpname, tmptype);
|
|
XP_FREE(tmpname);
|
|
tmpname = NULL;
|
|
}
|
|
|
|
if (context->type == MWContextAddressBook) {
|
|
FE_Alert(context, XP_GetString(XP_BKMKS_CANT_WRITE_ADDRESSBOOK));
|
|
}
|
|
else {
|
|
if (!f->errorSavingBookmarks) {
|
|
FE_Alert(context, XP_GetString(XP_BKMKS_CANT_WRITE_BOOKMARKS));
|
|
f->errorSavingBookmarks = TRUE;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* returns the first selected item
|
|
if parent is selected, parent is returned,
|
|
otherwise the first selected child is returned
|
|
if there is no selected item following parent,
|
|
NULL is returned */
|
|
PRIVATE BM_Entry*
|
|
bm_FirstSelectedItem_1(BM_Entry* parent)
|
|
{
|
|
BM_Entry* child;
|
|
|
|
XP_ASSERT(parent);
|
|
XP_ASSERT(BM_ISHEADER(parent));
|
|
|
|
if (BM_ISSELECTED(parent)) return parent;
|
|
|
|
child = parent->d.header.children;
|
|
while (child)
|
|
{
|
|
if (child->flags & BM_ATTR_SELECTED)
|
|
return child;
|
|
if (child->type == BM_TYPE_HEADER)
|
|
{
|
|
BM_Entry* rv;
|
|
rv = bm_FirstSelectedItem_1(child);
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
child = child->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
PUBLIC BM_Entry*
|
|
BM_FirstSelectedItem(MWContext* context)
|
|
{
|
|
CHKCONTEXT(context);
|
|
return bm_FirstSelectedItem_1(BM_GetRoot(context));
|
|
}
|
|
|
|
static void
|
|
bm_InsertBySelection(MWContext* context, BM_Entry* firstSelected,
|
|
BM_Entry* newItem)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(firstSelected);
|
|
if (!firstSelected) firstSelected = BM_GetRoot(context);
|
|
|
|
/* If we've selected the root node, then make sure it's not folded, so that
|
|
we will be sure to put our new item inside the root, where it belongs. */
|
|
|
|
if (firstSelected == BM_GetRoot(context) && BM_ISFOLDED(firstSelected)) {
|
|
BM_FoldHeader(context, firstSelected, FALSE, TRUE, FALSE);
|
|
}
|
|
|
|
f->max_depth = 0;
|
|
|
|
if (context->type == MWContextAddressBook && !BM_ISALIAS(newItem)) {
|
|
bm_AddChildToHeaderSorted(context, BM_GetRoot(context), newItem);
|
|
} else {
|
|
/* insert into header if it's open, else after it */
|
|
if (BM_ISHEADER(firstSelected) && !BM_ISFOLDED(firstSelected)) {
|
|
if (context->type == MWContextBookmarks) {
|
|
BM_PrependChildToHeader(context, firstSelected, newItem);
|
|
} else {
|
|
bm_AddChildToHeaderSorted(context, firstSelected, newItem);
|
|
}
|
|
} else {
|
|
if (context->type == MWContextBookmarks) {
|
|
bm_InsertItemAfter(context, firstSelected, newItem, TRUE);
|
|
} else {
|
|
bm_AddChildToHeaderSorted(context, firstSelected->parent, newItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_BeginEditNewHeader(MWContext* context)
|
|
{
|
|
BM_Entry* header;
|
|
BM_Entry* firstSelected;
|
|
int32 index;
|
|
|
|
|
|
firstSelected = BM_FirstSelectedItem(context);
|
|
if (firstSelected)
|
|
index = BM_GetIndex(context, firstSelected);
|
|
else
|
|
{
|
|
firstSelected = BM_GetRoot(context);
|
|
index = 1;
|
|
}
|
|
|
|
header = BM_NewHeader(XP_GetString(XP_BKMKS_NEW_HEADER));
|
|
|
|
BM_SETFLAG(header, BM_ATTR_ISNEW);
|
|
|
|
bm_InsertBySelection(context, firstSelected, header);
|
|
BM_SelectItem(context, header, TRUE, FALSE, TRUE);
|
|
bm_SyncCount(context);
|
|
bm_refresh(context,
|
|
context->type == MWContextBookmarks ? index + 1 : 1,
|
|
BM_LAST_CELL);
|
|
BMFE_OpenBookmarksWindow(context);
|
|
BMFE_EditItem(context, header);
|
|
}
|
|
|
|
static void
|
|
bm_BeginEditNewUrl(MWContext* context)
|
|
{
|
|
BM_Entry* url;
|
|
BM_Entry* firstSelected;
|
|
int32 index;
|
|
|
|
firstSelected = BM_FirstSelectedItem(context);
|
|
if (firstSelected)
|
|
index = BM_GetIndex(context, firstSelected);
|
|
else
|
|
{
|
|
firstSelected = BM_GetRoot(context);
|
|
index = 1;
|
|
}
|
|
|
|
if (context->type == MWContextBookmarks) {
|
|
url = BM_NewUrl(XP_GetString(XP_BKMKS_NEW_BOOKMARK), NULL, NULL, 0);
|
|
} else {
|
|
url = bm_NewAddress("", "");
|
|
}
|
|
if (!url) return;
|
|
BM_SETFLAG(url, BM_ATTR_ISNEW);
|
|
if (context->type == MWContextBookmarks) {
|
|
bm_InsertBySelection(context, firstSelected, url);
|
|
} else {
|
|
bm_AddChildToHeaderSorted(context, BM_GetRoot(context), url);
|
|
}
|
|
BM_SelectItem(context, url, TRUE, FALSE, TRUE);
|
|
bm_SyncCount(context);
|
|
bm_refresh(context,
|
|
context->type == MWContextBookmarks ? index + 1 : 1,
|
|
BM_LAST_CELL);
|
|
BMFE_OpenBookmarksWindow(context);
|
|
BMFE_EditItem(context, url);
|
|
}
|
|
|
|
PUBLIC void
|
|
BM_GotoBookmark(MWContext* context, BM_Entry* item)
|
|
{
|
|
char* url = NULL;
|
|
char* target = NULL;
|
|
|
|
CHKCONTEXTVOID(context);
|
|
|
|
XP_ASSERT(item);
|
|
if (!item) return;
|
|
|
|
if (item->type == BM_TYPE_ALIAS)
|
|
{
|
|
XP_ASSERT(BM_ISURL(item->d.alias.original));
|
|
if (BM_ISURL(item->d.alias.original)) {
|
|
url = item->d.alias.original->d.url.address;
|
|
target = item->d.alias.original->d.url.target;
|
|
}
|
|
}
|
|
else if (item->type == BM_TYPE_URL)
|
|
{
|
|
url = item->d.url.address;
|
|
target = item->d.url.target;
|
|
}
|
|
|
|
if (url)
|
|
BMFE_GotoBookmark(context, url, target);
|
|
}
|
|
|
|
static void
|
|
bm_BeginFindBookmark(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
if (!f->gFindInfo) {
|
|
f->gFindInfo = XP_NEW_ZAP(BM_FindInfo);
|
|
if (!f->gFindInfo) return;
|
|
if (context->type == MWContextAddressBook) {
|
|
f->gFindInfo->checkNickname = TRUE;
|
|
}
|
|
f->gFindInfo->checkName = TRUE;
|
|
f->gFindInfo->checkLocation = TRUE;
|
|
f->gFindInfo->checkDescription = TRUE;
|
|
}
|
|
f->gFindInfo->lastEntry = NULL;
|
|
f->gTemporary = BMFE_OpenFindWindow(context, f->gFindInfo);
|
|
}
|
|
|
|
|
|
PRIVATE void
|
|
bm_SelectAliases(MWContext* context, BM_Entry* at, BM_Entry* forEntry)
|
|
{
|
|
BM_Entry* head;
|
|
for ( ; at ; at = at->next) {
|
|
if (at->type == BM_TYPE_HEADER) {
|
|
bm_SelectAliases(context, at->d.header.children, forEntry);
|
|
} else if (BM_ISALIAS(at) && at->d.alias.original == forEntry) {
|
|
BM_SelectItem(context, at, TRUE, TRUE, TRUE);
|
|
for (head = at->parent ; head ; head = head->parent) {
|
|
if (BM_ISFOLDED(head)) {
|
|
BM_FoldHeader(context, head, FALSE, TRUE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PUBLIC void
|
|
BM_SelectAliases(MWContext* context, BM_Entry* forEntry)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(forEntry);
|
|
if (forEntry) {
|
|
bm_start_batch(context);
|
|
bm_SelectAliases(context, BM_GetRoot(context), forEntry);
|
|
bm_end_batch(context);
|
|
BMFE_ScrollIntoView(context, forEntry);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_CloseLastFind_1(MWContext* context, BM_Entry* entry, XP_Bool closeit)
|
|
{
|
|
for ( ; entry ; entry = entry->next) {
|
|
if (BM_ISHEADER(entry)) {
|
|
if (entry->flags & BM_ATTR_FINDAFF) {
|
|
if (closeit) BM_FoldHeader(context, entry, TRUE, TRUE, FALSE);
|
|
BM_CLEARFLAG(entry, BM_ATTR_FINDAFF);
|
|
}
|
|
bm_CloseLastFind_1(context, entry->d.header.children, closeit);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Close any headers that we may have opened last time we did a find. */
|
|
static void
|
|
bm_CloseLastFind(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
if (f->unfoldedForFind) {
|
|
f->unfoldedForFind = FALSE; /* Do this first, to not confuse
|
|
BM_FoldHeader(). */
|
|
bm_CloseLastFind_1(context, BM_GetRoot(context), TRUE);
|
|
}
|
|
}
|
|
|
|
/* Forget about any headers that were opened last time we did a find; leave
|
|
things the way they are now. */
|
|
static void
|
|
bm_CancelLastFind(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
if (f->unfoldedForFind) {
|
|
f->unfoldedForFind = FALSE;
|
|
bm_CloseLastFind_1(context, BM_GetRoot(context), FALSE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_OpenNewFind(MWContext* context, BM_Entry* entry)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* head;
|
|
XP_Bool found = FALSE;
|
|
if (!entry) return;
|
|
bm_CloseLastFind(context);
|
|
for (head = entry->parent ; head ; head = head->parent) {
|
|
if (BM_ISFOLDED(head)) {
|
|
BM_FoldHeader(context, head, FALSE, TRUE, FALSE);
|
|
BM_SETFLAG(head, BM_ATTR_FINDAFF);
|
|
found = TRUE;
|
|
}
|
|
}
|
|
f->unfoldedForFind = found; /* Must set last, to not confuse
|
|
BM_FoldHeader. */
|
|
}
|
|
|
|
|
|
void
|
|
BM_DoFindBookmark(MWContext* context, BM_FindInfo* findInfo)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* found = NULL;
|
|
BM_Entry* startAt = NULL;
|
|
XP_Bool unfoldedForFind;
|
|
|
|
/* If no find string is specified, return. */
|
|
if (findInfo->textToFind == NULL)
|
|
return;
|
|
|
|
bm_CloseLastFind(context);
|
|
|
|
if (findInfo->lastEntry) {
|
|
startAt = bm_GetNextSpanningWrapping(context, findInfo->lastEntry);
|
|
} else {
|
|
startAt = BM_GetRoot(context);
|
|
}
|
|
found = bm_DoFindBookmark_1(context, startAt, findInfo);
|
|
|
|
if (found) {
|
|
bm_CloseLastFind(context);
|
|
bm_OpenNewFind(context, found);
|
|
unfoldedForFind = f->unfoldedForFind;
|
|
f->unfoldedForFind = FALSE; /* Don't confuse BM_SelectItem */
|
|
BM_SelectItem(context, found, TRUE, FALSE, TRUE);
|
|
bm_flush_updates(context);
|
|
BMFE_ScrollIntoView(context, found);
|
|
f->unfoldedForFind = unfoldedForFind;
|
|
} else {
|
|
FE_Alert(context, XP_GetString(XP_BKMKS_NOT_FOUND));
|
|
}
|
|
findInfo->lastEntry = found;
|
|
}
|
|
|
|
|
|
static void
|
|
bm_parse_mailto(const char* url, char** name, char** addr)
|
|
{
|
|
char* ptr;
|
|
char* buf;
|
|
int32 L;
|
|
if (name) *name = NULL;
|
|
if (addr) *addr = NULL;
|
|
if (strncasecomp(url, "mailto:?to=", 11) != 0) return;
|
|
url += 11;
|
|
ptr = XP_STRCHR(url, '&');
|
|
L = (ptr ? (ptr - url) : XP_STRLEN(url));
|
|
buf = (char *) XP_ALLOC(L+1);
|
|
if (!buf) return;
|
|
XP_MEMCPY (buf, url, L);
|
|
buf[L] = 0;
|
|
buf = NET_UnEscape(buf);
|
|
|
|
#if 0
|
|
MSG_ParseRFC822Addresses(buf, name, addr);
|
|
#else
|
|
/* We need to do it this way to get msg_quote_phrase_or_addr() to be
|
|
called on the names. Perhaps that function should just be exported...
|
|
*/
|
|
#ifdef MOZ_MAIL_NEWS
|
|
if (name) *name = MSG_ExtractRFC822AddressNames (buf);
|
|
if (addr) *addr = MSG_ExtractRFC822AddressMailboxes (buf);
|
|
#endif /* MOZ_MAIL_NEWS */
|
|
#endif
|
|
|
|
XP_FREE(buf);
|
|
}
|
|
|
|
BM_Entry*
|
|
BM_FindAddress(MWContext* context, const char* url)
|
|
{
|
|
char* address;
|
|
BM_Entry* result = NULL;
|
|
BM_Entry* entry;
|
|
CHKCONTEXT(context);
|
|
XP_ASSERT(url);
|
|
if (!url) return NULL;
|
|
bm_parse_mailto(url, NULL, &address);
|
|
if (!address) return NULL;
|
|
|
|
/* Takes advantage of the fact that addressbook always has all address
|
|
entries as children of the root header. */
|
|
for (entry = BM_GetRoot(context)->d.header.children;
|
|
entry;
|
|
entry = entry->next) {
|
|
if (BM_ISADDRESS(entry) &&
|
|
XP_STRCMP(entry->d.address.address, address) == 0) {
|
|
result = entry;
|
|
break;
|
|
}
|
|
}
|
|
XP_FREE(address);
|
|
return result;
|
|
}
|
|
|
|
|
|
void
|
|
BM_EditAddress(MWContext* context, const char* url)
|
|
{
|
|
BM_Entry* entry;
|
|
CHKCONTEXTVOID(context);
|
|
XP_ASSERT(url);
|
|
if (!url) return;
|
|
bm_start_batch(context);
|
|
entry = BM_FindAddress(context, url);
|
|
if (!entry) {
|
|
char* name;
|
|
char* address;
|
|
bm_parse_mailto(url, &name, &address); /* Parsing a second time. Oh,
|
|
well. ### */
|
|
if (!name) name = XP_STRDUP("");
|
|
if (!address) address = XP_STRDUP("");
|
|
if (!name || !address) goto FAIL;
|
|
|
|
entry = bm_NewAddress(name, address);
|
|
XP_FREE(name);
|
|
XP_FREE(address);
|
|
if (!entry) goto FAIL;
|
|
BM_SETFLAG(entry, BM_ATTR_ISNEW);
|
|
bm_AddChildToHeaderSorted(context, BM_GetRoot(context), entry);
|
|
BM_SelectItem(context, entry, TRUE, FALSE, TRUE);
|
|
bm_SyncCount(context);
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
}
|
|
BMFE_OpenBookmarksWindow(context);
|
|
BMFE_EditItem(context, entry);
|
|
FAIL:
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Utilities to fuss with bookmarks in a drag and drop evnironment
|
|
*
|
|
* There are two user visible functions in this file:
|
|
*
|
|
* Allocate and return a string that contains the text representation of
|
|
* a list of bookmarks entries (including headers and their contents).
|
|
* The caller is responsible for freeing the string. The total length of
|
|
* the block that was allocated is returned in lTotalLen. List is the
|
|
* list of pointers to bookmarks items that are selected, iCount is the
|
|
* length of that list.
|
|
* This function has two modes of operation, a short mode and a long mode.
|
|
* If bLongFormat == FALSE the returned block just has URLs separated by
|
|
* \n's. If bLongFormat == TRUE all of the information needed to recreate
|
|
* the bookmarks item is included
|
|
*
|
|
|
|
PUBLIC char *
|
|
BM_ConvertSelectionsToBlock(BM_Entry ** list,
|
|
int iCount,
|
|
int bLongFormat,
|
|
int32 * lTotalLen);
|
|
*
|
|
* ------------------------
|
|
*
|
|
* Take a block of memory formatted by BM_ConvertSelectionsToBlock and insert
|
|
* the items it represents into the bookmarks following 'item'. If item is
|
|
* NULL insert at the beginning of the bookmarks. bLongFormat has the same
|
|
* meaning as in BM_ConvertSelectionsToBlock(). lTotalLen should be the
|
|
* length of the block of memory --- I'm not sure if this is necessary
|
|
* because on Windows at least the value we get back is meaningless, so
|
|
* this function just ignores it.
|
|
*
|
|
PUBLIC void
|
|
BM_InsertBlockAt(char * pOriginalBlock,
|
|
BM_Entry * item,
|
|
int bLongFormat,
|
|
int32 lTotalLen);
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
#define TEXT_INDENT 3
|
|
|
|
/*
|
|
* Measure a boring URL bookmarks entry and return the length in bytes
|
|
*
|
|
* Short format:
|
|
* " item->address\n"
|
|
* where there are nIndent number of spaces before the item
|
|
*
|
|
* Long format:
|
|
* uint16 type
|
|
* char item->name\n
|
|
* char item->address\n
|
|
* time_t addition_date
|
|
* time_t last_visit_date
|
|
* time_t last_modified_date
|
|
* char item->description\0
|
|
*
|
|
* The item->description field is *NOT* \n terminated since it might
|
|
* be a multi-line string and is therefore \0 terminated
|
|
*/
|
|
PRIVATE int32
|
|
bm_measure_URL(BM_Entry* item, XP_Bool bLongFormat, int nIndent)
|
|
{
|
|
int32 iSpace = 0;
|
|
|
|
if (!item)
|
|
return 0;
|
|
|
|
XP_ASSERT(BM_ISURL(item));
|
|
|
|
/* NO. We cannot check for ISSELECTED here. We could be called
|
|
* by bm_measure_Header which was selected although we by
|
|
* ourselves are not selected.
|
|
*
|
|
* if (! BM_ISSELECTED(item))
|
|
* return 0;
|
|
*/
|
|
|
|
if (bLongFormat)
|
|
{
|
|
iSpace += sizeof(item->type) +
|
|
sizeof(item->addition_date) +
|
|
sizeof(item->d.url.last_visit) +
|
|
sizeof(item->d.url.last_modified);
|
|
|
|
if (item->name)
|
|
iSpace += XP_STRLEN(item->name);
|
|
iSpace++; /* +1 for '\n' */
|
|
if (item->description)
|
|
iSpace += XP_STRLEN(item->description);
|
|
iSpace++; /* +1 for '\0' */
|
|
}
|
|
else
|
|
{
|
|
/* space indentation and '\n' terminator */
|
|
iSpace = nIndent;
|
|
}
|
|
|
|
/* the address appears in both formats */
|
|
if (item->d.url.address)
|
|
iSpace += (XP_STRLEN(item->d.url.address) + 1); /* +1 for '\n' */
|
|
|
|
return (iSpace);
|
|
}
|
|
|
|
PRIVATE int32
|
|
bm_measure_Alias(BM_Entry* item, XP_Bool bLongFormat, int nIndent)
|
|
{
|
|
int32 iSpace = 0;
|
|
|
|
XP_ASSERT(BM_ISALIAS(item));
|
|
|
|
if (bLongFormat)
|
|
{
|
|
iSpace += sizeof(item->type);
|
|
}
|
|
return (iSpace);
|
|
}
|
|
|
|
/*
|
|
* Measure a separator entry and return the length in bytes
|
|
*
|
|
* Short format:
|
|
* " -------------\0"
|
|
* where there are nIndent number of spaces before the 13 -'s
|
|
*
|
|
* Long format:
|
|
* uint16 type
|
|
*
|
|
*/
|
|
PRIVATE int32
|
|
bm_measure_Separator(BM_Entry* item, XP_Bool bLongFormat, int nIndent)
|
|
{
|
|
int32 iSpace = 0;
|
|
|
|
if (!item)
|
|
return 0;
|
|
|
|
XP_ASSERT(BM_ISSEPARATOR(item));
|
|
|
|
if (bLongFormat)
|
|
{
|
|
iSpace += sizeof(item->type);
|
|
}
|
|
else
|
|
{
|
|
/* space indentation and '\n' terminator */
|
|
iSpace = nIndent;
|
|
iSpace += 13;
|
|
iSpace ++ /* for '\n' */;
|
|
}
|
|
|
|
return (iSpace);
|
|
}
|
|
|
|
/*
|
|
* Measure a header entry and all its children
|
|
*
|
|
* Short format:
|
|
* " item->name\n"
|
|
* " child1->address\n"
|
|
* " child2->address\n"
|
|
* where there are nIndent number of spaces before the item and
|
|
* TEXT_INDENT spaces between levels
|
|
*
|
|
* Long format:
|
|
* uint16 type
|
|
* char item->name\n
|
|
* time_t addition_date
|
|
* uint32 number of children
|
|
* char item->description\0
|
|
*
|
|
* The item->description field is *NOT* \n terminated since it might
|
|
* be a multi-line string and is therefore \0 terminated. Note that
|
|
* the address field is *NOT* written for headers since its it meaningless
|
|
*/
|
|
PRIVATE int32
|
|
bm_measure_Header(BM_Entry* item, XP_Bool bLongFormat, int nIndent)
|
|
{
|
|
int32 iSpace = 0;
|
|
BM_Entry* child = NULL;
|
|
|
|
if (!item)
|
|
return 0;
|
|
|
|
XP_ASSERT(BM_ISHEADER(item));
|
|
|
|
/* if the header is selected, count it as well */
|
|
if (bLongFormat)
|
|
{
|
|
iSpace += sizeof(item->type) +
|
|
sizeof(item->addition_date) +
|
|
sizeof(item->d.header.childCount);
|
|
|
|
if (item->description)
|
|
iSpace += XP_STRLEN(item->description);
|
|
iSpace++; /* for \0 */
|
|
}
|
|
else
|
|
{
|
|
/* space indentation and '\n' terminator */
|
|
iSpace = nIndent;
|
|
}
|
|
|
|
/* the name appears in both formats */
|
|
if (item->name)
|
|
iSpace += XP_STRLEN(item->name);
|
|
iSpace ++; /* for \n terminator */
|
|
|
|
/* measure the amount of space taken up by this item's children */
|
|
child = item->d.header.children;
|
|
while (child)
|
|
{
|
|
switch (child->type)
|
|
{
|
|
case BM_TYPE_URL:
|
|
iSpace += bm_measure_URL(child, bLongFormat, nIndent + TEXT_INDENT);
|
|
break;
|
|
case BM_TYPE_ALIAS:
|
|
iSpace += bm_measure_Alias(child, bLongFormat, nIndent + TEXT_INDENT);
|
|
break;
|
|
case BM_TYPE_HEADER:
|
|
iSpace += bm_measure_Header(child, bLongFormat, nIndent + TEXT_INDENT);
|
|
break;
|
|
case BM_TYPE_SEPARATOR:
|
|
iSpace += bm_measure_Separator(child, bLongFormat, nIndent + TEXT_INDENT);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
child = child->next;
|
|
}
|
|
|
|
return iSpace;
|
|
}
|
|
|
|
/*
|
|
* Write out a separator bookmarks entry.
|
|
*/
|
|
PRIVATE char*
|
|
bm_write_Separator(char* buffer, BM_Entry* item, XP_Bool bLongFormat, int nIndent)
|
|
{
|
|
int32 iLen;
|
|
BM_Type type;
|
|
|
|
if (!item || !buffer)
|
|
return buffer;
|
|
|
|
XP_ASSERT(BM_ISSEPARATOR(item));
|
|
|
|
if (bLongFormat)
|
|
{
|
|
/* copy the type */
|
|
type = item->type;
|
|
iLen = sizeof(BM_Type);
|
|
XP_MEMCPY(buffer, &type, iLen);
|
|
buffer += iLen;
|
|
}
|
|
else
|
|
{
|
|
XP_MEMSET(buffer, ' ', nIndent);
|
|
buffer += nIndent;
|
|
XP_MEMSET(buffer, '-', 13);
|
|
buffer += 13;
|
|
*buffer++ = '\0';
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/*
|
|
* Write out a boring URL bookmarks entry. See comment at the top of
|
|
* bm_measure_URL for the format used. Assume we start writing at
|
|
* the start of the buffer passed in. Return a pointer to where the
|
|
* buffer ends when we get done.
|
|
*/
|
|
PRIVATE char*
|
|
bm_write_URL(char* buffer, BM_Entry* item, XP_Bool bLongFormat, int nIndent)
|
|
{
|
|
int32 iLen;
|
|
BM_Date lVal;
|
|
BM_Type type;
|
|
|
|
if (!item || !buffer)
|
|
return buffer;
|
|
|
|
XP_ASSERT(BM_ISURL(item) || BM_ISALIAS(item));
|
|
|
|
if (bLongFormat)
|
|
{
|
|
/* copy the type */
|
|
type = item->type;
|
|
iLen = sizeof(BM_Type);
|
|
XP_MEMCPY(buffer, &type, iLen);
|
|
buffer += iLen;
|
|
|
|
if (BM_ISALIAS(item))
|
|
return buffer;
|
|
|
|
/* copy the name */
|
|
if (item->name)
|
|
{
|
|
iLen = XP_STRLEN(item->name);
|
|
XP_MEMCPY(buffer, item->name, iLen);
|
|
buffer += iLen;
|
|
}
|
|
/* put the \n terminator on */
|
|
*buffer++ = '\n';
|
|
|
|
/* copy the address */
|
|
if (item->d.url.address)
|
|
{
|
|
iLen = XP_STRLEN(item->d.url.address);
|
|
XP_MEMCPY(buffer, item->d.url.address, iLen);
|
|
buffer += iLen;
|
|
}
|
|
/* put the \n terminator on */
|
|
*buffer++ = '\n';
|
|
|
|
/* addition date */
|
|
lVal = item->addition_date;
|
|
iLen = sizeof(BM_Date);
|
|
XP_MEMCPY(buffer, &lVal, iLen);
|
|
buffer += iLen;
|
|
|
|
/* last visit date */
|
|
lVal = item->d.url.last_visit;
|
|
iLen = sizeof(BM_Date);
|
|
XP_MEMCPY(buffer, &lVal, iLen);
|
|
buffer += iLen;
|
|
|
|
/* last modified date */
|
|
lVal = item->d.url.last_modified;
|
|
iLen = sizeof(BM_Date);
|
|
XP_MEMCPY(buffer, &lVal, iLen);
|
|
buffer += iLen;
|
|
|
|
/* copy the description */
|
|
if (item->description)
|
|
{
|
|
iLen = XP_STRLEN(item->description);
|
|
XP_MEMCPY(buffer, item->description, iLen);
|
|
buffer += iLen;
|
|
}
|
|
/* put the \n terminator on */
|
|
*buffer++ = '\0';
|
|
|
|
}
|
|
else if (BM_ISURL(item))
|
|
{
|
|
XP_MEMSET(buffer, ' ', nIndent);
|
|
buffer += nIndent;
|
|
|
|
if(item->d.url.address)
|
|
{
|
|
XP_STRCPY(buffer, item->d.url.address);
|
|
buffer += XP_STRLEN(item->d.url.address);
|
|
}
|
|
*buffer++ = '\n';
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
|
|
/*
|
|
* Write out a bookmarks header entry. See comment at the top of
|
|
* bm_measure_Header for the format used. Assume we start writing at
|
|
* the start of the buffer passed in. Return a pointer to where the
|
|
* buffer ends when we get done.
|
|
*/
|
|
PRIVATE char*
|
|
bm_write_Header(char* buffer, BM_Entry* item, XP_Bool bLongFormat, int nIndent)
|
|
{
|
|
long iLen;
|
|
BM_Date lVal;
|
|
BM_Type type;
|
|
uint32 children;
|
|
BM_Entry* child = NULL;
|
|
|
|
if (!item || !buffer)
|
|
return buffer;
|
|
|
|
XP_ASSERT(BM_ISHEADER(item));
|
|
|
|
if (bLongFormat)
|
|
{
|
|
/* copy the type */
|
|
type = item->type;
|
|
iLen = sizeof(BM_Type);
|
|
XP_MEMCPY(buffer, &type, iLen);
|
|
buffer += iLen;
|
|
|
|
/* copy the name */
|
|
if (item->name)
|
|
{
|
|
iLen = XP_STRLEN(item->name);
|
|
XP_MEMCPY(buffer, item->name, iLen);
|
|
buffer += iLen;
|
|
}
|
|
/* put the \n terminator on */
|
|
*buffer++ = '\n';
|
|
|
|
/* addition date */
|
|
lVal = item->addition_date;
|
|
iLen = sizeof(BM_Date);
|
|
XP_MEMCPY(buffer, &lVal, iLen);
|
|
buffer += iLen;
|
|
|
|
/* number of children */
|
|
children = item->d.header.childCount;
|
|
iLen = sizeof(uint32);
|
|
XP_MEMCPY(buffer, &children, iLen);
|
|
buffer += iLen;
|
|
|
|
/* copy the description */
|
|
if (item->description)
|
|
{
|
|
iLen = XP_STRLEN(item->description);
|
|
XP_MEMCPY(buffer, item->description, iLen);
|
|
buffer += iLen;
|
|
}
|
|
/* put the \n terminator on */
|
|
*buffer++ = '\0';
|
|
|
|
}
|
|
else
|
|
{
|
|
XP_MEMSET(buffer, ' ', nIndent);
|
|
buffer += nIndent;
|
|
if(item->name)
|
|
{
|
|
XP_STRCPY(buffer, item->name);
|
|
buffer += XP_STRLEN(item->name);
|
|
}
|
|
*buffer++ = '\n';
|
|
}
|
|
|
|
child = item->d.header.children;
|
|
while (child)
|
|
{
|
|
switch (child->type)
|
|
{
|
|
case BM_TYPE_URL:
|
|
case BM_TYPE_ALIAS:
|
|
buffer = bm_write_URL(buffer, child, bLongFormat, nIndent + TEXT_INDENT);
|
|
break;
|
|
case BM_TYPE_HEADER:
|
|
buffer = bm_write_Header(buffer, child, bLongFormat, nIndent + TEXT_INDENT);
|
|
break;
|
|
case BM_TYPE_SEPARATOR:
|
|
buffer = bm_write_Separator(buffer, child, bLongFormat, nIndent + TEXT_INDENT);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
child = child->next;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/*
|
|
* Take a separator packed in a block the way bm_write_Separator packs it.
|
|
* Return the new item if we created one
|
|
*/
|
|
PRIVATE BM_Entry*
|
|
bm_read_Separator(char* buffer, XP_Bool bLongFormat, int32* lBytesEaten)
|
|
{
|
|
BM_Entry* new_item = NULL;
|
|
|
|
if (!buffer)
|
|
return NULL;
|
|
|
|
if (bLongFormat)
|
|
{
|
|
/* for now the separator written has only the type.
|
|
since that was already read in before this was clled
|
|
we have nothing to eat here */
|
|
new_item = bm_NewSeparator();
|
|
*lBytesEaten = 0;
|
|
}
|
|
else
|
|
{
|
|
/* we should really strip leading whitespace */
|
|
new_item = bm_NewSeparator();
|
|
*lBytesEaten = XP_STRLEN(buffer) + 1;
|
|
}
|
|
|
|
return new_item;
|
|
}
|
|
|
|
/*
|
|
* Take a URL packed in a block the way bm_write_URL packs it.
|
|
* Return the new item if we created one
|
|
*/
|
|
PRIVATE BM_Entry*
|
|
bm_read_url_long(char* buffer, XP_Bool bLongFormat, int32* lBytesEaten)
|
|
{
|
|
BM_Entry* new_item = NULL;
|
|
|
|
if (!buffer)
|
|
return NULL;
|
|
|
|
if (bLongFormat)
|
|
{
|
|
BM_Date addition;
|
|
BM_Date visit;
|
|
BM_Date modified;
|
|
|
|
/* get the name */
|
|
char* name = buffer;
|
|
char* address = strchr(name, '\n');
|
|
char* description = NULL;
|
|
char* ptr;
|
|
if (!address)
|
|
return NULL;
|
|
*address++ = '\0';
|
|
|
|
/* get the address */
|
|
ptr = strchr(address, '\n');
|
|
if(!ptr)
|
|
return NULL;
|
|
*ptr++ = '\0';
|
|
|
|
/* addition date */
|
|
XP_MEMCPY(&addition, ptr, sizeof(BM_Date));
|
|
ptr += sizeof(BM_Date);
|
|
|
|
/* visiting date */
|
|
XP_MEMCPY(&visit, ptr, sizeof(BM_Date));
|
|
ptr += sizeof(BM_Date);
|
|
|
|
/* modified date */
|
|
XP_MEMCPY(&modified, ptr, sizeof(BM_Date));
|
|
ptr += sizeof(BM_Date);
|
|
|
|
/* get the description (it should be NULL terminated) */
|
|
description = ptr;
|
|
|
|
/* we should really strip leading whitespace */
|
|
new_item = BM_NewUrl(name, address, 0, visit);
|
|
new_item->addition_date = addition;
|
|
new_item->description = XP_STRDUP(description);
|
|
new_item->d.url.last_modified = modified;
|
|
*lBytesEaten = XP_STRLEN(description) + (description - buffer) + 1;
|
|
|
|
}
|
|
else
|
|
{
|
|
char* end = strchr(buffer, '\n');
|
|
|
|
/* if there was a return NULL terminate the current string */
|
|
if (end)
|
|
*end++ = '\0';
|
|
|
|
/* we should really strip leading whitespace */
|
|
new_item = BM_NewUrl(buffer, buffer, 0, 0);
|
|
new_item->addition_date = XP_TIME();
|
|
*lBytesEaten = XP_STRLEN(buffer) + 1;
|
|
}
|
|
|
|
return new_item;
|
|
}
|
|
|
|
/*
|
|
* Take a header and children packed in a block the way bm_write_Header
|
|
* packs it. Return the new header item if we created one
|
|
*/
|
|
PRIVATE BM_Entry*
|
|
bm_read_header_long(MWContext* context, char* buffer, XP_Bool bLongFormat,
|
|
int32* lBytesEaten)
|
|
{
|
|
uint32 kids = 0;
|
|
BM_Type type;
|
|
BM_Entry* new_item = NULL;
|
|
BM_Entry* kid = NULL;
|
|
char* name = NULL;
|
|
char* ptr = NULL;
|
|
char* description = NULL;
|
|
BM_Date addition;
|
|
uint32 i;
|
|
int32 lEat;
|
|
|
|
if (!buffer)
|
|
return NULL;
|
|
|
|
/* can only read long format headers */
|
|
if (bLongFormat)
|
|
{
|
|
/* get the name */
|
|
name = buffer;
|
|
ptr = strchr(name, '\n');
|
|
description = NULL;
|
|
if (!ptr)
|
|
return NULL;
|
|
|
|
/* skip over the \n but change it to a \0 so strcpy() will work */
|
|
*ptr++ = '\0';
|
|
|
|
/* addition date */
|
|
XP_MEMCPY(&addition, ptr, sizeof(BM_Date));
|
|
ptr += sizeof(BM_Date);
|
|
|
|
/* number of children to read */
|
|
XP_MEMCPY(&kids, ptr, sizeof(uint32));
|
|
ptr += sizeof(uint32);
|
|
|
|
/* get the description (it should be NULL terminated) */
|
|
description = ptr;
|
|
|
|
/* we should really strip leading whitespace */
|
|
new_item = BM_NewHeader(name);
|
|
new_item->addition_date = addition;
|
|
new_item->description = XP_STRDUP(description);
|
|
*lBytesEaten = XP_STRLEN(description) + (description - buffer) + 1;
|
|
|
|
/* handle all of the kids now */
|
|
if (kids)
|
|
{
|
|
buffer += *lBytesEaten;
|
|
|
|
for (i = 0; i < kids; i++)
|
|
{
|
|
/* determine the type of the next entry */
|
|
XP_MEMCPY(&type, buffer, sizeof(BM_Type));
|
|
buffer += sizeof(BM_Type);
|
|
*lBytesEaten += sizeof(BM_Type);
|
|
|
|
switch (type)
|
|
{
|
|
case BM_TYPE_URL:
|
|
kid = bm_read_url_long(buffer, bLongFormat, &lEat);
|
|
*lBytesEaten += lEat;
|
|
buffer += lEat;
|
|
bm_AppendChildToHeader(context, new_item, kid);
|
|
break;
|
|
case BM_TYPE_ALIAS:
|
|
break;
|
|
case BM_TYPE_HEADER:
|
|
kid = bm_read_header_long(context, buffer, bLongFormat, &lEat);
|
|
*lBytesEaten += lEat;
|
|
buffer += lEat;
|
|
bm_AppendChildToHeader(context, new_item, kid);
|
|
break;
|
|
case BM_TYPE_SEPARATOR:
|
|
kid = bm_read_Separator(buffer, bLongFormat, &lEat);
|
|
*lBytesEaten += lEat;
|
|
buffer += lEat;
|
|
bm_AppendChildToHeader(context, new_item, kid);
|
|
break;
|
|
case 12345:
|
|
/* Ah ha! this is the end marker we wrote. Something
|
|
terribly wrong here. We shouldn't have hit this
|
|
before we read all the kids in. */
|
|
abort();
|
|
break;
|
|
default:
|
|
/* bogus type. Who knows whats going on. Just quit and get out */
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return new_item;
|
|
}
|
|
|
|
PRIVATE int32
|
|
bm_measure(BM_Entry* root, XP_Bool bLongFormat, int32 indent)
|
|
{
|
|
int32 iSpace = 0;
|
|
BM_Entry* child;
|
|
|
|
XP_ASSERT(root);
|
|
XP_ASSERT(BM_ISHEADER(root));
|
|
|
|
child = root->d.header.children;
|
|
|
|
while (child)
|
|
{
|
|
switch (child->type)
|
|
{
|
|
case BM_TYPE_URL:
|
|
if (BM_ISSELECTED(child))
|
|
iSpace += bm_measure_URL(child, bLongFormat, indent);
|
|
break;
|
|
case BM_TYPE_ALIAS:
|
|
iSpace += bm_measure_Alias(child, bLongFormat, indent + TEXT_INDENT);
|
|
break;
|
|
case BM_TYPE_HEADER:
|
|
if (BM_ISSELECTED(child))
|
|
iSpace += bm_measure_Header(child, bLongFormat, indent);
|
|
else
|
|
if (! BM_ISFOLDED(child))
|
|
iSpace += bm_measure(child, bLongFormat, indent);
|
|
break;
|
|
case BM_TYPE_SEPARATOR:
|
|
if (BM_ISSELECTED(child))
|
|
iSpace += bm_measure_Separator(child, bLongFormat, indent);
|
|
break;
|
|
}
|
|
child = child->next;
|
|
}
|
|
return iSpace;
|
|
}
|
|
|
|
PRIVATE char*
|
|
bm_write(char* buffer, BM_Entry* root, XP_Bool bLongFormat, int32 indent)
|
|
{
|
|
BM_Entry* child;
|
|
|
|
XP_ASSERT(root);
|
|
XP_ASSERT(BM_ISHEADER(root));
|
|
|
|
child = root->d.header.children;
|
|
|
|
while (child)
|
|
{
|
|
switch (child->type)
|
|
{
|
|
case BM_TYPE_URL:
|
|
case BM_TYPE_ALIAS:
|
|
if (BM_ISSELECTED(child))
|
|
buffer = bm_write_URL(buffer, child, bLongFormat, indent);
|
|
break;
|
|
|
|
case BM_TYPE_HEADER:
|
|
if (BM_ISSELECTED(child))
|
|
buffer = bm_write_Header(buffer, child, bLongFormat, indent);
|
|
else
|
|
if (! BM_ISFOLDED(child))
|
|
buffer = bm_write(buffer, child, bLongFormat, indent);
|
|
break;
|
|
case BM_TYPE_SEPARATOR:
|
|
if (BM_ISSELECTED(child))
|
|
buffer = bm_write_Separator(buffer, child, bLongFormat, indent);
|
|
break;
|
|
}
|
|
child = child->next;
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
/*
|
|
* Allocate and return a string that contains the text representation of
|
|
* a list of bookmarks entries from specified selections in a History window.
|
|
* The caller is responsible for freeing the string
|
|
*/
|
|
PUBLIC char *
|
|
BM_ClipCopyHistorySelection( void *pHistCsr, uint32 *pSelections, int iCount, int *pSize, XP_Bool bLongFormat )
|
|
{
|
|
int i, iLen, iSize = 0;
|
|
uint16 marker;
|
|
char * pStorage = NULL;
|
|
char * pStgCsr = NULL;
|
|
BM_Entry * pBM = NULL;
|
|
BM_Entry * pPrevBM = NULL;
|
|
BM_Entry * pCsr = NULL;
|
|
BM_Entry * pBMList = NULL;
|
|
|
|
if( !pHistCsr || !pSelections || iCount <= 0 )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Build the list of BM_Entrys and calc the total size
|
|
*/
|
|
for( i = 0; i < iCount; i++ )
|
|
{
|
|
gh_HistEntry *pHistEntry = GH_GetRecord( pHistCsr, pSelections[i] );
|
|
if( !pHistEntry )
|
|
{
|
|
/* In case history file was somehow compromised */
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Map the gh_HistEntry to the BM_Entry
|
|
*/
|
|
|
|
pBM = (BM_Entry *)XP_ALLOC( sizeof(BM_Entry) );
|
|
XP_MEMSET( pBM, 0, sizeof(BM_Entry) );
|
|
if( !pPrevBM )
|
|
{
|
|
pBMList = pBM;
|
|
}
|
|
else
|
|
{
|
|
pPrevBM->next = pBM;
|
|
}
|
|
pPrevBM = pBM;
|
|
|
|
/* type */
|
|
pBM->type = BM_TYPE_URL;
|
|
|
|
if( bLongFormat )
|
|
{
|
|
/* name */
|
|
iLen = pHistEntry->pszName ? XP_STRLEN( pHistEntry->pszName )+1 : 0;
|
|
pBM->name = iLen ? (char *)XP_ALLOC( iLen*sizeof(char) ) : NULL;
|
|
if( iLen )
|
|
{
|
|
XP_STRCPY( pBM->name, pHistEntry->pszName );
|
|
}
|
|
|
|
/* addition date */
|
|
time( &pBM->addition_date );
|
|
|
|
/* last visit date */
|
|
pBM->d.url.last_visit = pHistEntry->last_accessed;
|
|
|
|
/* last modified */
|
|
time( &pBM->d.url.last_modified );
|
|
|
|
/* description */
|
|
pBM->description = NULL;
|
|
}
|
|
|
|
/* address */
|
|
iLen = pHistEntry->address ? XP_STRLEN( pHistEntry->address )+1 : 0;
|
|
pBM->d.url.address = iLen ? (char *)XP_ALLOC( iLen*sizeof(char) ) : NULL;
|
|
if( iLen )
|
|
{
|
|
XP_STRCPY( pBM->d.url.address, pHistEntry->address );
|
|
}
|
|
|
|
|
|
/*
|
|
* Calc the size
|
|
*/
|
|
|
|
iSize += bm_measure_URL( pBM, bLongFormat, 0 );
|
|
}
|
|
|
|
if( bLongFormat )
|
|
{
|
|
iSize += sizeof(uint16);
|
|
}
|
|
|
|
/* Leave room for the termination character */
|
|
iSize++;
|
|
|
|
#ifdef XP_WIN16
|
|
if( (uint16)iSize > (uint16)(32*1024-1) )
|
|
{
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/* Allocate the storage */
|
|
pStorage = pStgCsr = (char *)XP_ALLOC( iSize*sizeof(char) );
|
|
if( !pStorage )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Copy the bookmarks to the storage */
|
|
pCsr = pBMList;
|
|
while( pCsr )
|
|
{
|
|
pStgCsr = bm_write_URL( pStgCsr, pCsr, bLongFormat, 0 );
|
|
|
|
pPrevBM = pCsr;
|
|
|
|
pCsr = pCsr->next;
|
|
|
|
/* Free the entry */
|
|
pPrevBM->next = NULL;
|
|
bm_ShallowFreeEntry( pPrevBM );
|
|
}
|
|
|
|
if (bLongFormat)
|
|
{
|
|
/* Write the end-of-list marker */
|
|
marker = 12345;
|
|
XP_MEMCPY( pStgCsr, &marker, 2 );
|
|
pStgCsr += sizeof(uint16);
|
|
}
|
|
|
|
/* End the string */
|
|
*pStgCsr++ = '\0';
|
|
*pSize = (pStgCsr - pStorage);
|
|
|
|
return pStorage;
|
|
}
|
|
|
|
|
|
/*
|
|
* Allocate and return a string that contains the text representation of
|
|
* a list of bookmarks entries (including headers and their contents).
|
|
* The caller is responsible for freeing the string
|
|
*/
|
|
PUBLIC char*
|
|
BM_ConvertSelectionsToBlock(MWContext* context,
|
|
XP_Bool bLongFormat,
|
|
int32* lTotalLen)
|
|
{
|
|
uint16 marker;
|
|
int32 iSpace = 0;
|
|
char* pString;
|
|
char* pOriginal;
|
|
|
|
BM_Entry* tmp;
|
|
|
|
CHKCONTEXT(context);
|
|
|
|
tmp = BM_GetRoot(context);
|
|
iSpace = bm_measure(tmp, bLongFormat, 0);
|
|
|
|
/* leave room for end of list marker */
|
|
if (bLongFormat)
|
|
iSpace += sizeof(uint16);
|
|
|
|
/* leave room for the termination character */
|
|
iSpace++;
|
|
|
|
#ifdef XP_WIN16
|
|
if (iSpace > 32000)
|
|
return NULL;
|
|
#endif
|
|
|
|
/* allocate the string */
|
|
pOriginal = pString = (char*)XP_ALLOC(iSpace * sizeof(char));
|
|
if (!pString)
|
|
return NULL;
|
|
|
|
/* Make a big string */
|
|
pString = bm_write(pString, tmp, bLongFormat, 0);
|
|
|
|
/* stick the end of list marker on so that when we are decoding this */
|
|
/* block we know when we are done */
|
|
if (bLongFormat)
|
|
{
|
|
marker = 12345;
|
|
XP_MEMCPY(pString, &marker, 2);
|
|
pString += sizeof(uint16);
|
|
}
|
|
|
|
/* end the string and return the total length to our caller */
|
|
*pString++ = '\0';
|
|
*lTotalLen = (pString - pOriginal);
|
|
return pOriginal;
|
|
}
|
|
|
|
|
|
/*
|
|
* Take a block of memory formatted by BM_ConvertSelectionsToBlock and insert
|
|
* the items it represents into the bookmarks following 'item'. If item is
|
|
* NULL insert at the beginning of the bookmarks.
|
|
*/
|
|
PUBLIC void
|
|
BM_InsertBlockAt(MWContext* context,
|
|
char* pOriginalBlock,
|
|
BM_Entry* addTo,
|
|
XP_Bool bLongFormat,
|
|
int32 lTotalLen)
|
|
{
|
|
BM_Type type;
|
|
int32 lBytesEaten = 0; /* total number of bytes eaten */
|
|
int32 lEat; /* number of bytes eaten on this item */
|
|
char* pCurrentPos;
|
|
char* pBlock;
|
|
BM_Entry* tmp;
|
|
BM_Entry* item;
|
|
XP_Bool first = TRUE;
|
|
|
|
CHKCONTEXTVOID(context);
|
|
if (!pOriginalBlock) return;
|
|
|
|
if (addTo == NULL) addTo = BM_GetRoot(context);
|
|
|
|
/* make a copy of the string we can write into */
|
|
pCurrentPos = pBlock = (char*) XP_ALLOC(lTotalLen + 1);
|
|
if (!pBlock) return;
|
|
|
|
bm_start_batch(context);
|
|
/* copy the data over and make sure we are NULL terminated to make life
|
|
easier */
|
|
XP_MEMCPY(pBlock, pOriginalBlock, lTotalLen);
|
|
pBlock[lTotalLen] = '\0';
|
|
|
|
/* long format can have all kinds of different types of things in it */
|
|
if (bLongFormat) {
|
|
while (lBytesEaten < lTotalLen) {
|
|
/* determine the type of the next entry */
|
|
XP_MEMCPY(&type, pCurrentPos, sizeof(BM_Type));
|
|
pCurrentPos += sizeof(BM_Type);
|
|
lBytesEaten += sizeof(BM_Type);
|
|
|
|
item = NULL;
|
|
|
|
switch (type) {
|
|
case BM_TYPE_URL:
|
|
item = bm_read_url_long(pCurrentPos, bLongFormat, &lEat);
|
|
lBytesEaten += lEat;
|
|
pCurrentPos += lEat;
|
|
break;
|
|
|
|
case BM_TYPE_ALIAS:
|
|
break;
|
|
|
|
case BM_TYPE_HEADER:
|
|
item = bm_read_header_long(context, pCurrentPos, bLongFormat, &lEat);
|
|
lBytesEaten += lEat;
|
|
pCurrentPos += lEat;
|
|
break;
|
|
|
|
case BM_TYPE_SEPARATOR:
|
|
item = bm_read_Separator(pCurrentPos, bLongFormat, &lEat);
|
|
lBytesEaten += lEat;
|
|
pCurrentPos += lEat;
|
|
break;
|
|
|
|
case 12345:
|
|
/* Ah ha! this is the end marker we wrote! remember... */
|
|
XP_ASSERT((lBytesEaten+1) == lTotalLen);
|
|
break;
|
|
default:
|
|
/* bogus type. Who knows whats going on. Just quit and
|
|
get out */
|
|
goto GETOUT;
|
|
|
|
break;
|
|
}
|
|
if (item) {
|
|
if (first && BM_ISHEADER(addTo) && !BM_ISFOLDED(addTo)) {
|
|
/* Adding inside a folder as first child. */
|
|
BM_PrependChildToHeader(context, addTo, item);
|
|
} else {
|
|
bm_InsertItemAfter(context, addTo, item, FALSE);
|
|
}
|
|
addTo = item;
|
|
first = FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
item = NULL;
|
|
/* short format is just a list of URLs separated by \n's */
|
|
while (lBytesEaten < lTotalLen) {
|
|
item = bm_read_url_long(pCurrentPos, bLongFormat, &lEat);
|
|
lBytesEaten += lEat;
|
|
pCurrentPos += lEat;
|
|
|
|
if (item) {
|
|
tmp = addTo->next;
|
|
addTo->next = item;
|
|
item->next = tmp;
|
|
addTo = item;
|
|
}
|
|
/* if we just walked over a \0 we are done */
|
|
if (pOriginalBlock[lBytesEaten - 1] == '\0') {
|
|
lBytesEaten = lTotalLen;
|
|
}
|
|
}
|
|
}
|
|
|
|
GETOUT:
|
|
/* mark the bookmark list as changed and clean up */
|
|
bm_SetModified(context, TRUE);
|
|
XP_FREE(pBlock);
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
bm_make_alias(MWContext* context, BM_Entry* entry, void* closure)
|
|
{
|
|
BM_Entry* newAlias;
|
|
int32 index;
|
|
|
|
CHKCONTEXTVOID(context);
|
|
|
|
if (BM_ISURL(entry)) {
|
|
newAlias = bm_NewAlias(entry);
|
|
if (!newAlias) return;
|
|
|
|
bm_InsertItemAfter(context, entry, newAlias, FALSE);
|
|
|
|
index = BM_GetIndex(context, newAlias);
|
|
if (index > 0) {
|
|
bm_refresh(context, index, BM_LAST_CELL);
|
|
}
|
|
}
|
|
}
|
|
|
|
PUBLIC void
|
|
BM_MakeAliases(MWContext* context)
|
|
{
|
|
CHKCONTEXTVOID(context);
|
|
bm_start_batch(context);
|
|
BM_EachSelectedEntryDo(context, bm_make_alias, NULL);
|
|
bm_SyncCount(context);
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
BM_Entry*
|
|
BM_GetAliasOriginal(BM_Entry* entry)
|
|
{
|
|
XP_ASSERT(BM_ISALIAS(entry));
|
|
return BM_ISALIAS(entry) ? entry->d.alias.original : NULL;
|
|
}
|
|
|
|
|
|
static XP_Bool
|
|
bm_SelectionIsContiguous_1(MWContext* context, BM_Entry* entry,
|
|
XP_Bool* started, XP_Bool* finished)
|
|
{
|
|
XP_Bool result;
|
|
for (; entry ; entry = entry->next) {
|
|
if (BM_ISSELECTED(entry)) {
|
|
if (*finished) return FALSE;
|
|
*started = TRUE;
|
|
} else {
|
|
if (*started) *finished = TRUE;
|
|
}
|
|
if (BM_ISHEADER(entry) && !BM_ISFOLDED(entry)) {
|
|
result = bm_SelectionIsContiguous_1(context, entry->d.header.children,
|
|
started, finished);
|
|
if (!result) return result;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static XP_Bool
|
|
bm_SelectionIsContiguous(MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
XP_Bool started = FALSE;
|
|
XP_Bool finished = FALSE;
|
|
if (f->gSelectionCount < 0) bm_SyncSelection(context);
|
|
if (f->gSelectionCount < 2) return TRUE;
|
|
return bm_SelectionIsContiguous_1(context, BM_GetRoot(context),
|
|
&started, &finished);
|
|
}
|
|
|
|
XP_Bool BM_FindCommandStatus(MWContext* context, BM_CommandType command)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
int32 length;
|
|
|
|
CHKCONTEXT(context);
|
|
|
|
if (f->gSelectionCount < 0) bm_SyncSelection(context);
|
|
|
|
switch (command) {
|
|
case BM_Cmd_Invalid:
|
|
return FALSE;
|
|
|
|
case BM_Cmd_Open: /**/
|
|
case BM_Cmd_ImportBookmarks:
|
|
case BM_Cmd_SaveAs: /**/
|
|
return TRUE;
|
|
|
|
case BM_Cmd_Close: /**/
|
|
return TRUE;
|
|
|
|
case BM_Cmd_Undo:
|
|
return UNDO_CanUndo(f->undo);
|
|
case BM_Cmd_Redo:
|
|
return UNDO_CanRedo(f->undo);
|
|
|
|
case BM_Cmd_Cut:
|
|
case BM_Cmd_Copy:
|
|
case BM_Cmd_Delete:
|
|
return (f->gSelectionCount > 0);
|
|
|
|
case BM_Cmd_Paste:
|
|
return BMFE_GetClipContents(context, &length) ? TRUE : FALSE;
|
|
break;
|
|
|
|
|
|
case BM_Cmd_SelectAllBookmarks: /**/
|
|
return TRUE;
|
|
|
|
case BM_Cmd_Find:
|
|
return TRUE;
|
|
|
|
case BM_Cmd_FindAgain:
|
|
return ((f->gFindInfo != NULL) && (f->gFindInfo->textToFind != NULL));
|
|
|
|
case BM_Cmd_BookmarkProps: /**/
|
|
return (f->gSelectionCount == 1 &&
|
|
!(f->gSelectionMask & BM_TYPE_SEPARATOR));
|
|
|
|
case BM_Cmd_Sort_Name:
|
|
case BM_Cmd_Sort_Name_Asc:
|
|
case BM_Cmd_Sort_Address:
|
|
case BM_Cmd_Sort_Address_Asc:
|
|
case BM_Cmd_Sort_AddDate:
|
|
case BM_Cmd_Sort_AddDate_Asc:
|
|
case BM_Cmd_Sort_LastVisit:
|
|
case BM_Cmd_Sort_LastVisit_Asc:
|
|
case BM_Cmd_Sort_Natural:
|
|
if (f->gSelectionCount == 0) return FALSE;
|
|
if (f->gSelectionCount == 1) {
|
|
return f->gSelectionMask == BM_TYPE_HEADER;
|
|
}
|
|
return bm_SelectionIsContiguous(context);
|
|
|
|
case BM_Cmd_InsertBookmark:
|
|
case BM_Cmd_InsertHeader:
|
|
return TRUE;
|
|
|
|
case BM_Cmd_InsertSeparator:
|
|
return context->type == MWContextBookmarks;
|
|
|
|
case BM_Cmd_GotoBookmark:
|
|
if (context->type == MWContextBookmarks) {
|
|
return (f->gSelectionCount == 1 &&
|
|
(f->gSelectionMask & (BM_TYPE_URL | BM_TYPE_ALIAS)));
|
|
} else {
|
|
return f->gSelectionCount > 0;
|
|
}
|
|
|
|
case BM_Cmd_MakeAlias:
|
|
return (f->gSelectionCount == 1 &&
|
|
(f->gSelectionMask & BM_TYPE_URL));
|
|
|
|
case BM_Cmd_SetAddHeader:
|
|
return (context->type == MWContextBookmarks &&
|
|
f->gSelectionCount == 1 &&
|
|
(f->gSelectionMask & BM_TYPE_HEADER) &&
|
|
BM_FirstSelectedItem(context) != f->addheader);
|
|
|
|
case BM_Cmd_SetMenuHeader:
|
|
return (context->type == MWContextBookmarks &&
|
|
f->gSelectionCount == 1 &&
|
|
(f->gSelectionMask & BM_TYPE_HEADER) &&
|
|
BM_FirstSelectedItem(context) != f->menuheader);
|
|
|
|
default:
|
|
XP_ASSERT(0);
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
bm_open_file(MWContext* context, char* newFile, void* closure)
|
|
{
|
|
if (newFile) {
|
|
#ifdef XP_WIN
|
|
BMFE_ChangingBookmarksFile();
|
|
#endif
|
|
|
|
BM_ReadBookmarksFromDisk(context, newFile, NULL);
|
|
|
|
#ifdef XP_WIN
|
|
BMFE_ChangedBookmarksFile();
|
|
#endif
|
|
|
|
XP_FREE(newFile);
|
|
}
|
|
}
|
|
|
|
|
|
/* LI_STUFF give li the ability to open a new bookmarks file */
|
|
void
|
|
BM_Open_File(MWContext* context, char* newFile)
|
|
{
|
|
bm_open_file(context, newFile, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
bm_import_file(MWContext* context, char* newFile, void* closure)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
bm_start_batch(context);
|
|
UNDO_DiscardAll(f->undo);
|
|
if (newFile) {
|
|
BM_Entry* oldroot = f->gBookmarks;
|
|
BM_Entry* oldmenuheader = f->menuheader;
|
|
BM_Entry* oldaddheader = f->addheader;
|
|
BM_Entry* newroot;
|
|
BM_Entry* entry;
|
|
BM_Entry* next;
|
|
char* oldfile = NULL;
|
|
XP_StatStruct savedStat;
|
|
|
|
if (f->gFile) {
|
|
oldfile = XP_STRDUP(f->gFile);
|
|
if (!oldfile) return; /* Out of memory... */
|
|
}
|
|
f->gBookmarks = NULL;
|
|
f->gBookmarksModified = FALSE; /* Don't save now. */
|
|
|
|
/* save real stat 'cause it's about to get busted */
|
|
XP_MEMCPY(&savedStat, &(f->laststat), sizeof(savedStat));
|
|
|
|
BM_ReadBookmarksFromDisk(context, newFile, NULL);
|
|
|
|
/* restore real stat */
|
|
XP_MEMCPY(&(f->laststat), &savedStat, sizeof(savedStat));
|
|
|
|
newroot = f->gBookmarks;
|
|
if (newroot && oldroot) {
|
|
/* Make the new stuff be the first folder of the old stuff. */
|
|
f->gBookmarks = oldroot;
|
|
if (context->type == MWContextAddressBook) {
|
|
XP_ASSERT(BM_ISHEADER(newroot));
|
|
if (BM_ISHEADER(newroot)) {
|
|
for (entry = newroot->d.header.children;
|
|
entry;
|
|
entry = next) {
|
|
next = entry->next;
|
|
entry->next = NULL;
|
|
bm_AddChildToHeaderSorted(context, oldroot, entry);
|
|
}
|
|
}
|
|
} else {
|
|
BM_PrependChildToHeader(context, oldroot, newroot);
|
|
}
|
|
f->menuheader = oldmenuheader;
|
|
f->addheader = oldaddheader;
|
|
} else {
|
|
f->gBookmarks = oldroot ? oldroot : newroot;
|
|
}
|
|
FREEIF(f->gFile);
|
|
f->gFile = oldfile;
|
|
oldfile = NULL;
|
|
bm_SetModified(context, TRUE);
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
XP_FREE(newFile);
|
|
}
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
static void
|
|
bm_save_as_file(MWContext* context, char* saveName, void* closure)
|
|
{
|
|
if (saveName) {
|
|
BM_SaveBookmarks(context, saveName);
|
|
XP_FREE(saveName);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
bm_comparenames(const void* e1, const void* e2)
|
|
{
|
|
#ifdef INTL_SORT
|
|
return XP_StrColl((*((BM_Entry**)e1))->name, (*((BM_Entry**)e2))->name);
|
|
#else
|
|
return XP_STRCMP((*((BM_Entry**)e1))->name, (*((BM_Entry**)e2))->name);
|
|
#endif
|
|
}
|
|
static int
|
|
bm_comparenames_Asc(const void* e1, const void* e2)
|
|
{
|
|
#ifdef INTL_SORT
|
|
return XP_StrColl((*((BM_Entry**)e2))->name, (*((BM_Entry**)e1))->name);
|
|
#else
|
|
return XP_STRCMP((*((BM_Entry**)e2))->name, (*((BM_Entry**)e1))->name);
|
|
#endif
|
|
}
|
|
|
|
static int bm_compare_address( const void *elem1, const void *elem2 )
|
|
{
|
|
BM_Entry *p1 = *(BM_Entry **)elem1;
|
|
BM_Entry *p2 = *(BM_Entry **)elem2;
|
|
|
|
if( !(p1->type == BM_TYPE_URL ||
|
|
p1->type == BM_TYPE_ADDRESS) )
|
|
{
|
|
if( !(p2->type == BM_TYPE_URL ||
|
|
p2->type == BM_TYPE_ADDRESS) )
|
|
{
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
else if( !(p2->type == BM_TYPE_URL ||
|
|
p2->type == BM_TYPE_ADDRESS) )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/* Note we rely on d.url.address is at same mem address as d.address.address */
|
|
|
|
#ifdef INTL_SORT
|
|
return XP_StrColl( p1->d.url.address, p2->d.url.address );
|
|
#else
|
|
return XP_STRCMP( p1->d.url.address, p2->d.url.address );
|
|
#endif
|
|
}
|
|
static int bm_compare_address_Asc( const void *elem1, const void *elem2 )
|
|
{
|
|
BM_Entry *p2 = *(BM_Entry **)elem1;
|
|
BM_Entry *p1 = *(BM_Entry **)elem2;
|
|
|
|
if( !(p1->type == BM_TYPE_URL ||
|
|
p1->type == BM_TYPE_ADDRESS) )
|
|
{
|
|
if( !(p2->type == BM_TYPE_URL ||
|
|
p2->type == BM_TYPE_ADDRESS) )
|
|
{
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
else if( !(p2->type == BM_TYPE_URL ||
|
|
p2->type == BM_TYPE_ADDRESS) )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/* Note we rely on d.url.address is at same mem address as d.address.address */
|
|
|
|
#ifdef INTL_SORT
|
|
return XP_StrColl( p1->d.url.address, p2->d.url.address );
|
|
#else
|
|
return XP_STRCMP( p1->d.url.address, p2->d.url.address );
|
|
#endif
|
|
}
|
|
|
|
#ifdef SUNOS4
|
|
/* difftime() doesn't seem to exist on SunOS anywhere. -mcafee */
|
|
static double difftime(time_t time1, time_t time0)
|
|
{
|
|
return (double) (time1 - time0);
|
|
}
|
|
#endif
|
|
|
|
static int bm_compare_time (time_t time1, time_t time0)
|
|
{
|
|
double diff = difftime( time1, time0 );
|
|
|
|
if (diff > 0.0) return 1;
|
|
if (diff < 0.0) return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bm_compare_addition_date( const void *elem1, const void *elem2 )
|
|
{
|
|
BM_Entry *p1 = *(BM_Entry **)elem1;
|
|
BM_Entry *p2 = *(BM_Entry **)elem2;
|
|
|
|
return bm_compare_time( p2->addition_date, p1->addition_date );
|
|
}
|
|
static int bm_compare_addition_date_Asc( const void *elem1, const void *elem2 )
|
|
{
|
|
BM_Entry *p2 = *(BM_Entry **)elem1;
|
|
BM_Entry *p1 = *(BM_Entry **)elem2;
|
|
|
|
return bm_compare_time( p2->addition_date, p1->addition_date );
|
|
}
|
|
|
|
static int bm_compare_natural( const void *elem1, const void *elem2 )
|
|
{
|
|
BM_Entry *p2 = *(BM_Entry **)elem1;
|
|
BM_Entry *p1 = *(BM_Entry **)elem2;
|
|
|
|
return (p2->iNaturalIndex >= p1->iNaturalIndex) ? 1 : -1;
|
|
}
|
|
|
|
static int bm_compare_last_visit( const void *elem1, const void *elem2 )
|
|
{
|
|
BM_Entry *p1 = *(BM_Entry **)elem1;
|
|
BM_Entry *p2 = *(BM_Entry **)elem2;
|
|
if( p2->type != BM_TYPE_URL )
|
|
{
|
|
if( p1->type != BM_TYPE_URL )
|
|
{
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
else if( p1->type != BM_TYPE_URL )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return bm_compare_time( p2->d.url.last_visit, p1->d.url.last_visit );
|
|
}
|
|
static int bm_compare_last_visit_Asc( const void *elem1, const void *elem2 )
|
|
{
|
|
BM_Entry *p2 = *(BM_Entry **)elem1;
|
|
BM_Entry *p1 = *(BM_Entry **)elem2;
|
|
if( p2->type != BM_TYPE_URL )
|
|
{
|
|
if( p1->type != BM_TYPE_URL )
|
|
{
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
else if( p1->type != BM_TYPE_URL )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return bm_compare_time( p2->d.url.last_visit, p1->d.url.last_visit );
|
|
}
|
|
|
|
static void
|
|
bm_SortSelected_1(MWContext* context, BM_Entry* header, BM_SortType enSortType )
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry** list = NULL;
|
|
int numlist;
|
|
BM_Entry* entry;
|
|
BM_Entry* previous = NULL;
|
|
int i;
|
|
#ifdef XP_WIN
|
|
int (__cdecl *pfSort)(const void *, const void *);
|
|
#else
|
|
int (*pfSort)(const void *, const void *);
|
|
#endif
|
|
|
|
XP_ASSERT(BM_ISHEADER(header));
|
|
|
|
switch( enSortType )
|
|
{
|
|
case BM_Sort_Name:
|
|
pfSort = bm_comparenames;
|
|
break;
|
|
case BM_Sort_Name_Asc:
|
|
pfSort = bm_comparenames_Asc;
|
|
break;
|
|
|
|
case BM_Sort_Address:
|
|
pfSort = bm_compare_address;
|
|
break;
|
|
case BM_Sort_Address_Asc:
|
|
pfSort = bm_compare_address_Asc;
|
|
break;
|
|
|
|
case BM_Sort_AddDate:
|
|
pfSort = bm_compare_addition_date;
|
|
break;
|
|
case BM_Sort_AddDate_Asc:
|
|
pfSort = bm_compare_addition_date_Asc;
|
|
break;
|
|
|
|
case BM_Sort_LastVisit:
|
|
pfSort = bm_compare_last_visit;
|
|
break;
|
|
case BM_Sort_LastVisit_Asc:
|
|
pfSort = bm_compare_last_visit_Asc;
|
|
break;
|
|
|
|
case BM_Sort_Natural:
|
|
default:
|
|
pfSort = bm_compare_natural;
|
|
break;
|
|
}
|
|
|
|
if (header->d.header.childCount == 0) return;
|
|
if (BM_ISSELECTED(header) && f->gSelectionCount == 1) {
|
|
numlist = header->d.header.childCount;
|
|
list = (BM_Entry**) XP_ALLOC(numlist * sizeof(BM_Entry*));
|
|
if (!list) return;
|
|
for (i = 0, entry = header->d.header.children;
|
|
i < numlist;
|
|
i++, entry = entry->next) {
|
|
list[i] = entry;
|
|
}
|
|
} else {
|
|
i = 0;
|
|
for (entry = header->d.header.children ; entry ; entry = entry->next) {
|
|
if (BM_ISSELECTED(entry)) {
|
|
if (list == NULL) {
|
|
list = (BM_Entry**) XP_ALLOC(header->d.header.childCount *
|
|
sizeof(BM_Entry*));
|
|
if (list == NULL) return;
|
|
}
|
|
list[i++] = entry;
|
|
} else {
|
|
if (list == NULL) previous = entry;
|
|
}
|
|
}
|
|
numlist = i;
|
|
}
|
|
if (list) {
|
|
if (numlist > 1) {
|
|
for (i=0 ; i<numlist ; i++) {
|
|
if (list[i]->name == NULL) {
|
|
list[i]->name = XP_STRDUP("");
|
|
if (list[i]->name == NULL) return;
|
|
}
|
|
BM_RemoveChildFromHeader(context, header, list[i]);
|
|
}
|
|
XP_QSORT(list, numlist, sizeof(BM_Entry*), pfSort);
|
|
for (i=0 ; i<numlist ; i++) {
|
|
if (previous) {
|
|
bm_InsertItemAfter(context, previous, list[i], FALSE);
|
|
} else {
|
|
BM_PrependChildToHeader(context, header, list[i]);
|
|
}
|
|
previous = list[i];
|
|
}
|
|
}
|
|
XP_FREE(list);
|
|
list = NULL;
|
|
}
|
|
for (entry = header->d.header.children ; entry ; entry = entry->next) {
|
|
if (BM_ISHEADER(entry)) bm_SortSelected_1(context, entry, enSortType );
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_SortSilent_1(MWContext* context, BM_Entry* header, BM_SortType enSortType )
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry** list = NULL;
|
|
int numlist;
|
|
BM_Entry* entry;
|
|
BM_Entry* previous = NULL;
|
|
int i;
|
|
XP_Bool bSelected = FALSE;
|
|
#ifdef XP_WIN
|
|
int (__cdecl *pfSort)(const void *, const void *);
|
|
#else
|
|
int (*pfSort)(const void *, const void *);
|
|
#endif
|
|
|
|
XP_ASSERT(BM_ISHEADER(header));
|
|
|
|
switch( enSortType )
|
|
{
|
|
case BM_Sort_Name:
|
|
pfSort = bm_comparenames;
|
|
break;
|
|
case BM_Sort_Name_Asc:
|
|
pfSort = bm_comparenames_Asc;
|
|
break;
|
|
|
|
case BM_Sort_Address:
|
|
pfSort = bm_compare_address;
|
|
break;
|
|
case BM_Sort_Address_Asc:
|
|
pfSort = bm_compare_address_Asc;
|
|
break;
|
|
|
|
case BM_Sort_AddDate:
|
|
pfSort = bm_compare_addition_date;
|
|
break;
|
|
case BM_Sort_AddDate_Asc:
|
|
pfSort = bm_compare_addition_date_Asc;
|
|
break;
|
|
|
|
case BM_Sort_LastVisit:
|
|
pfSort = bm_compare_last_visit;
|
|
break;
|
|
case BM_Sort_LastVisit_Asc:
|
|
pfSort = bm_compare_last_visit_Asc;
|
|
break;
|
|
|
|
case BM_Sort_Natural:
|
|
default:
|
|
pfSort = bm_compare_natural;
|
|
break;
|
|
}
|
|
|
|
if (header->d.header.childCount == 0) return;
|
|
numlist = header->d.header.childCount;
|
|
list = (BM_Entry**) XP_ALLOC(numlist * sizeof(BM_Entry*));
|
|
if (!list) return;
|
|
for (i = 0, entry = header->d.header.children;
|
|
i < numlist;
|
|
i++, entry = entry->next) {
|
|
list[i] = entry;
|
|
}
|
|
if (numlist > 1) {
|
|
for (i=0 ; i<numlist ; i++) {
|
|
if (list[i]->name == NULL) {
|
|
list[i]->name = XP_STRDUP("");
|
|
if (list[i]->name == NULL) return;
|
|
}
|
|
if (BM_ISSELECTED(list[i])) {
|
|
BM_CLEARFLAG(list[i], BM_ATTR_SELECTED);
|
|
bSelected = TRUE;
|
|
}
|
|
BM_RemoveChildFromHeader(context, header, list[i]);
|
|
if (bSelected) {
|
|
BM_SETFLAG(list[i], BM_ATTR_SELECTED);
|
|
bSelected = FALSE;
|
|
}
|
|
}
|
|
XP_QSORT(list, numlist, sizeof(BM_Entry*), pfSort);
|
|
for (i=0 ; i<numlist ; i++) {
|
|
if (previous) {
|
|
bm_InsertItemAfter(context, previous, list[i], FALSE);
|
|
} else {
|
|
BM_PrependChildToHeader(context, header, list[i]);
|
|
}
|
|
previous = list[i];
|
|
}
|
|
}
|
|
XP_FREE(list);
|
|
list = NULL;
|
|
for (entry = header->d.header.children ; entry ; entry = entry->next) {
|
|
if (BM_ISHEADER(entry)) bm_SortSilent_1(context, entry, enSortType );
|
|
}
|
|
}
|
|
|
|
/*
|
|
// Normalize bookmarks based on the current sort. Note the current sort
|
|
// should be the natural sort order (aka BM_Sort_Natural) as no other
|
|
// sort order has a mapping to the natural index.
|
|
*/
|
|
static void
|
|
bm_Normalize(MWContext* context, BM_Entry* at)
|
|
{
|
|
BM_Entry* nextChild;
|
|
BM_Entry* children;
|
|
|
|
int32 iNaturalIndex = 0;
|
|
|
|
while (at) {
|
|
nextChild = at->next;
|
|
if (BM_ISHEADER(at)) {
|
|
children = at->d.header.children;
|
|
} else {
|
|
children = NULL;
|
|
}
|
|
|
|
at->iNaturalIndex = iNaturalIndex++;
|
|
|
|
if (children) {
|
|
bm_Normalize(context, children);
|
|
}
|
|
|
|
at = nextChild;
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_SortSelected(MWContext* context, BM_SortType enSortType )
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
if (f->gSelectionCount < 0) bm_SyncSelection(context);
|
|
if (f->enSortType == BM_Sort_Natural) bm_Normalize(context, BM_GetRoot(context));
|
|
f->enSortType = enSortType;
|
|
f->bSorting = TRUE;
|
|
bm_SortSelected_1(context, BM_GetRoot(context), enSortType);
|
|
f->bSorting = FALSE;
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
}
|
|
|
|
static void
|
|
bm_SortSilent(MWContext* context, BM_SortType enSortType )
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
if (f->gSelectionCount < 0) bm_SyncSelection(context);
|
|
if (f->enSortType == BM_Sort_Natural) bm_Normalize(context, BM_GetRoot(context));
|
|
f->enSortType = enSortType;
|
|
f->bSorting = TRUE;
|
|
bm_SortSilent_1(context, BM_GetRoot(context), enSortType);
|
|
f->bSorting = FALSE;
|
|
}
|
|
|
|
static void bm_append_address_string(MWContext* context, BM_Entry* entry,
|
|
void* closure);
|
|
|
|
static void
|
|
bm_append_fulladdress_string(MWContext* context, BM_Entry* entry,
|
|
void* closure)
|
|
{
|
|
if (BM_ISALIAS(entry)) entry = entry->d.alias.original;
|
|
if (entry->flags & BM_ATTR_MARKED) return;
|
|
if (BM_ISHEADER(entry)) {
|
|
BM_SETFLAG(entry, BM_ATTR_MARKED);
|
|
for (entry = entry->d.header.children ; entry ; entry = entry->next) {
|
|
bm_append_fulladdress_string(context, entry, closure);
|
|
}
|
|
} else if (BM_ISADDRESS(entry)) {
|
|
bm_append_address_string(context, entry, closure);
|
|
XP_ASSERT(entry->flags & BM_ATTR_MARKED);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bm_append_address_string(MWContext* context, BM_Entry* entry, void* closure)
|
|
{
|
|
if (BM_ISALIAS(entry)) entry = entry->d.alias.original;
|
|
if (entry->flags & BM_ATTR_MARKED) return;
|
|
if (BM_ISADDRESS(entry) || BM_ISHEADER(entry)) {
|
|
char* address =
|
|
BM_ISHEADER(entry) ? BM_GetNickName(entry) : BM_GetAddress(entry);
|
|
if (BM_ISHEADER(entry) && (address == NULL || *address == '\0')) {
|
|
/* No nickname for a header, so we don't have anything to write down
|
|
that we can remember later. Just write down all the members of
|
|
this list. */
|
|
bm_append_fulladdress_string(context, entry, closure);
|
|
} else {
|
|
#ifdef MOZ_MAIL_NEWS
|
|
char** buf = (char**) closure;
|
|
char* full = MSG_MakeFullAddress(BM_GetName(entry), address);
|
|
if (full) {
|
|
if (*buf) NET_SACat(buf, ", ");
|
|
NET_SACat(buf, full);
|
|
XP_FREE(full);
|
|
}
|
|
#endif /* MOZ_MAIL_NEWS */
|
|
}
|
|
}
|
|
BM_SETFLAG(entry, BM_ATTR_MARKED);
|
|
}
|
|
|
|
char*
|
|
BM_GetFullAddress(MWContext* context, BM_Entry* entry)
|
|
{
|
|
char* result = NULL;
|
|
bm_append_address_string(context, entry, &result);
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
bm_ComposeMessageToSelected(MWContext* context)
|
|
{
|
|
char* buf = NULL;
|
|
char* tmp;
|
|
URL_Struct *url_struct;
|
|
bm_ClearMarkEverywhere(context);
|
|
BM_EachSelectedEntryDo(context, bm_append_address_string, &buf);
|
|
if (!buf) return;
|
|
tmp = NET_Escape(buf, URL_PATH);
|
|
XP_FREE(buf);
|
|
buf = tmp;
|
|
if (!buf) return;
|
|
tmp = XP_Cat("mailto:?to=", buf, (char*)/*Win16*/ NULL);
|
|
XP_FREE(buf);
|
|
buf = tmp;
|
|
if (!buf) return;
|
|
url_struct = NET_CreateURLStruct (buf, NET_NORMAL_RELOAD);
|
|
if (url_struct) {
|
|
url_struct->internal_url = TRUE;
|
|
FE_GetURL(context, url_struct);
|
|
}
|
|
XP_FREE(buf);
|
|
}
|
|
|
|
|
|
char*
|
|
BM_ExpandHeaderString(MWContext* context, const char* value,
|
|
XP_Bool expandfull)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
char* name;
|
|
char* address;
|
|
char* curname;
|
|
char* curaddress;
|
|
char* pHashStr;
|
|
int num;
|
|
int i,j;
|
|
XP_Bool found = FALSE;
|
|
BM_Entry* entry;
|
|
char* result = NULL;
|
|
char* pTempStr = NULL;
|
|
int tempBufLen = 0;
|
|
CHKCONTEXT(context);
|
|
#ifdef MOZ_MAIL_NEWS
|
|
num = MSG_ParseRFC822Addresses(value, &name, &address);
|
|
#else
|
|
num = 0;
|
|
#endif /* MOZ_MAIL_NEWS */
|
|
curname = name;
|
|
curaddress = address;
|
|
bm_ClearMarkEverywhere(context);
|
|
for (i=0 ; i<num ; i++) {
|
|
pHashStr = NULL;
|
|
if (XP_STRCHR(curaddress, '@') == NULL) {
|
|
/* to make nicknames case-insensitive, we have to
|
|
** change the string here to be all lowercase before
|
|
** passing it to the hash lookup function */
|
|
|
|
/* first make sure the temporary buffer we have
|
|
** is long enough for this string. If not, make
|
|
** a new one that is long enough. */
|
|
int curlen;
|
|
curlen = XP_STRLEN(curaddress);
|
|
if (!pTempStr || (curlen > tempBufLen)) {
|
|
FREEIF(pTempStr);
|
|
pTempStr = XP_STRDUP(curaddress);
|
|
tempBufLen = curlen;
|
|
} else {
|
|
/* just copy the string into the existing buffer */
|
|
XP_STRCPY(pTempStr, curaddress);
|
|
}
|
|
if (pTempStr) {
|
|
/* now the buffer is loaded with the string, change the string to lowercase */
|
|
for (j = 0; j < curlen; j++) {
|
|
if (isupper(pTempStr[j])) {
|
|
pTempStr[j] = (char)tolower(pTempStr[j]);
|
|
}
|
|
}
|
|
pHashStr = pTempStr; /* use the temp str for the hash function */
|
|
} else {
|
|
pHashStr = curaddress; /* use the old string if low on memory */
|
|
}
|
|
}
|
|
if (pHashStr && (entry = XP_Gethash(f->nicknameTable, pHashStr, NULL)) != NULL) {
|
|
found = TRUE;
|
|
if (expandfull) {
|
|
bm_append_fulladdress_string(context, entry, &result);
|
|
} else {
|
|
bm_append_address_string(context, entry, &result);
|
|
}
|
|
} else {
|
|
if (result) NET_SACat(&result, ", ");
|
|
if (*curname) {
|
|
NET_SACat(&result, curname);
|
|
NET_SACat(&result, " <");
|
|
}
|
|
NET_SACat(&result, curaddress);
|
|
if (*curname) {
|
|
NET_SACat(&result, ">");
|
|
}
|
|
}
|
|
curname += XP_STRLEN(curname) + 1;
|
|
curaddress += XP_STRLEN(curaddress) + 1;
|
|
}
|
|
FREEIF(name);
|
|
FREEIF(address);
|
|
FREEIF(pTempStr);
|
|
if (!found) {
|
|
FREEIF(result); /* Note this sets also result to NULL. */
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void BM_ObeyCommand(MWContext* context, BM_CommandType command)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
BM_Entry* firstSelected;
|
|
|
|
CHKCONTEXTVOID(context);
|
|
|
|
if (!BM_FindCommandStatus(context, command)) return;
|
|
|
|
firstSelected = BM_FirstSelectedItem(context);
|
|
|
|
bm_start_batch(context);
|
|
|
|
switch (command) {
|
|
case BM_Cmd_Invalid:
|
|
break;
|
|
|
|
case BM_Cmd_Open:
|
|
FE_PromptForFileName(context, XP_GetString(XP_BKMKS_OPEN_BKMKS_FILE),
|
|
0, TRUE, FALSE, bm_open_file, NULL);
|
|
break;
|
|
|
|
case BM_Cmd_ImportBookmarks:
|
|
FE_PromptForFileName(context,
|
|
XP_GetString(context->type == MWContextAddressBook ?
|
|
XP_BKMKS_IMPORT_ADDRBOOK : XP_BKMKS_IMPORT_BKMKS_FILE),
|
|
0, TRUE, FALSE, bm_import_file, NULL);
|
|
break;
|
|
|
|
case BM_Cmd_SaveAs:
|
|
FE_PromptForFileName(context,
|
|
XP_GetString(context->type == MWContextAddressBook ?
|
|
XP_BKMKS_SAVE_ADDRBOOK : XP_BKMKS_SAVE_BKMKS_FILE),
|
|
0, FALSE, FALSE, bm_save_as_file, NULL);
|
|
break;
|
|
|
|
case BM_Cmd_Close:
|
|
BM_SaveBookmarks(context, f->gFile);
|
|
/* ### Maybe need to do more? */
|
|
break;
|
|
|
|
case BM_Cmd_Undo:
|
|
UNDO_EndBatch(f->undo, NULL, NULL);
|
|
UNDO_DoUndo(f->undo);
|
|
UNDO_StartBatch(f->undo);
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
bm_SyncCount(context);
|
|
break;
|
|
|
|
case BM_Cmd_Redo:
|
|
UNDO_EndBatch(f->undo, NULL, NULL);
|
|
UNDO_DoRedo(f->undo);
|
|
UNDO_StartBatch(f->undo);
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
bm_SyncCount(context);
|
|
break;
|
|
|
|
case BM_Cmd_Cut:
|
|
bm_cut(context);
|
|
break;
|
|
|
|
case BM_Cmd_Copy:
|
|
bm_copy(context);
|
|
break;
|
|
|
|
case BM_Cmd_Paste:
|
|
bm_paste(context);
|
|
break;
|
|
|
|
case BM_Cmd_Delete:
|
|
bm_delete(context);
|
|
break;
|
|
|
|
case BM_Cmd_SelectAllBookmarks:
|
|
BM_SelectAll(context, TRUE);
|
|
break;
|
|
|
|
case BM_Cmd_Find:
|
|
bm_CloseLastFind(context);
|
|
bm_BeginFindBookmark(context);
|
|
break;
|
|
|
|
case BM_Cmd_FindAgain:
|
|
bm_CloseLastFind(context);
|
|
BM_DoFindBookmark(context, f->gFindInfo);
|
|
break;
|
|
|
|
case BM_Cmd_BookmarkProps:
|
|
if (firstSelected) {
|
|
BMFE_OpenBookmarksWindow(context);
|
|
BMFE_EditItem(context, firstSelected);
|
|
}
|
|
break;
|
|
|
|
case BM_Cmd_GotoBookmark:
|
|
if (context->type == MWContextAddressBook) {
|
|
bm_ComposeMessageToSelected(context);
|
|
} else if (firstSelected) {
|
|
BM_GotoBookmark(context, firstSelected);
|
|
}
|
|
break;
|
|
|
|
case BM_Cmd_Sort_Name:
|
|
case BM_Cmd_Sort_Name_Asc:
|
|
case BM_Cmd_Sort_Address:
|
|
case BM_Cmd_Sort_Address_Asc:
|
|
case BM_Cmd_Sort_AddDate:
|
|
case BM_Cmd_Sort_AddDate_Asc:
|
|
case BM_Cmd_Sort_LastVisit:
|
|
case BM_Cmd_Sort_LastVisit_Asc:
|
|
case BM_Cmd_Sort_Natural:
|
|
bm_SortSelected( context, (BM_SortType)(command-BM_Cmd_Sort_Name) );
|
|
break;
|
|
|
|
case BM_Cmd_InsertBookmark:
|
|
bm_BeginEditNewUrl(context);
|
|
break;
|
|
|
|
case BM_Cmd_InsertHeader:
|
|
bm_BeginEditNewHeader(context);
|
|
break;
|
|
|
|
case BM_Cmd_InsertSeparator:
|
|
if (firstSelected) {
|
|
bm_InsertItemAfter(context, firstSelected, bm_NewSeparator(), TRUE);
|
|
bm_refresh(context, BM_GetIndex(context, firstSelected) + 1,
|
|
BM_LAST_CELL);
|
|
}
|
|
break;
|
|
|
|
case BM_Cmd_MakeAlias:
|
|
BM_MakeAliases(context);
|
|
break;
|
|
|
|
case BM_Cmd_SetAddHeader:
|
|
if (firstSelected) {
|
|
BM_SetAddHeader(context, firstSelected);
|
|
}
|
|
break;
|
|
|
|
case BM_Cmd_SetMenuHeader:
|
|
if (firstSelected) {
|
|
BM_SetMenuHeader(context, firstSelected);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
XP_ASSERT(0);
|
|
|
|
}
|
|
bm_end_batch(context);
|
|
}
|
|
|
|
|
|
|
|
/* Make sure that the given entry is a real entry, and not a pointer that has
|
|
since become invalid. */
|
|
|
|
static XP_Bool
|
|
bm_validate_entry(MWContext* context, BM_Entry* entry, BM_Entry* search)
|
|
{
|
|
for (; entry ; entry = entry->next) {
|
|
if (entry == search) return TRUE;
|
|
if (BM_ISHEADER(entry)) {
|
|
if (bm_validate_entry(context, entry->d.header.children, search)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
bm_urlcheck_finished(URL_Struct* url_struct, int status, MWContext* context)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
time_t now;
|
|
char timestr[40];
|
|
if (f) {
|
|
struct BM_WhatsChangedInfo* w = (struct BM_WhatsChangedInfo *)&(f->whatschanged);
|
|
BM_Entry* entry = (BM_Entry*) url_struct->fe_data;
|
|
if (bm_validate_entry(context, BM_GetRoot(context), entry)) {
|
|
const char* url = BM_GetAddress(entry);
|
|
int32 oldstate = BM_GetChangedState(entry);
|
|
BM_CLEARFLAG(entry, BM_ATTR_CHECKING);
|
|
if (status >= 0) {
|
|
if (url && XP_STRCMP(url_struct->address, url) == 0) {
|
|
w->numreached++;
|
|
entry->d.url.last_modified = url_struct->last_modified;
|
|
if (entry->d.url.last_modified > entry->d.url.last_visit) {
|
|
w->numchanged++;
|
|
}
|
|
}
|
|
} else {
|
|
entry->d.url.last_modified = 0;
|
|
}
|
|
if (BM_GetChangedState(entry) != oldstate) {
|
|
bm_entry_changed(context, entry);
|
|
bm_SetModified(context, TRUE);
|
|
}
|
|
|
|
now = time ((time_t *) 0);
|
|
|
|
if (w->numreached == 0) {
|
|
XP_STRCPY(timestr, "???");
|
|
} else {
|
|
int32 estimate = (now - w->starttime) * (w->total - w->numreached) /
|
|
w->numreached;
|
|
if (estimate < 2 * 60) {
|
|
PR_snprintf(timestr, sizeof(timestr), XP_GetString(XP_BKMKS_SECONDS),
|
|
estimate);
|
|
} else if (estimate < 2 * 60 * 60) {
|
|
PR_snprintf(timestr, sizeof(timestr), XP_GetString(XP_BKMKS_MINUTES),
|
|
estimate / 60);
|
|
} else {
|
|
PR_snprintf(timestr, sizeof(timestr), XP_GetString(XP_BKMKS_HOURS_MINUTES),
|
|
estimate / 3600, (estimate / 60) % 60);
|
|
}
|
|
}
|
|
BMFE_UpdateWhatsChanged(context, url, w->numreached, w->total,
|
|
timestr);
|
|
}
|
|
|
|
/* Check to see if we're all done. First check to see if we're in the
|
|
middle of a batch operation; if we are, then we must be still setting
|
|
things up and we got called here because we had an invalid bookmark
|
|
and netlib called the exit routine immediately. In that case, we
|
|
don't want to say we're all done; we're probably still sending
|
|
URLs to netlib.
|
|
|
|
If we're not in the middle of a batch operation, then we're all done
|
|
if there are no more outstanding connections on our context. */
|
|
if (f->batch_depth == 0 &&
|
|
!NET_AreThereActiveConnectionsForWindow(context)) {
|
|
BMFE_FinishedWhatsChanged(context, w->total, w->numreached,
|
|
w->numchanged);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef XP_WIN16
|
|
/* code segment is full, switch to a new segment */
|
|
#pragma code_seg("BKMKS2_TEXT","CODE")
|
|
#endif
|
|
|
|
|
|
|
|
static void
|
|
bm_urlcheck_start(MWContext* context, BM_Entry* entry)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
char* url;
|
|
URL_Struct* url_struct;
|
|
|
|
XP_ASSERT(entry);
|
|
|
|
if (BM_ISALIAS(entry)) {
|
|
entry = entry->d.alias.original;
|
|
}
|
|
if (!entry) return;
|
|
|
|
if (entry->flags & BM_ATTR_CHECKING) return;
|
|
|
|
url = BM_GetAddress(entry);
|
|
if (!url) return;
|
|
url_struct = NET_CreateURLStruct(url, NET_SUPER_RELOAD);
|
|
if (!url_struct) return;
|
|
BM_SETFLAG(entry, BM_ATTR_CHECKING);
|
|
url_struct->method = URL_HEAD_METHOD;
|
|
url_struct->fe_data = entry;
|
|
f->whatschanged.total++;
|
|
NET_GetURL(url_struct, FO_PRESENT, context, bm_urlcheck_finished);
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
bm_start_whats_changed_1(MWContext* context, BM_Entry* entry,
|
|
XP_Bool do_only_selected)
|
|
{
|
|
for ( ; entry ; entry = entry->next) {
|
|
if (BM_ISURL(entry) || BM_ISALIAS(entry)) {
|
|
if (!do_only_selected || BM_ISSELECTED(entry)) {
|
|
bm_urlcheck_start(context, entry);
|
|
}
|
|
} else if (BM_ISHEADER(entry)) {
|
|
/* Recur through the children. If we are selected and folded, then
|
|
make sure we do all of our descendents. */
|
|
bm_start_whats_changed_1(context, entry->d.header.children,
|
|
(BM_ISSELECTED(entry) && BM_ISFOLDED(entry)) ?
|
|
FALSE : do_only_selected);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
bm_clear_check_attr(MWContext* context, BM_Entry* entry, void* closure)
|
|
{
|
|
BM_CLEARFLAG(entry, BM_ATTR_CHECKING);
|
|
}
|
|
|
|
|
|
int
|
|
BM_StartWhatsChanged(MWContext* context, XP_Bool do_only_selected)
|
|
{
|
|
BM_Frame* f = GETFRAME(context);
|
|
struct BM_WhatsChangedInfo* w;
|
|
XP_ASSERT(context && context->type == MWContextBookmarks && f);
|
|
if (!context || context->type != MWContextBookmarks || !f) return -1;
|
|
w = &(f->whatschanged);
|
|
BM_CancelWhatsChanged(context);
|
|
XP_MEMSET(w, 0, sizeof(*w));
|
|
w->starttime = time ((time_t *) 0);
|
|
|
|
BM_EachEntryDo(context, bm_clear_check_attr, NULL);
|
|
|
|
bm_start_batch(context);
|
|
bm_start_whats_changed_1(context, BM_GetRoot(context), do_only_selected);
|
|
|
|
#if 0
|
|
minutes = f->whatschanged.total * 35 / 60;
|
|
/* Assumes a maximum timeout of 35 seconds per
|
|
connection. Need to not hard-code
|
|
this... #### */
|
|
if (minutes < 60) {
|
|
/* Fix i18n ### */
|
|
PR_snprintf(w->totaltime, sizeof(w->totaltime), "%ld minutes", minutes);
|
|
} else {
|
|
/* Fix i18n ### */
|
|
PR_snprintf(w->totaltime, sizeof(w->totaltime), "%ld hours",
|
|
(minutes / 60) + 1);
|
|
}
|
|
#endif
|
|
|
|
BMFE_UpdateWhatsChanged(context, NULL, w->numreached, w->total,
|
|
"???"); /* Fix i18n ### */
|
|
bm_refresh(context, 1, BM_LAST_CELL);
|
|
bm_end_batch(context);
|
|
if (!NET_AreThereActiveConnectionsForWindow(context)) {
|
|
/* All done, already (probably because nothing was selected). */
|
|
BMFE_FinishedWhatsChanged(context, w->total, w->numreached,
|
|
w->numchanged);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
BM_CancelWhatsChanged(MWContext* context)
|
|
{
|
|
XP_InterruptContext(context);
|
|
return 0;
|
|
}
|
|
|
|
void BM_ResetUndo(MWContext * context)
|
|
{
|
|
BM_Frame * f = GETFRAME(context);
|
|
UNDO_DiscardAll( f->undo );
|
|
}
|
|
|