File:  [mozdev] / chimera / BookmarksService.mm
Revision 1.24: download - view: text, annotated - select for diffs - revision graph
Wed Apr 17 23:55:25 2002 UTC (15 years, 6 months ago) by hyatt
Branches: MAIN
CVS tags: HEAD
Hooking up new folder.

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or 
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#import "NSBrowserView.h"
#include "BookmarksService.h"
#include "nsIDocument.h"
#include "nsIContent.h"
#include "nsIAtom.h"
#include "nsITextContent.h"
#include "nsIDOMWindow.h"
#include "nsIDOMHTMLDocument.h"
#include "nsIDOMElement.h"
#include "nsString.h"
#include "nsIFile.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsIXMLHttpRequest.h"
#include "nsIDOMSerializer.h"
#include "nsNetUtil.h"
#include "nsINamespaceManager.h"
#include "nsIXBLService.h"
#include "nsIWebBrowser.h"

@implementation BookmarksDataSource

-(id) init
{
    [super init];
    mBookmarks = nsnull;
    mCachedParent = nsnull;
    mCachedHref = nil;
    return self;
}

-(void) dealloc
{
  [super dealloc];
}

-(void) windowClosing
{
  if (mBookmarks) {
    mBookmarks->RemoveObserver();
    delete mBookmarks;
  }
}

-(void) ensureBookmarks
{
    if (mBookmarks)
        return;
    
    mBookmarks = new BookmarksService(self);
    mBookmarks->AddObserver();
    
    [mOutlineView setTarget: self];
    [mOutlineView setDoubleAction: @selector(openBookmark:)];
    [mOutlineView reloadData];
}

-(IBAction)addBookmark:(id)aSender
{
  [self addBookmark: aSender useSelection: YES isFolder: NO];
}

-(IBAction)addFolder:(id)aSender
{
  [self addBookmark: aSender useSelection: YES isFolder: YES];
}

-(void)addBookmark:(id)aSender useSelection:(BOOL)aUseSel isFolder:(BOOL)aIsFolder
{
  if (!mBookmarks)
    return;

  nsCOMPtr<nsIContent> content;
  if (aUseSel) {
    int index = [mOutlineView selectedRow];

    if (index >= 0) {
      BookmarkItem* item = [mOutlineView itemAtRow: index];
      if ([mOutlineView isExpandable: item])
        content = [item contentNode];
    }
  }

  if (!content)
    mBookmarks->GetRootContent(getter_AddRefs(content));

  mCachedParent = content;
  
  nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mBookmarks->gBookmarks));
  
  // Fetch the title of the current page and the URL.
  nsAutoString title;
  if (!aIsFolder) {
    nsCOMPtr<nsIWebBrowser> webBrowser = getter_AddRefs([[[mBrowserWindowController getMyBrowserView]
      getBrowserView] getWebBrowser]);
    nsCOMPtr<nsIDOMWindow> window;
    webBrowser->GetContentDOMWindow(getter_AddRefs(window));
    nsCOMPtr<nsIDOMDocument> htmlDoc;
    window->GetDocument(getter_AddRefs(htmlDoc));
    nsCOMPtr<nsIDocument> pageDoc(do_QueryInterface(htmlDoc));

    nsAutoString href;
    if (pageDoc) {
      nsCOMPtr<nsIURI> url;
      pageDoc->GetDocumentURL(getter_AddRefs(url));
      nsCAutoString spec;
      url->GetSpec(spec);
      href.AssignWithConversion(spec.get());
    }

    mCachedHref = [NSString stringWithCharacters: href.get() length: nsCRT::strlen(href.get())];
    [mCachedHref retain];
  
    nsCOMPtr<nsIDOMHTMLDocument> htmlDocument(do_QueryInterface(htmlDoc));
    if (htmlDocument)
      htmlDocument->GetTitle(title);
    if (title.IsEmpty())
      title = href;
  }
  else {
    mCachedHref = nil;
    title = NS_LITERAL_STRING("New Folder");
  }
  
  NSTextField* textField = [mBrowserWindowController getAddBookmarkTitle];
  [textField setStringValue: [NSString stringWithCharacters: title.get() length: nsCRT::strlen(title.get())]];

  [mBrowserWindowController cacheBookmarkDS: self];
  
  [NSApp beginSheet:	[mBrowserWindowController getAddBookmarkSheetWindow]
     modalForWindow:	[mBrowserWindowController window]
      modalDelegate:	nil //self
     didEndSelector:	nil //@selector(sheetDidEnd:)
        contextInfo:	nil];
}

