/* * BookmarksService.cpp * Chimera * * Created by David Hyatt on Thu Feb 07 2002. * Copyright (c) 2001 __MyCompanyName__. All rights reserved. * */ #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; 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 { if (!mBookmarks) return; nsCOMPtr content; 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)); 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)); // 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 { 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]; } } } - (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 content; if (!item) mBookmarks->GetRootContent(getter_AddRefs(content)); else content = [item contentNode]; nsCOMPtr 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 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 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:@"smallDocument"]]; } //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 child; aElement->ChildAt(i, *getter_AddRefs(child)); nsCOMPtr 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; 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; } BookmarksService::~BookmarksService() { } void BookmarksService::GetRootContent(nsIContent** aResult) { *aResult = nsnull; if (gBookmarks) { nsCOMPtr elt; nsCOMPtr 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; } 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) { 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); } 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 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]]; }