/* -*- 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) 1998 * 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 "NSString+Utils.h" #include "nsCWebBrowser.h" #include "nsIBaseWindow.h" #include "nsIWebNavigation.h" #include "nsComponentManagerUtils.h" #include "nsIURI.h" #include "nsIDOMWindow.h" #include "nsIWidget.h" // Printing #include "nsIWebBrowserPrint.h" //#include "nsIPrintSettings.h" // Saving of links/images/docs #include "nsIWebBrowserFocus.h" #include "nsIDOMNSDocument.h" #include "nsIDOMLocation.h" #include "nsIWebBrowserPersist.h" #include "nsIProperties.h" //#include "nsIRequest.h" //#include "nsIPrefService.h" #include "nsISHistory.h" #include "nsIHistoryEntry.h" #include "nsISHEntry.h" #include "nsNetUtil.h" #include "SaveHeaderSniffer.h" #import "CHBrowserWrapper.h" #import "CHBrowserView.h" #import "FindDlgController.h" #import "nsCocoaBrowserService.h" #import "nsCocoaBrowserListener.h" #import "mozView.h" typedef unsigned int DragReference; #include "nsIDragHelperService.h" // Cut/copy/paste #include "nsIClipboardCommands.h" #include "nsIInterfaceRequestorUtils.h" // Undo/redo #include "nsICommandManager.h" #include "nsICommandParams.h" const char kPersistContractID[] = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1"; const char kDirServiceContractID[] = "@mozilla.org/file/directory_service;1"; @implementation CHBrowserView - (id)initWithFrame:(NSRect)frame andWindow:(NSWindow*)aWindow { mWindow = aWindow; return [self initWithFrame:frame]; } - (id)initWithFrame:(NSRect)frame { if ( (self = [super initWithFrame:frame]) ) { nsresult rv = nsCocoaBrowserService::InitEmbedding(); if (NS_FAILED(rv)) { // XXX need to throw } _listener = new nsCocoaBrowserListener(self); NS_ADDREF(_listener); // Create the web browser instance nsCOMPtr browser = do_CreateInstance(NS_WEBBROWSER_CONTRACTID, &rv); if (NS_FAILED(rv)) { // XXX need to throw } _webBrowser = browser; NS_ADDREF(_webBrowser); // Set the container nsIWebBrowserChrome _webBrowser->SetContainerWindow(NS_STATIC_CAST(nsIWebBrowserChrome *, _listener)); // Register as a listener for web progress nsCOMPtr weak = do_GetWeakReference(NS_STATIC_CAST(nsIWebProgressListener*, _listener)); _webBrowser->AddWebBrowserListener(weak, NS_GET_IID(nsIWebProgressListener)); // Hook up the widget hierarchy with us as the parent nsCOMPtr baseWin = do_QueryInterface(_webBrowser); baseWin->InitWindow((NSView*)self, nsnull, 0, 0, frame.size.width, frame.size.height); baseWin->Create(); // register the view as a drop site for text, files, and urls. [self registerForDraggedTypes: [NSArray arrayWithObjects: @"MozURLType", NSStringPboardType, NSURLPboardType, NSFilenamesPboardType, nil]]; } return self; } - (void)destroyWebBrowser { nsCOMPtr baseWin = do_QueryInterface(_webBrowser); baseWin->Destroy(); } - (void)dealloc { NS_RELEASE(_listener); NS_IF_RELEASE(_webBrowser); nsCocoaBrowserService::BrowserClosed(); #if DEBUG NSLog(@"CHBrowserView died."); #endif [super dealloc]; } - (void)setFrame:(NSRect)frameRect { [super setFrame:frameRect]; if (_webBrowser) { nsCOMPtr window = do_QueryInterface(_webBrowser); window->SetSize((PRInt32)frameRect.size.width, (PRInt32)frameRect.size.height, PR_TRUE); } } - (void)addListener:(id )listener { _listener->AddListener(listener); } - (void)removeListener:(id )listener { _listener->RemoveListener(listener); } - (void)setContainer:(id )container { _listener->SetContainer(container); } - (nsIDOMWindow*)getContentWindow { nsIDOMWindow* window; _webBrowser->GetContentDOMWindow(&window); return window; } - (void)loadURI:(NSString *)urlSpec referrer:(NSString*)referrer flags:(unsigned int)flags { nsCOMPtr nav = do_QueryInterface(_webBrowser); int length = [urlSpec length]; PRUnichar* specStr = nsMemory::Alloc((length+1) * sizeof(PRUnichar)); [urlSpec getCharacters:specStr]; specStr[length] = PRUnichar(0); nsCOMPtr referrerURI; if ( referrer ) NS_NewURI(getter_AddRefs(referrerURI), [referrer UTF8String]); PRUint32 navFlags = nsIWebNavigation::LOAD_FLAGS_NONE; if (flags & NSLoadFlagsDontPutInHistory) { navFlags |= nsIWebNavigation::LOAD_FLAGS_BYPASS_HISTORY; } if (flags & NSLoadFlagsReplaceHistoryEntry) { navFlags |= nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY; } if (flags & NSLoadFlagsBypassCacheAndProxy) { navFlags |= nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE | nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY; } nsresult rv = nav->LoadURI(specStr, navFlags, referrerURI, nsnull, nsnull); if (NS_FAILED(rv)) { // XXX need to throw } nsMemory::Free(specStr); } - (void)reload:(unsigned int)flags { nsCOMPtr nav = do_QueryInterface(_webBrowser); PRUint32 navFlags = nsIWebNavigation::LOAD_FLAGS_NONE; if (flags & NSLoadFlagsBypassCacheAndProxy) { navFlags |= nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE | nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY; } nsresult rv = nav->Reload(navFlags); if (NS_FAILED(rv)) { // XXX need to throw } } - (BOOL)canGoBack { nsCOMPtr nav = do_QueryInterface(_webBrowser); PRBool can; nav->GetCanGoBack(&can); return can ? YES : NO; } - (BOOL)canGoForward { nsCOMPtr nav = do_QueryInterface(_webBrowser); PRBool can; nav->GetCanGoForward(&can); return can ? YES : NO; } - (void)goBack { nsCOMPtr nav = do_QueryInterface(_webBrowser); nsresult rv = nav->GoBack(); if (NS_FAILED(rv)) { // XXX need to throw } } - (void)goForward { nsCOMPtr nav = do_QueryInterface(_webBrowser); nsresult rv = nav->GoForward(); if (NS_FAILED(rv)) { // XXX need to throw } } - (void)gotoIndex:(int)index { nsCOMPtr nav = do_QueryInterface(_webBrowser); nsresult rv = nav->GotoIndex(index); if (NS_FAILED(rv)) { // XXX need to throw } } - (void)stop:(unsigned int)flags { nsCOMPtr nav = do_QueryInterface(_webBrowser); nsresult rv = nav->Stop(flags); if (NS_FAILED(rv)) { // XXX need to throw } } // XXXbryner This isn't used anywhere. how is it different from getCurrentURLSpec? - (NSString*)getCurrentURI { nsCOMPtr uri; nsCOMPtr nav = do_QueryInterface(_webBrowser); nav->GetCurrentURI(getter_AddRefs(uri)); if (!uri) { return nsnull; } nsCAutoString spec; uri->GetSpec(spec); const char* cstr = spec.get(); NSString* str = [NSString stringWithCString:cstr]; return str; } - (nsCocoaBrowserListener*)getCocoaBrowserListener { return _listener; } - (nsIWebBrowser*)getWebBrowser { NS_IF_ADDREF(_webBrowser); return _webBrowser; } - (void)setWebBrowser:(nsIWebBrowser*)browser { _webBrowser = browser; if (_webBrowser) { // Set the container nsIWebBrowserChrome _webBrowser->SetContainerWindow(NS_STATIC_CAST(nsIWebBrowserChrome *, _listener)); NSRect frame = [self frame]; // Hook up the widget hierarchy with us as the parent nsCOMPtr baseWin = do_QueryInterface(_webBrowser); baseWin->InitWindow((NSView*)self, nsnull, 0, 0, frame.size.width, frame.size.height); baseWin->Create(); } } -(void) saveInternal: (nsIURI*)aURI withDocument: (nsIDOMDocument*)aDocument suggestedFilename: (const char*)aFilename bypassCache: (BOOL)aBypassCache filterView: (NSView*)aFilterView filterList: (NSPopUpButton*)aFilterList { // Create our web browser persist object. This is the object that knows // how to actually perform the saving of the page (and of the images // on the page). nsCOMPtr webPersist(do_CreateInstance(kPersistContractID)); if (!webPersist) return; // Make a temporary file object that we can save to. nsCOMPtr dirService(do_GetService(kDirServiceContractID)); if (!dirService) return; nsCOMPtr tmpFile; dirService->Get("TmpD", NS_GET_IID(nsIFile), getter_AddRefs(tmpFile)); static short unsigned int tmpRandom = 0; nsAutoString tmpNo; tmpNo.AppendInt(tmpRandom++); nsAutoString saveFile(NS_LITERAL_STRING("-sav")); saveFile += tmpNo; saveFile += NS_LITERAL_STRING("tmp"); tmpFile->Append(saveFile); // Get the post data if we're an HTML doc. nsCOMPtr postData; if (aDocument) { nsCOMPtr webNav(do_QueryInterface(_webBrowser)); nsCOMPtr sessionHistory; webNav->GetSessionHistory(getter_AddRefs(sessionHistory)); nsCOMPtr entry; PRInt32 sindex; sessionHistory->GetIndex(&sindex); sessionHistory->GetEntryAtIndex(sindex, PR_FALSE, getter_AddRefs(entry)); nsCOMPtr shEntry(do_QueryInterface(entry)); if (shEntry) shEntry->GetPostData(getter_AddRefs(postData)); } // when saving, we first fire off a save with a nsHeaderSniffer as a progress // listener. This allows us to look for the content-disposition header, which // can supply a filename, and maybe has something to do with CGI-generated // content (?) nsCAutoString fileName(aFilename); nsHeaderSniffer* sniffer = new nsHeaderSniffer(webPersist, tmpFile, aURI, aDocument, postData, fileName, aBypassCache, aFilterView, aFilterList); if (!sniffer) return; webPersist->SetProgressListener(sniffer); // owned webPersist->SaveURI(aURI, nsnull, tmpFile); } -(void)printDocument { nsCOMPtr domWindow; _webBrowser->GetContentDOMWindow(getter_AddRefs(domWindow)); nsCOMPtr ir(do_QueryInterface(domWindow)); nsCOMPtr print; ir->GetInterface(NS_GET_IID(nsIWebBrowserPrint), getter_AddRefs(print)); print->Print(nsnull, nsnull); } - (BOOL)findInPageWithPattern:(NSString*)inText caseSensitive:(BOOL)inCaseSensitive wrap:(BOOL)inWrap backwards:(BOOL)inBackwards { PRBool found = PR_FALSE; nsCOMPtr wbf(do_QueryInterface(_webBrowser)); nsCOMPtr rootWindow; nsCOMPtr focusedWindow; _webBrowser->GetContentDOMWindow(getter_AddRefs(rootWindow)); wbf->GetFocusedWindow(getter_AddRefs(focusedWindow)); if (!focusedWindow) focusedWindow = rootWindow; nsCOMPtr webFind(do_GetInterface(_webBrowser)); if ( webFind ) { nsCOMPtr framesFind(do_QueryInterface(webFind)); framesFind->SetRootSearchFrame(rootWindow); framesFind->SetCurrentSearchFrame(focusedWindow); webFind->SetMatchCase(inCaseSensitive ? PR_TRUE : PR_FALSE); webFind->SetWrapFind(inWrap ? PR_TRUE : PR_FALSE); webFind->SetFindBackwards(inBackwards ? PR_TRUE : PR_FALSE); PRUnichar* text = (PRUnichar*)nsMemory::Alloc(([inText length]+1)*sizeof(PRUnichar)); if ( text ) { [inText getCharacters:text]; text[[inText length]] = 0; webFind->SetSearchString(text); webFind->FindNext(&found); nsMemory::Free(text); } } return found; } - (void)saveURL: (NSView*)aFilterView filterList: (NSPopUpButton*)aFilterList url: (NSString*)aURLSpec suggestedFilename: (NSString*)aFilename { nsCOMPtr url; nsresult rv = NS_NewURI(getter_AddRefs(url), [aURLSpec UTF8String]); if (NS_FAILED(rv)) return; [self saveInternal: url.get() withDocument: nsnull suggestedFilename: (([aFilename length] > 0) ? [aFilename fileSystemRepresentation] : "") bypassCache: YES filterView: aFilterView filterList: aFilterList]; } -(NSString*)getFocusedURLString { nsCOMPtr wbf(do_QueryInterface(_webBrowser)); nsCOMPtr domWindow; wbf->GetFocusedWindow(getter_AddRefs(domWindow)); if (!domWindow) _webBrowser->GetContentDOMWindow(getter_AddRefs(domWindow)); if (!domWindow) return @""; nsCOMPtr domDocument; domWindow->GetDocument(getter_AddRefs(domDocument)); if (!domDocument) return @""; nsCOMPtr nsDoc(do_QueryInterface(domDocument)); if (!nsDoc) return @""; nsCOMPtr location; nsDoc->GetLocation(getter_AddRefs(location)); if (!location) return @""; nsAutoString urlStr; location->GetHref(urlStr); return [NSString stringWith_nsAString: urlStr]; } - (void)saveDocument: (NSView*)aFilterView filterList: (NSPopUpButton*)aFilterList { nsCOMPtr wbf(do_QueryInterface(_webBrowser)); nsCOMPtr domWindow; wbf->GetFocusedWindow(getter_AddRefs(domWindow)); if (!domWindow) _webBrowser->GetContentDOMWindow(getter_AddRefs(domWindow)); if (!domWindow) return; nsCOMPtr domDocument; domWindow->GetDocument(getter_AddRefs(domDocument)); if (!domDocument) return; nsCOMPtr nsDoc(do_QueryInterface(domDocument)); if (!nsDoc) return; nsCOMPtr location; nsDoc->GetLocation(getter_AddRefs(location)); if (!location) return; nsAutoString urlStr; location->GetHref(urlStr); nsCAutoString urlCStr; urlCStr.AssignWithConversion(urlStr); nsCOMPtr url; nsresult rv = NS_NewURI(getter_AddRefs(url), urlCStr.get()); if (NS_FAILED(rv)) return; [self saveInternal: url.get() withDocument: domDocument suggestedFilename: "" bypassCache: NO filterView: aFilterView filterList: aFilterList]; } -(void)doCommand:(const char*)commandName { nsCOMPtr commandMgr(do_GetInterface(_webBrowser)); if (commandMgr) { nsCOMPtr commandParams = do_CreateInstance("@mozilla.org/embedcomp/command-params;1"); if (commandParams) { nsresult rv; nsAutoString commandNameStr; commandNameStr.AssignWithConversion(commandName); rv = commandParams->SetStringValue(NS_LITERAL_STRING("cmd_name"), commandNameStr); rv = commandMgr->DoCommand(commandParams); #if DEBUG if (NS_FAILED(rv)) NSLog(@"DoCommand failed"); #endif } else { #if DEBUG NSLog(@"Failed to make command params"); #endif } } else { #if DEBUG NSLog(@"No command manager"); #endif } } -(BOOL)isCommandEnabled:(const char*)commandName { PRBool isEnabled = PR_FALSE; nsCOMPtr commandMgr(do_GetInterface(_webBrowser)); if (commandMgr) { nsAutoString commandNameStr; commandNameStr.AssignWithConversion(commandName); nsresult rv = commandMgr->IsCommandEnabled(commandNameStr, &isEnabled); #if DEBUG if (NS_FAILED(rv)) NSLog(@"IsCommandEnabled failed"); #endif } else { #if DEBUG NSLog(@"No command manager"); #endif } return (isEnabled) ? YES : NO; } -(IBAction)cut:(id)aSender { nsCOMPtr clipboard(do_GetInterface(_webBrowser)); clipboard->CutSelection(); } -(BOOL)canCut { PRBool canCut = PR_FALSE; nsCOMPtr clipboard(do_GetInterface(_webBrowser)); clipboard->CanCutSelection(&canCut); return canCut; } -(IBAction)copy:(id)aSender { nsCOMPtr clipboard(do_GetInterface(_webBrowser)); clipboard->CopySelection(); } -(BOOL)canCopy { PRBool canCut = PR_FALSE; nsCOMPtr clipboard(do_GetInterface(_webBrowser)); clipboard->CanCopySelection(&canCut); return canCut; } -(IBAction)paste:(id)aSender { nsCOMPtr clipboard(do_GetInterface(_webBrowser)); clipboard->Paste(); } -(BOOL)canPaste { PRBool canCut = PR_FALSE; nsCOMPtr clipboard(do_GetInterface(_webBrowser)); clipboard->CanPaste(&canCut); return canCut; } -(IBAction)delete:(id)aSender { [self doCommand: "cmd_delete"]; } -(BOOL)canDelete { return [self isCommandEnabled: "cmd_delete"]; } -(IBAction)selectAll:(id)aSender { nsCOMPtr clipboard(do_GetInterface(_webBrowser)); clipboard->SelectAll(); } -(IBAction)undo:(id)aSender { [self doCommand: "cmd_undo"]; } -(IBAction)redo:(id)aSender { [self doCommand: "cmd_redo"]; } - (BOOL)canUndo { return [self isCommandEnabled: "cmd_undo"]; } - (BOOL)canRedo { return [self isCommandEnabled: "cmd_redo"]; } -(NSString*)getCurrentURLSpec { NSString* empty = @""; nsCOMPtr domWindow; _webBrowser->GetContentDOMWindow(getter_AddRefs(domWindow)); if (!domWindow) return empty; nsCOMPtr domDocument; domWindow->GetDocument(getter_AddRefs(domDocument)); if (!domDocument) return empty; nsCOMPtr nsDoc(do_QueryInterface(domDocument)); if (!nsDoc) return empty; nsCOMPtr location; nsDoc->GetLocation(getter_AddRefs(location)); if (!location) return empty; nsAutoString urlStr; location->GetHref(urlStr); return [NSString stringWith_nsAString: urlStr]; } - (void)setActive: (BOOL)aIsActive { nsCOMPtr wbf(do_QueryInterface(_webBrowser)); if (aIsActive) wbf->Activate(); else wbf->Deactivate(); } -(NSMenu*)getContextMenu { if ([[self superview] conformsToProtocol:@protocol(NSBrowserContainer)]) { id browserContainer = [self superview]; return [browserContainer getContextMenu]; } return nil; } -(NSWindow*)getNativeWindow { NSWindow* window = [self window]; if (window) return window; // We're visible. Just hand the window back. // We're invisible. It's likely that we're in a Cocoa tab view. // First see if we have a cached window. if (mWindow) return mWindow; // Finally, see if our parent responds to the getNativeWindow selector, // and if they do, let them handle it. if ([[self superview] conformsToProtocol:@protocol(NSBrowserContainer)]) { id browserContainer = [self superview]; return [browserContainer getNativeWindow]; } return nil; } // // -findEventSink:forPoint:inWindow: // // Given a point in window coordinates, find the Gecko event sink of the ChildView // the point is over. This involves first converting the point to this view's // coordinate system and using hitTest: to get the subview. Then we get // that view's widget and QI it to an event sink // - (void) findEventSink:(nsIEventSink**)outSink forPoint:(NSPoint)inPoint inWindow:(NSWindow*)inWind { NSPoint localPoint = [self convertPoint:inPoint fromView:[inWind contentView]]; NSView* hitView = [self hitTest:localPoint]; if ( [hitView conformsToProtocol:@protocol(mozView)] ) { nsCOMPtr sink (do_QueryInterface([hitView widget])); *outSink = sink.get(); NS_IF_ADDREF(*outSink); } } #pragma mark - - (BOOL)shouldAcceptDrag:(id )sender { if ([[self superview] conformsToProtocol:@protocol(NSBrowserContainer)]) { id browserContainer = [self superview]; return [browserContainer shouldAcceptDragFromSource:[sender draggingSource]]; } return YES; } - (unsigned int)draggingEntered:(id )sender { if (![self shouldAcceptDrag:sender]) return NSDragOperationNone; // NSLog(@"draggingEntered"); nsCOMPtr helper(do_GetService("@mozilla.org/widget/draghelperservice;1")); mDragHelper = helper.get(); NS_IF_ADDREF(mDragHelper); NS_ASSERTION ( mDragHelper, "Couldn't get a drag service, we're in big trouble" ); if ( mDragHelper ) { mLastTrackedLocation = [sender draggingLocation]; mLastTrackedWindow = [sender draggingDestinationWindow]; nsCOMPtr sink; [self findEventSink:getter_AddRefs(sink) forPoint:mLastTrackedLocation inWindow:mLastTrackedWindow]; if (sink) mDragHelper->Enter ( [sender draggingSequenceNumber], sink ); } return NSDragOperationCopy; } - (void)draggingExited:(id )sender { // NSLog(@"draggingExited"); if ( mDragHelper ) { nsCOMPtr sink; [self findEventSink:getter_AddRefs(sink) forPoint:mLastTrackedLocation /* [sender draggingLocation] */ inWindow:mLastTrackedWindow /* [sender draggingDestinationWindow] */ ]; if (sink) mDragHelper->Leave( [sender draggingSequenceNumber], sink ); NS_RELEASE(mDragHelper); } } - (unsigned int)draggingUpdated:(id )sender { if (![self shouldAcceptDrag:sender]) return NSDragOperationNone; // NSLog(@"draggingUpdated"); PRBool dropAllowed = PR_FALSE; if ( mDragHelper ) { mLastTrackedLocation = [sender draggingLocation]; mLastTrackedWindow = [sender draggingDestinationWindow]; nsCOMPtr sink; [self findEventSink:getter_AddRefs(sink) forPoint:mLastTrackedLocation inWindow:mLastTrackedWindow]; if (sink) mDragHelper->Tracking([sender draggingSequenceNumber], sink, &dropAllowed); } return dropAllowed ? NSDragOperationCopy : NSDragOperationNone; } - (BOOL)prepareForDragOperation:(id )sender { return YES; } - (BOOL)performDragOperation:(id )sender { if (![self shouldAcceptDrag:sender]) return NO; PRBool dragAccepted = PR_FALSE; if ( mDragHelper ) { nsCOMPtr sink; [self findEventSink:getter_AddRefs(sink) forPoint:[sender draggingLocation] inWindow:[sender draggingDestinationWindow]]; if (sink) mDragHelper->Drop([sender draggingSequenceNumber], sink, &dragAccepted); } return dragAccepted ? YES : NO; } #pragma mark - -(BOOL)validateMenuItem: (NSMenuItem*)aMenuItem { // update first responder items based on the selection SEL action = [aMenuItem action]; if (action == @selector(cut:)) return [self canCut]; else if (action == @selector(copy:)) return [self canCopy]; else if (action == @selector(paste:)) return [self canPaste]; else if (action == @selector(delete:)) return [self canDelete]; else if (action == @selector(undo:)) return [self canUndo]; else if (action == @selector(redo:)) return [self canRedo]; else if (action == @selector(selectAll:)) return YES; return NO; } @end