-(void)endAddBookmark: (int)aCode
{
  if (aCode == 0)
    return;
  
  const char* titleC = [[[mBrowserWindowController getAddBookmarkTitle] stringValue] cString];
  nsAutoString title; title.AssignWithConversion(titleC);

  nsAutoString tagName;
  if (mCachedHref)
    tagName = NS_LITERAL_STRING("bookmark");
  else
    tagName = NS_LITERAL_STRING("folder");
  
  nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mBookmarks->gBookmarks));
  nsCOMPtr<nsIDOMElement> elt;
  domDoc->CreateElementNS(NS_LITERAL_STRING("http://chimera.mozdev.org/bookmarks/"),
                          tagName,
                          getter_AddRefs(elt));

  elt->SetAttribute(NS_LITERAL_STRING("name"), title);

  if (mCachedHref) {
    nsAutoString href; href.AssignWithConversion([mCachedHref cString]);
    [mCachedHref release];
    elt->SetAttribute(NS_LITERAL_STRING("href"), href);
  }
  
  nsCOMPtr<nsIDOMElement> parent(do_QueryInterface(mCachedParent));
  nsCOMPtr<nsIDOMNode> dummy;
  parent->AppendChild(elt, getter_AddRefs(dummy));

  nsCOMPtr<nsIContent> childContent(do_QueryInterface(elt));
  mBookmarks->BookmarkAdded(mCachedParent, childContent);
}

-(IBAction)deleteBookmark: (id)aSender
{
    if (!mBookmarks)
        return;
    
    int index = [mOutlineView selectedRow];
    if (index == -1)
        return;
        
    BookmarkItem* item = [mOutlineView itemAtRow: index];
    nsCOMPtr<nsIContent> content = [item contentNode];
    nsCOMPtr<nsIDOMElement> child(do_QueryInterface(content));
    nsCOMPtr<nsIDOMNode> parent;
    child->GetParentNode(getter_AddRefs(parent));
    nsCOMPtr<nsIContent> parentContent(do_QueryInterface(parent));
    nsCOMPtr<nsIDOMNode> dummy;
    parent->RemoveChild(child, getter_AddRefs(dummy));
    mBookmarks->BookmarkRemoved(parentContent, content);
    
    int total = [mOutlineView numberOfRows];
    if (index == total)
        index--;
        
    [mOutlineView selectRow: index byExtendingSelection: NO];
}

-(IBAction)openBookmark: (id)aSender
{
    int index = [mOutlineView selectedRow];
    if (index == -1)
        return;
    
    id item = [mOutlineView itemAtRow: index];
    if (!item)
        return;
        
    if ([mOutlineView isExpandable: item]) {
        if ([mOutlineView isItemExpanded: item])
            [mOutlineView collapseItem: item];
        else
            [mOutlineView expandItem: item];
    }
    else {
        nsIContent* content = [item contentNode];
        nsAutoString href;
        content->GetAttr(kNameSpaceID_None, BookmarksService::gHrefAtom, href);
        if (!href.IsEmpty()) {
            nsCAutoString cstr; cstr.AssignWithConversion(href);
            NSString* url = [NSString stringWithCString: cstr.get()];
            [[[mBrowserWindowController getMyBrowserView] getBrowserView] loadURI:[NSURL URLWithString: url] flags:NSLoadFlagsNone];
            // Focus and activate our content area.
            [[[mBrowserWindowController getMyBrowserView] getBrowserView] setActive: YES];
        }
    } 
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    return NO;
}

- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
{
    if (!mBookmarks)
        return nil;
       
    nsCOMPtr<nsIContent> content;
    if (!item)
        mBookmarks->GetRootContent(getter_AddRefs(content));
    else
        content = [item contentNode];
    
    nsCOMPtr<nsIContent> child;
    content->ChildAt(index, *getter_AddRefs(child));
    return mBookmarks->GetWrapperFor(child);
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    if (!mBookmarks)
        return NO;
    
    if (!item)
        return YES; // The root node is always open.
    
    nsCOMPtr<nsIAtom> tagName;
    nsIContent* content = [item contentNode];
    content->GetTag(*getter_AddRefs(tagName));
    
    return (tagName == BookmarksService::gFolderAtom);
}

- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    if (!mBookmarks)
        return 0;
  
    nsCOMPtr<nsIContent> content;
    if (!item)
        mBookmarks->GetRootContent(getter_AddRefs(content));
    else 
        content = [item contentNode];
    
    PRInt32 childCount;
    content->ChildCount(childCount);
    
    return childCount;
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
    NSString 					*columnName = [tableColumn identifier];
    NSMutableAttributedString 	*cellValue = [[NSMutableAttributedString alloc] init];
    NSFileWrapper				*fileWrapper = [[NSFileWrapper alloc] initRegularFileWithContents:nil];
    NSTextAttachment			*textAttachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper];
    NSMutableAttributedString   *attachmentAttrString = nil;
    NSCell 						*attachmentAttrStringCell;

    if ([columnName isEqualToString: @"name"]) {
        nsIContent* content = [item contentNode];
        nsAutoString nameAttr;
        content->GetAttr(kNameSpaceID_None, BookmarksService::gNameAtom, nameAttr);
        nsCAutoString cStr; cStr.AssignWithConversion(nameAttr);
        
        //Set cell's textual contents
        [cellValue replaceCharactersInRange:NSMakeRange(0, [cellValue length])
                                 withString:[NSString stringWithCString: cStr.get()]];
        
        //Create an attributed string to hold the empty attachment, then release the components.
        attachmentAttrString = [[NSMutableAttributedString attributedStringWithAttachment:textAttachment] retain];
        [textAttachment release];
        [fileWrapper release];

        //Get the cell of the text attachment.
        attachmentAttrStringCell = (NSCell *)[(NSTextAttachment *)[attachmentAttrString attribute:NSAttachmentAttributeName
                                                                                          atIndex:0
                                                                                   effectiveRange:nil] attachmentCell];
        //Figure out which image to add, and set the cell's image.
        if ( [self outlineView:outlineView isItemExpandable:item] ) {
            [attachmentAttrStringCell setImage:[NSImage imageNamed:@"folder"]];
        } else {
            [attachmentAttrStringCell setImage:[NSImage imageNamed:@"smallbookmark"]];
        }
        //Insert the image
        [cellValue replaceCharactersInRange:NSMakeRange(0, 0) withAttributedString:attachmentAttrString];
        
        //Tweak the baseline to vertically center the text.
        [cellValue addAttribute:NSBaselineOffsetAttributeName
                          value:[NSNumber numberWithFloat:-3.0]
                          range:NSMakeRange(0, 1)];
    }
    return cellValue;
}

- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
}

- (void)reloadDataForItem:(id)item reloadChildren: (BOOL)aReloadChildren
{
    printf("Reloading?\n");
    if (!item)
        [mOutlineView reloadData];
    else if ([mOutlineView isItemExpanded: item])
        [mOutlineView reloadItem: item reloadChildren: aReloadChildren];
}

@end

@implementation BookmarkItem
-(nsIContent*)contentNode
{
    return mContentNode;
}

-(void)setContentNode: (nsIContent*)aContentNode
{
    mContentNode = aContentNode;
}

- (id)copyWithZone:(NSZone *)aZone
{
    BookmarkItem* copy = [[[self class] allocWithZone: aZone] init];
    [copy setContentNode: mContentNode];
    return copy;
}

@end

// Helper for stripping whitespace
static void
StripWhitespaceNodes(nsIContent* aElement)
{
    PRInt32 childCount;
    aElement->ChildCount(childCount);
    for (PRInt32 i = 0; i < childCount; i++) {
        nsCOMPtr<nsIContent> child;
        aElement->ChildAt(i, *getter_AddRefs(child));
        nsCOMPtr<nsITextContent> text = do_QueryInterface(child);
        if (text) {
            PRBool isEmpty;
            text->IsOnlyWhitespace(&isEmpty);
            if (isEmpty) {
                // This node contained nothing but whitespace.
                // Remove it from the content model.
                aElement->RemoveChildAt(i, PR_TRUE);
                i--; // Decrement our count, since we just removed this child.
                childCount--; // Also decrement our total count.
            }
        }
        else StripWhitespaceNodes(child);
    }
}

