--- chimera/BookmarksService.mm 2002/02/09 01:25:26 1.3 +++ chimera/BookmarksService.mm 2002/03/10 08:14:36 1.17 @@ -1,11 +1,39 @@ -/* - * BookmarksService.cpp - * Chimera +/* -*- 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 * - * Created by David Hyatt on Thu Feb 07 2002. - * Copyright (c) 2001 __MyCompanyName__. All rights reserved. + * 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" @@ -13,15 +41,18 @@ #include "nsIContent.h" #include "nsIAtom.h" #include "nsITextContent.h" -#include "nsIDOMDocument.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 @@ -35,8 +66,14 @@ -(void) dealloc { [super dealloc]; - if (mBookmarks) - BookmarksService::RemoveObserver(nsnull); +} + +-(void) windowClosing +{ + if (mBookmarks) { + mBookmarks->RemoveObserver(); + delete mBookmarks; + } } -(void) ensureBookmarks @@ -44,7 +81,8 @@ if (mBookmarks) return; - mBookmarks = BookmarksService::AddObserver(nsnull); + mBookmarks = new BookmarksService(self); + mBookmarks->AddObserver(); [mOutlineView setTarget: self]; [mOutlineView setDoubleAction: @selector(openBookmark:)]; @@ -55,37 +93,83 @@ { if (!mBookmarks) return; - + nsCOMPtr content; int index = [mOutlineView selectedRow]; - - if (index > 0) { + + if (index >= 0) { BookmarkItem* item = [mOutlineView itemAtRow: index]; - if (![mOutlineView isExpandable: item]) + if ([mOutlineView isExpandable: item]) content = [item contentNode]; } if (!content) mBookmarks->GetRootContent(getter_AddRefs(content)); - nsCOMPtr doc; - content->GetDocument(*getter_AddRefs(doc)); - nsCOMPtr domDoc(do_QueryInterface(doc)); + nsCOMPtr domDoc(do_QueryInterface(mBookmarks->gBookmarks)); nsCOMPtr elt; domDoc->CreateElementNS(NS_LITERAL_STRING("http://chimera.mozdev.org/bookmarks"), NS_LITERAL_STRING("bookmark"), getter_AddRefs(elt)); - elt->SetAttribute(NS_LITERAL_STRING("title"), NS_LITERAL_STRING("MozillaZine")); - elt->SetAttribute(NS_LITERAL_STRING("href"), NS_LITERAL_STRING("http://www.mozillazine.org/")); + // Fetch the title of the current page and the URL. + nsCOMPtr webBrowser = getter_AddRefs([[[mBrowserWindowController getMyBrowserView] getBrowserView] getWebBrowser]); + nsCOMPtr window; + webBrowser->GetContentDOMWindow(getter_AddRefs(window)); + nsCOMPtr htmlDoc; + window->GetDocument(getter_AddRefs(htmlDoc)); + nsCOMPtr pageDoc(do_QueryInterface(htmlDoc)); + + nsAutoString href; + if (pageDoc) { + nsCOMPtr url; + pageDoc->GetDocumentURL(getter_AddRefs(url)); + nsCAutoString spec; + url->GetSpec(spec); + href.AssignWithConversion(spec.get()); + } + + nsAutoString title; + nsCOMPtr htmlDocument(do_QueryInterface(htmlDoc)); + if (htmlDocument) + htmlDocument->GetTitle(title); + if (title.IsEmpty()) + title = href; + + elt->SetAttribute(NS_LITERAL_STRING("name"), title); + elt->SetAttribute(NS_LITERAL_STRING("href"), href); nsCOMPtr parent(do_QueryInterface(content)); nsCOMPtr dummy; parent->AppendChild(elt, getter_AddRefs(dummy)); + + mBookmarks->NotifyObservers(content, PR_TRUE); } -(IBAction)deleteBookmark: (id)aSender { + if (!mBookmarks) + return; + + int index = [mOutlineView selectedRow]; + if (index == -1) + return; + + BookmarkItem* item = [mOutlineView itemAtRow: index]; + nsCOMPtr content = [item contentNode]; + nsCOMPtr child(do_QueryInterface(content)); + nsCOMPtr parent; + child->GetParentNode(getter_AddRefs(parent)); + nsCOMPtr dummy; + parent->RemoveChild(child, getter_AddRefs(dummy)); + nsCOMPtr parentContent(do_QueryInterface(parent)); + mBookmarks->NotifyObservers(parentContent, PR_TRUE); + + int total = [mOutlineView numberOfRows]; + if (index == total) + index--; + + [mOutlineView selectRow: index byExtendingSelection: NO]; } -(IBAction)openBookmark: (id)aSender @@ -111,11 +195,16 @@ if (!href.IsEmpty()) { nsCAutoString cstr; cstr.AssignWithConversion(href); NSString* url = [NSString stringWithCString: cstr.get()]; - [[mBrowserView getBrowserView] loadURI:[NSURL URLWithString: url] flags:NSLoadFlagsNone]; + [[[mBrowserWindowController getMyBrowserView] getBrowserView] loadURI:[NSURL URLWithString: url] flags:NSLoadFlagsNone]; } } } +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + return NO; +} + - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item { if (!mBookmarks) @@ -166,26 +255,62 @@ - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { - if (!item) - return nil; - - NSString* columnName = [tableColumn identifier]; - + 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); - return [NSString stringWithCString: cStr.get()]; + + //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 nil; + 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 @@ -198,6 +323,14 @@ { mContentNode = aContentNode; } + +- (id)copyWithZone:(NSZone *)aZone +{ + BookmarkItem* copy = [[[self class] allocWithZone: aZone] init]; + [copy setContentNode: mContentNode]; + return copy; +} + @end // Helper for stripping whitespace @@ -226,45 +359,31 @@ StripWhitespaceNodes(nsIContent* aElemen } PRUint32 BookmarksService::gRefCnt = 0; -BookmarksService* BookmarksService::gSingleton = nsnull; +nsIDocument* BookmarksService::gBookmarks = nsnull; +NSMutableDictionary* BookmarksService::gDictionary = nil; +MainController* BookmarksService::gMainController = nil; nsIAtom* BookmarksService::gFolderAtom = nsnull; nsIAtom* BookmarksService::gBookmarkAtom = nsnull; nsIAtom* BookmarksService::gHrefAtom = nsnull; nsIAtom* BookmarksService::gNameAtom = nsnull; +nsVoidArray* BookmarksService::gInstances = nsnull; -BookmarksService::BookmarksService() +BookmarksService::BookmarksService(BookmarksDataSource* aDataSource) { - mDictionary = nil; - nsCOMPtr profileDir; - NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir)); - profileDir->Append("bookmarks.xml"); - - nsXPIDLCString bookmarksFileURL; - NS_GetURLSpecFromFile(profileDir, getter_Copies(bookmarksFileURL)); - - nsCOMPtr uri; - NS_NewURI(getter_AddRefs(uri), bookmarksFileURL.get()); - - nsCOMPtr xblService(do_GetService("@mozilla.org/xbl;1")); - xblService->FetchSyncXMLDocument(uri, getter_AddRefs(mBookmarks)); - - nsCOMPtr rootNode; - GetRootContent(getter_AddRefs(rootNode)); - StripWhitespaceNodes(rootNode); + mDataSource = aDataSource; } BookmarksService::~BookmarksService() { - [mDictionary release]; } void BookmarksService::GetRootContent(nsIContent** aResult) { *aResult = nsnull; - if (mBookmarks) { + if (gBookmarks) { nsCOMPtr elt; - nsCOMPtr domDoc(do_QueryInterface(mBookmarks)); + nsCOMPtr domDoc(do_QueryInterface(gBookmarks)); domDoc->GetDocumentElement(getter_AddRefs(elt)); elt->QueryInterface(NS_GET_IID(nsIContent), (void**)aResult); // Addref happens here. } @@ -273,52 +392,190 @@ BookmarksService::GetRootContent(nsICont BookmarkItem* BookmarksService::GetWrapperFor(nsIContent* aContent) { - if (!mDictionary) - mDictionary = [[NSMutableDictionary alloc] initWithCapacity: 30]; + if (!gDictionary) + gDictionary = [[NSMutableDictionary alloc] initWithCapacity: 30]; PRUint32 contentID; aContent->GetContentID(&contentID); - BookmarkItem* item = [mDictionary objectForKey: [NSNumber numberWithInt: 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]; - [mDictionary setObject: item forKey: [NSNumber numberWithInt: contentID]]; + [gDictionary setObject: item forKey: [NSNumber numberWithInt: contentID]]; } return item; } -BookmarksService* -BookmarksService::AddObserver(nsIDocumentObserver* aObserver) +void +BookmarksService::NotifyObservers(nsIContent* aContainer, PRBool aReloadChildren) +{ + if (!gInstances) + return; + + PRInt32 count = gInstances->Count(); + for (PRInt32 i = 0; i < count; i++) { + BookmarksService* instance = (BookmarksService*)gInstances->ElementAt(i); + instance->NotifyObserver(aContainer, aReloadChildren); + } + + FlushBookmarks(); +} + + +void +BookmarksService::NotifyObserver(nsIContent* aContainer, PRBool aReloadChildren) +{ + if (!gDictionary) + return; + + nsCOMPtr parent; + aContainer->GetParent(*getter_AddRefs(parent)); + + BookmarkItem* item = nil; + if (parent) + // We're not the root. + item = GetWrapperFor(aContainer); + + [mDataSource reloadDataForItem: item reloadChildren: aReloadChildren]; +} + +void +BookmarksService::AddObserver() { gRefCnt++; if (gRefCnt == 1) { - gSingleton = new BookmarksService(); gBookmarkAtom = NS_NewAtom("bookmark"); gFolderAtom = NS_NewAtom("folder"); gNameAtom = NS_NewAtom("name"); gHrefAtom = NS_NewAtom("href"); + gInstances = new nsVoidArray(); + + nsCOMPtr profileDir; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir)); + profileDir->Append("bookmarks.xml"); + + nsCAutoString bookmarksFileURL; + NS_GetURLSpecFromFile(profileDir, bookmarksFileURL); + + nsCOMPtr uri; + NS_NewURI(getter_AddRefs(uri), bookmarksFileURL.get()); + + nsCOMPtr xblService(do_GetService("@mozilla.org/xbl;1")); + xblService->FetchSyncXMLDocument(uri, &gBookmarks); // The addref is here. + + nsCOMPtr rootNode; + GetRootContent(getter_AddRefs(rootNode)); + StripWhitespaceNodes(rootNode); } - return gSingleton; + gInstances->AppendElement(this); } void -BookmarksService::RemoveObserver(nsIDocumentObserver* aObserver) +BookmarksService::RemoveObserver() { if (gRefCnt == 0) return; - + + gInstances->RemoveElement(this); + gRefCnt--; if (gRefCnt == 0) { - delete gSingleton; + NS_IF_RELEASE(gBookmarks); NS_RELEASE(gBookmarkAtom); NS_RELEASE(gFolderAtom); NS_RELEASE(gNameAtom); NS_RELEASE(gHrefAtom); + [gDictionary release]; + } +} + +void +BookmarksService::FlushBookmarks() +{ + nsCOMPtr bookmarksFile; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(bookmarksFile)); + bookmarksFile->Append("bookmarks.xml"); + + nsCOMPtr outputStream; + NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), bookmarksFile); + + nsCOMPtr domDoc(do_QueryInterface(gBookmarks)); + + nsCOMPtr domSerializer(do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID)); + domSerializer->SerializeToStream(domDoc, outputStream, nsnull); +} + +void +BookmarksService::ConstructBookmarksMenu(NSMenu* aMenu, nsIContent* aContent) +{ + nsCOMPtr content = aContent; + if (!content) { + GetRootContent(getter_AddRefs(content)); + GetWrapperFor(content); + } + + // Now walk our children, and for folders also recur into them. + PRInt32 childCount; + content->ChildCount(childCount); + + for (PRInt32 i = 0; i < childCount; i++) { + nsCOMPtr child; + content->ChildAt(i, *getter_AddRefs(child)); + + // Obtain our name attribute. + nsAutoString name; + child->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(child); + [aMenu addItem: menuItem]; + + nsCOMPtr tag; + child->GetTag(*getter_AddRefs(tag)); + + if (tag == gFolderAtom) { + NSMenu* menu = [[[NSMenu alloc] initWithTitle: title] autorelease]; + [aMenu setSubmenu: menu forItem: menuItem]; + [menu setAutoenablesItems: NO]; + ConstructBookmarksMenu(menu, child); + } + else { + [menuItem setTarget: gMainController]; + [menuItem setAction: @selector(openMenuBookmark:)]; + } + + PRUint32 contentID; + child->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]]; +} \ No newline at end of file