PRUint32 BookmarksService::gRefCnt = 0;
nsIDocument* BookmarksService::gBookmarks = nsnull;
NSMutableDictionary* BookmarksService::gDictionary = nil;
MainController* BookmarksService::gMainController = nil;
NSMenu* BookmarksService::gBookmarksMenu = nil;
nsIDOMElement* BookmarksService::gToolbarRoot = nsnull;
nsIAtom* BookmarksService::gFolderAtom = nsnull;
nsIAtom* BookmarksService::gBookmarkAtom = nsnull;
nsIAtom* BookmarksService::gHrefAtom = nsnull;
nsIAtom* BookmarksService::gNameAtom = nsnull;
nsVoidArray* BookmarksService::gInstances = nsnull;

BookmarksService::BookmarksService(BookmarksDataSource* aDataSource)
{
  mDataSource = aDataSource;
  mToolbar = nil;
}

BookmarksService::BookmarksService(BookmarksToolbar* aToolbar)
{
  mDataSource = nil;
  mToolbar = aToolbar;
}

BookmarksService::~BookmarksService()
{
}

void
BookmarksService::GetRootContent(nsIContent** aResult)
{
    *aResult = nsnull;
    if (gBookmarks) {
        nsCOMPtr<nsIDOMElement> elt;
        nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(gBookmarks));
        domDoc->GetDocumentElement(getter_AddRefs(elt));
        elt->QueryInterface(NS_GET_IID(nsIContent), (void**)aResult); // Addref happens here.
    }
}

BookmarkItem*
BookmarksService::GetWrapperFor(nsIContent* aContent)
{
    if (!gDictionary)
        gDictionary = [[NSMutableDictionary alloc] initWithCapacity: 30];
    
    PRUint32 contentID;
    aContent->GetContentID(&contentID);
    
    BookmarkItem* item = [gDictionary objectForKey: [NSNumber numberWithInt: contentID]];
    if (item)
        return item;
    else {
        // Create an item.
        item = [[[BookmarkItem alloc] init] autorelease]; // The dictionary retains us.
        [item setContentNode: aContent];
        [gDictionary setObject: item forKey: [NSNumber numberWithInt: contentID]];
    }
    return item;
}

NSMenu*
BookmarksService::LocateMenu(nsIContent* aContent)
{
  nsCOMPtr<nsIContent> parent;
  aContent->GetParent(*getter_AddRefs(parent));
  if (!parent) {
    return BookmarksService::gBookmarksMenu;
  }
  
  NSMenu* parentMenu = LocateMenu(parent);
  
  PRUint32 contentID;
  aContent->GetContentID(&contentID);

  NSMenuItem* childMenu = [parentMenu itemWithTag: contentID];
  return [childMenu submenu];
}

void
BookmarksService::BookmarkAdded(nsIContent* aContainer, nsIContent* aChild)
{
  if (!gInstances || !gDictionary)
    return;

  PRInt32 count = gInstances->Count();
  for (PRInt32 i = 0; i < count; i++) {
    BookmarksService* instance = (BookmarksService*)gInstances->ElementAt(i);

    if (instance->mDataSource) {
      // We're a tree view.
      nsCOMPtr<nsIContent> parent;
      aContainer->GetParent(*getter_AddRefs(parent));

      BookmarkItem* item = nil;
      if (parent)
        // We're not the root.
        item = GetWrapperFor(aContainer);

      [(instance->mDataSource) reloadDataForItem: item reloadChildren: YES];
    }
    else if (instance->mToolbar) {
      // We're a personal toolbar.
      nsCOMPtr<nsIDOMElement> parentElt(do_QueryInterface(aContainer));
      if (parentElt == gToolbarRoot) {
        // We only care about changes that occur to the personal toolbar's immediate
        // children.
        PRInt32 index = -1;
        aContainer->IndexOf(aChild, index);
        nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(aChild));
        [(instance->mToolbar) addButton: elt atIndex: index];
      }
    }
    else {
      // We're the menu.
      PRInt32 index = -1;
      aContainer->IndexOf(aChild, index);
      NSMenu* menu = LocateMenu(aContainer);
      AddMenuBookmark(menu, aContainer, aChild, index);
    }
  }
  
  FlushBookmarks();  
}

void
BookmarksService::BookmarkChanged(nsIContent* aItem)
{
  if (!gInstances || !gDictionary)
    return;

  PRInt32 count = gInstances->Count();
  for (PRInt32 i = 0; i < count; i++) {
    BookmarksService* instance = (BookmarksService*)gInstances->ElementAt(i);
   
    if (instance->mDataSource) {
      BookmarkItem* item = GetWrapperFor(aItem);
      [(instance->mDataSource) reloadDataForItem: item reloadChildren: NO];
    }
  }

  FlushBookmarks();  
}

void
BookmarksService::BookmarkRemoved(nsIContent* aContainer, nsIContent* aChild)
{
  if (!gInstances)
    return;

  PRInt32 count = gInstances->Count();
  for (PRInt32 i = 0; i < count; i++) {
    BookmarksService* instance = (BookmarksService*)gInstances->ElementAt(i);

    if (instance->mDataSource) {
      // We're a tree view.
      nsCOMPtr<nsIContent> parent;
      aContainer->GetParent(*getter_AddRefs(parent));

      BookmarkItem* item = nil;
      if (parent)
        // We're not the root.
        item = GetWrapperFor(aContainer);

      [(instance->mDataSource) reloadDataForItem: item reloadChildren: YES];
    }
    else if (instance->mToolbar) {
      // We're a personal toolbar.
      nsCOMPtr<nsIDOMElement> parentElt(do_QueryInterface(aContainer));
      if (parentElt == gToolbarRoot) {
        // We only care about changes that occur to the personal toolbar's immediate
        // children.
        nsCOMPtr<nsIDOMElement> childElt(do_QueryInterface(aChild));
        [(instance->mToolbar) removeButton: childElt];
      }
    }    
    else {
      // We're the menu.
      NSMenu* menu = LocateMenu(aContainer);
      PRUint32 contentID;
      aChild->GetContentID(&contentID);
      NSMenuItem* childItem = [menu itemWithTag: contentID];
      [menu removeItem: childItem];
    }
  }

  FlushBookmarks(); 
}

void
BookmarksService::AddObserver()
{
    gRefCnt++;
    if (gRefCnt == 1) {
        gBookmarkAtom = NS_NewAtom("bookmark");
        gFolderAtom = NS_NewAtom("folder");
        gNameAtom = NS_NewAtom("name");
        gHrefAtom = NS_NewAtom("href");
        gInstances = new nsVoidArray();
                
        nsCOMPtr<nsIFile> profileDir;
        NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir));
        profileDir->Append("bookmarks.xml");
    
        nsCAutoString bookmarksFileURL;
        NS_GetURLSpecFromFile(profileDir, bookmarksFileURL);
        
        nsCOMPtr<nsIURI> uri;
        NS_NewURI(getter_AddRefs(uri), bookmarksFileURL.get());
    
        nsCOMPtr<nsIXBLService> xblService(do_GetService("@mozilla.org/xbl;1"));    
        xblService->FetchSyncXMLDocument(uri, &gBookmarks); // The addref is here.
        
        nsCOMPtr<nsIContent> rootNode;
        GetRootContent(getter_AddRefs(rootNode));
        StripWhitespaceNodes(rootNode);
    }
    
    gInstances->AppendElement(this);
}

void
BookmarksService::RemoveObserver()
{
    if (gRefCnt == 0)
        return;
 
    gInstances->RemoveElement(this);
     
    gRefCnt--;
    if (gRefCnt == 0) {
        NS_IF_RELEASE(gBookmarks);
        NS_RELEASE(gBookmarkAtom);
        NS_RELEASE(gFolderAtom);
        NS_RELEASE(gNameAtom);
        NS_RELEASE(gHrefAtom);
        [gDictionary release];
    }
}

void
BookmarksService::FlushBookmarks()
{
    nsCOMPtr<nsIFile> bookmarksFile;
    NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(bookmarksFile));
    bookmarksFile->Append("bookmarks.xml");

    nsCOMPtr<nsIOutputStream> outputStream;
    NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), bookmarksFile);

    nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(gBookmarks));
    
    nsCOMPtr<nsIDOMSerializer> domSerializer(do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID));
    domSerializer->SerializeToStream(domDoc, outputStream, nsnull);
}

void BookmarksService::EnsureToolbarRoot()
{
  if (gToolbarRoot)
    return;

  nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(gBookmarks));
  nsCOMPtr<nsIDOMElement> rootElt;
  domDoc->GetDocumentElement(getter_AddRefs(rootElt));
  
  nsCOMPtr<nsIDOMNode> child;
  rootElt->GetFirstChild(getter_AddRefs(child));
  nsAutoString typeValue;
  while (child) {
    nsCOMPtr<nsIDOMElement> childElt(do_QueryInterface(child));
    if (childElt) {
      childElt->GetAttribute(NS_LITERAL_STRING("type"), typeValue);
      if (typeValue.Equals(NS_LITERAL_STRING("toolbar")))
        gToolbarRoot = childElt;
    }
    
    nsCOMPtr<nsIDOMNode> temp;
    child->GetNextSibling(getter_AddRefs(temp));
    child = temp;
  }

  if (!gToolbarRoot) {
    printf("Repairing personal toolbar.\n");
    nsCOMPtr<nsIDOMElement> elt;
    domDoc->CreateElementNS(NS_LITERAL_STRING("http://chimera.mozdev.org/bookmarks/"),
                            NS_LITERAL_STRING("folder"),
                            getter_AddRefs(elt));

    elt->SetAttribute(NS_LITERAL_STRING("name"), NS_LITERAL_STRING("Toolbar Bookmarks"));
    elt->SetAttribute(NS_LITERAL_STRING("type"), NS_LITERAL_STRING("toolbar"));

    nsCOMPtr<nsIDOMNode> dummy;
    rootElt->AppendChild(elt, getter_AddRefs(dummy));
    gToolbarRoot = elt;
  }
}

void
BookmarksService::ConstructBookmarksMenu(NSMenu* aMenu, nsIContent* aContent)
{
    nsCOMPtr<nsIContent> content = aContent;
    if (!content) {
        GetRootContent(getter_AddRefs(content));
        GetWrapperFor(content);
        gBookmarksMenu = aMenu;
    }
    
    // Now walk our children, and for folders also recur into them.
    PRInt32 childCount;
    content->ChildCount(childCount);
    
    for (PRInt32 i = 0; i < childCount; i++) {
      nsCOMPtr<nsIContent> child;
      content->ChildAt(i, *getter_AddRefs(child));
      AddMenuBookmark(aMenu, content, child, -1);
    }
}

void
BookmarksService::AddMenuBookmark(NSMenu* aMenu, nsIContent* aParent, nsIContent* aChild, PRInt32 aIndex)
{
  nsAutoString name;
  aChild->GetAttr(kNameSpaceID_None, gNameAtom, name);
  nsCAutoString nameCStr; nameCStr.AssignWithConversion(name);
  NSString* title = [NSString stringWithCString: nameCStr.get()];

  // Create a menu or menu item for the child.
  NSMenuItem* menuItem = [[[NSMenuItem alloc] initWithTitle: title action: NULL keyEquivalent: @""] autorelease];
  GetWrapperFor(aChild);

  if (aIndex == -1)
    [aMenu addItem: menuItem];
  else
    [aMenu insertItem: menuItem atIndex: aIndex];
  
  nsCOMPtr<nsIAtom> tagName;
  aChild->GetTag(*getter_AddRefs(tagName));

  if (tagName == gFolderAtom) {
    NSMenu* menu = [[[NSMenu alloc] initWithTitle: title] autorelease];
    [aMenu setSubmenu: menu forItem: menuItem];
    [menu setAutoenablesItems: NO];
    ConstructBookmarksMenu(menu, aChild);
  }
  else {
    [menuItem setTarget: gMainController];
    [menuItem setAction: @selector(openMenuBookmark:)];
  }

  PRUint32 contentID;
  aChild->GetContentID(&contentID);
  [menuItem setTag: contentID];
}

void 
BookmarksService::OpenMenuBookmark(BrowserWindowController* aController, id aMenuItem)
{
    // Get the corresponding bookmark item.
    BookmarkItem* item = [gDictionary objectForKey: [NSNumber numberWithInt: [aMenuItem tag]]];
        
    // Get the content node.
    nsIContent* content = [item contentNode];
        
    // Get the href attribute.  This is the URL we want to load.
    nsAutoString href;
    content->GetAttr(kNameSpaceID_None, gHrefAtom, href);
    nsCAutoString cref; cref.AssignWithConversion(href);
    if (cref.IsEmpty())
        return;
        
    NSString* url = [NSString stringWithCString: cref.get()];
    
    // Now load the URL in the window.
    [aController loadURL:[NSURL URLWithString: url]];
    
    // Focus and activate our content area.
    [[[aController getMyBrowserView] getBrowserView] setActive: YES];
}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>