Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(87)

Side by Side Diff: chrome/browser/ui/cocoa/cookies_window_controller.mm

Issue 6339002: [Mac] Consolidate all files relating to preferences in a subdir of c/b/ui/coc... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/browser/ui/cocoa/cookies_window_controller.h"
6
7 #include <queue>
8 #include <vector>
9
10 #include "app/l10n_util_mac.h"
11 #include "app/resource_bundle.h"
12 #import "base/mac/mac_util.h"
13 #include "base/sys_string_conversions.h"
14 #include "chrome/browser/browsing_data_remover.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/cocoa/clear_browsing_data_controller.h"
17 #include "chrome/browser/ui/cocoa/cookie_details_view_controller.h"
18 #include "grit/generated_resources.h"
19 #include "grit/theme_resources.h"
20 #include "skia/ext/skia_utils_mac.h"
21 #include "third_party/apple/ImageAndTextCell.h"
22 #include "third_party/skia/include/core/SkBitmap.h"
23
24 // Key path used for notifying KVO.
25 static NSString* const kCocoaTreeModel = @"cocoaTreeModel";
26
27 CookiesTreeModelObserverBridge::CookiesTreeModelObserverBridge(
28 CookiesWindowController* controller)
29 : window_controller_(controller),
30 batch_update_(false) {
31 }
32
33 // Notification that nodes were added to the specified parent.
34 void CookiesTreeModelObserverBridge::TreeNodesAdded(ui::TreeModel* model,
35 ui::TreeModelNode* parent,
36 int start,
37 int count) {
38 // We're in for a major rebuild. Ignore this request.
39 if (batch_update_ || !HasCocoaModel())
40 return;
41
42 CocoaCookieTreeNode* cocoa_parent = FindCocoaNode(parent, nil);
43 NSMutableArray* cocoa_children = [cocoa_parent mutableChildren];
44
45 [window_controller_ willChangeValueForKey:kCocoaTreeModel];
46 CookieTreeNode* cookie_parent = static_cast<CookieTreeNode*>(parent);
47 for (int i = 0; i < count; ++i) {
48 CookieTreeNode* cookie_child = cookie_parent->GetChild(start + i);
49 CocoaCookieTreeNode* new_child = CocoaNodeFromTreeNode(cookie_child);
50 [cocoa_children addObject:new_child];
51 }
52 [window_controller_ didChangeValueForKey:kCocoaTreeModel];
53 }
54
55 // Notification that nodes were removed from the specified parent.
56 void CookiesTreeModelObserverBridge::TreeNodesRemoved(ui::TreeModel* model,
57 ui::TreeModelNode* parent,
58 int start,
59 int count) {
60 // We're in for a major rebuild. Ignore this request.
61 if (batch_update_ || !HasCocoaModel())
62 return;
63
64 CocoaCookieTreeNode* cocoa_parent = FindCocoaNode(parent, nil);
65 [window_controller_ willChangeValueForKey:kCocoaTreeModel];
66 NSMutableArray* cocoa_children = [cocoa_parent mutableChildren];
67 for (int i = start + count - 1; i >= start; --i) {
68 [cocoa_children removeObjectAtIndex:i];
69 }
70 [window_controller_ didChangeValueForKey:kCocoaTreeModel];
71 }
72
73 // Notification that the contents of a node has changed.
74 void CookiesTreeModelObserverBridge::TreeNodeChanged(ui::TreeModel* model,
75 ui::TreeModelNode* node) {
76 // If we don't have a Cocoa model, only let the root node change.
77 if (batch_update_ || (!HasCocoaModel() && model->GetRoot() != node))
78 return;
79
80 if (HasCocoaModel()) {
81 // We still have a Cocoa model, so just rebuild the node.
82 [window_controller_ willChangeValueForKey:kCocoaTreeModel];
83 CocoaCookieTreeNode* changed_node = FindCocoaNode(node, nil);
84 [changed_node rebuild];
85 [window_controller_ didChangeValueForKey:kCocoaTreeModel];
86 } else {
87 // Full rebuild.
88 [window_controller_ setCocoaTreeModel:CocoaNodeFromTreeNode(node)];
89 }
90 }
91
92 void CookiesTreeModelObserverBridge::TreeModelBeginBatch(
93 CookiesTreeModel* model) {
94 batch_update_ = true;
95 }
96
97 void CookiesTreeModelObserverBridge::TreeModelEndBatch(
98 CookiesTreeModel* model) {
99 DCHECK(batch_update_);
100 CocoaCookieTreeNode* root = CocoaNodeFromTreeNode(model->GetRoot());
101 [window_controller_ setCocoaTreeModel:root];
102 batch_update_ = false;
103 }
104
105 void CookiesTreeModelObserverBridge::InvalidateCocoaModel() {
106 [[[window_controller_ cocoaTreeModel] mutableChildren] removeAllObjects];
107 }
108
109 CocoaCookieTreeNode* CookiesTreeModelObserverBridge::CocoaNodeFromTreeNode(
110 ui::TreeModelNode* node) {
111 CookieTreeNode* cookie_node = static_cast<CookieTreeNode*>(node);
112 return [[[CocoaCookieTreeNode alloc] initWithNode:cookie_node] autorelease];
113 }
114
115 // Does breadth-first search on the tree to find |node|. This method is most
116 // commonly used to find origin/folder nodes, which are at the first level off
117 // the root (hence breadth-first search).
118 CocoaCookieTreeNode* CookiesTreeModelObserverBridge::FindCocoaNode(
119 ui::TreeModelNode* target, CocoaCookieTreeNode* start) {
120 if (!start) {
121 start = [window_controller_ cocoaTreeModel];
122 }
123 if ([start treeNode] == target) {
124 return start;
125 }
126
127 // Enqueue the root node of the search (sub-)tree.
128 std::queue<CocoaCookieTreeNode*> horizon;
129 horizon.push(start);
130
131 // Loop until we've looked at every node or we found the target.
132 while (!horizon.empty()) {
133 // Dequeue the item at the front.
134 CocoaCookieTreeNode* node = horizon.front();
135 horizon.pop();
136
137 // If this is the droid we're looking for, report it.
138 if ([node treeNode] == target)
139 return node;
140
141 // "Move along, move along." by adding all child nodes to the queue.
142 if (![node isLeaf]) {
143 NSArray* children = [node children];
144 for (CocoaCookieTreeNode* child in children) {
145 horizon.push(child);
146 }
147 }
148 }
149
150 return nil; // We couldn't find the node.
151 }
152
153 // Returns whether or not the Cocoa tree model is built.
154 bool CookiesTreeModelObserverBridge::HasCocoaModel() {
155 return ([[[window_controller_ cocoaTreeModel] children] count] > 0U);
156 }
157
158 #pragma mark Window Controller
159
160 @implementation CookiesWindowController
161
162 @synthesize removeButtonEnabled = removeButtonEnabled_;
163 @synthesize treeController = treeController_;
164
165 - (id)initWithProfile:(Profile*)profile
166 databaseHelper:(BrowsingDataDatabaseHelper*)databaseHelper
167 storageHelper:(BrowsingDataLocalStorageHelper*)storageHelper
168 appcacheHelper:(BrowsingDataAppCacheHelper*)appcacheHelper
169 indexedDBHelper:(BrowsingDataIndexedDBHelper*)indexedDBHelper {
170 DCHECK(profile);
171 NSString* nibpath = [base::mac::MainAppBundle() pathForResource:@"Cookies"
172 ofType:@"nib"];
173 if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
174 profile_ = profile;
175 databaseHelper_ = databaseHelper;
176 storageHelper_ = storageHelper;
177 appcacheHelper_ = appcacheHelper;
178 indexedDBHelper_ = indexedDBHelper;
179
180 [self loadTreeModelFromProfile];
181
182 // Register for Clear Browsing Data controller so we update appropriately.
183 ClearBrowsingDataController* clearingController =
184 [ClearBrowsingDataController controllerForProfile:profile_];
185 if (clearingController) {
186 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
187 [center addObserver:self
188 selector:@selector(clearBrowsingDataNotification:)
189 name:kClearBrowsingDataControllerDidDelete
190 object:clearingController];
191 }
192 }
193 return self;
194 }
195
196 - (void)dealloc {
197 [[NSNotificationCenter defaultCenter] removeObserver:self];
198 [super dealloc];
199 }
200
201 - (void)awakeFromNib {
202 DCHECK([self window]);
203 DCHECK_EQ(self, [[self window] delegate]);
204
205 detailsViewController_.reset([[CookieDetailsViewController alloc] init]);
206
207 NSView* detailView = [detailsViewController_.get() view];
208 NSRect viewFrameRect = [cookieDetailsViewPlaceholder_ frame];
209 [[detailsViewController_.get() view] setFrame:viewFrameRect];
210 [[cookieDetailsViewPlaceholder_ superview]
211 replaceSubview:cookieDetailsViewPlaceholder_
212 with:detailView];
213
214 [detailsViewController_ configureBindingsForTreeController:treeController_];
215 }
216
217 - (void)windowWillClose:(NSNotification*)notif {
218 [searchField_ setTarget:nil];
219 [outlineView_ setDelegate:nil];
220 [self autorelease];
221 }
222
223 - (void)attachSheetTo:(NSWindow*)window {
224 [NSApp beginSheet:[self window]
225 modalForWindow:window
226 modalDelegate:self
227 didEndSelector:@selector(sheetEndSheet:returnCode:contextInfo:)
228 contextInfo:nil];
229 }
230
231 - (void)sheetEndSheet:(NSWindow*)sheet
232 returnCode:(NSInteger)returnCode
233 contextInfo:(void*)context {
234 [sheet close];
235 [sheet orderOut:self];
236 }
237
238 - (IBAction)updateFilter:(id)sender {
239 DCHECK([sender isKindOfClass:[NSSearchField class]]);
240 NSString* string = [sender stringValue];
241 // Invalidate the model here because all the nodes are going to be removed
242 // in UpdateSearchResults(). This could lead to there temporarily being
243 // invalid pointers in the Cocoa model.
244 modelObserver_->InvalidateCocoaModel();
245 treeModel_->UpdateSearchResults(base::SysNSStringToWide(string));
246 }
247
248 - (IBAction)deleteCookie:(id)sender {
249 DCHECK_EQ(1U, [[treeController_ selectedObjects] count]);
250 [self deleteNodeAtIndexPath:[treeController_ selectionIndexPath]];
251 }
252
253 // This will delete the Cocoa model node as well as the backing model object at
254 // the specified index path in the Cocoa model. If the node that was deleted
255 // was the sole child of the parent node, this will be called recursively to
256 // delete empty parents.
257 - (void)deleteNodeAtIndexPath:(NSIndexPath*)path {
258 NSTreeNode* treeNode =
259 [[treeController_ arrangedObjects] descendantNodeAtIndexPath:path];
260 if (!treeNode)
261 return;
262
263 CocoaCookieTreeNode* node = [treeNode representedObject];
264 CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
265 treeModel_->DeleteCookieNode(cookie);
266 // If there is a next cookie, this will select it because items will slide
267 // up. If there is no next cookie, this is a no-op.
268 [treeController_ setSelectionIndexPath:path];
269 // If the above setting of the selection was in fact a no-op, find the next
270 // node to select.
271 if (![[treeController_ selectedObjects] count]) {
272 NSUInteger lastIndex = [path indexAtPosition:[path length] - 1];
273 if (lastIndex != 0) {
274 // If there any nodes remaining, select the node that is in the list
275 // before this one.
276 path = [path indexPathByRemovingLastIndex];
277 path = [path indexPathByAddingIndex:lastIndex - 1];
278 [treeController_ setSelectionIndexPath:path];
279 }
280 }
281 }
282
283 - (IBAction)deleteAllCookies:(id)sender {
284 // Preemptively delete all cookies in the Cocoa model.
285 modelObserver_->InvalidateCocoaModel();
286 treeModel_->DeleteAllStoredObjects();
287 }
288
289 - (IBAction)closeSheet:(id)sender {
290 [NSApp endSheet:[self window]];
291 }
292
293 - (void)clearBrowsingDataNotification:(NSNotification*)notif {
294 NSNumber* removeMask =
295 [[notif userInfo] objectForKey:kClearBrowsingDataControllerRemoveMask];
296 if ([removeMask intValue] & BrowsingDataRemover::REMOVE_COOKIES) {
297 [self loadTreeModelFromProfile];
298 }
299 }
300
301 // Override keyDown on the controller (which is the first responder) to allow
302 // both backspace and delete to be captured by the Remove button.
303 - (void)keyDown:(NSEvent*)theEvent {
304 NSString* keys = [theEvent characters];
305 if ([keys length]) {
306 unichar key = [keys characterAtIndex:0];
307 // The button has a key equivalent of backspace, so examine this event for
308 // forward delete.
309 if ((key == NSDeleteCharacter || key == NSDeleteFunctionKey) &&
310 [self removeButtonEnabled]) {
311 [removeButton_ performClick:self];
312 return;
313 }
314 }
315 [super keyDown:theEvent];
316 }
317
318 #pragma mark Getters and Setters
319
320 - (CocoaCookieTreeNode*)cocoaTreeModel {
321 return cocoaTreeModel_.get();
322 }
323 - (void)setCocoaTreeModel:(CocoaCookieTreeNode*)model {
324 cocoaTreeModel_.reset([model retain]);
325 }
326
327 - (CookiesTreeModel*)treeModel {
328 return treeModel_.get();
329 }
330
331 #pragma mark Outline View Delegate
332
333 - (void)outlineView:(NSOutlineView*)outlineView
334 willDisplayCell:(id)cell
335 forTableColumn:(NSTableColumn*)tableColumn
336 item:(id)item {
337 CocoaCookieTreeNode* node = [item representedObject];
338 int index = treeModel_->GetIconIndex([node treeNode]);
339 NSImage* icon = nil;
340 if (index >= 0)
341 icon = [icons_ objectAtIndex:index];
342 else
343 icon = [icons_ lastObject];
344 [(ImageAndTextCell*)cell setImage:icon];
345 }
346
347 - (void)outlineViewItemDidExpand:(NSNotification*)notif {
348 NSTreeNode* item = [[notif userInfo] objectForKey:@"NSObject"];
349 CocoaCookieTreeNode* node = [item representedObject];
350 NSArray* children = [node children];
351 if ([children count] == 1U) {
352 // The node that will expand has one child. Do the user a favor and expand
353 // that node (saving her a click) if it is non-leaf.
354 CocoaCookieTreeNode* child = [children lastObject];
355 if (![child isLeaf]) {
356 NSOutlineView* outlineView = [notif object];
357 // Tell the OutlineView to expand the NSTreeNode, not the model object.
358 children = [item childNodes];
359 DCHECK_EQ([children count], 1U);
360 [outlineView expandItem:[children lastObject]];
361 // Select the first node in that child set.
362 NSTreeNode* folderChild = [children lastObject];
363 if ([[folderChild childNodes] count] > 0) {
364 NSTreeNode* firstCookieChild =
365 [[folderChild childNodes] objectAtIndex:0];
366 [treeController_ setSelectionIndexPath:[firstCookieChild indexPath]];
367 }
368 }
369 }
370 }
371
372 - (void)outlineViewSelectionDidChange:(NSNotification*)notif {
373 // Multi-selection should be disabled in the UI, but for sanity, double-check
374 // that they can't do it here.
375 NSArray* selectedObjects = [treeController_ selectedObjects];
376 NSUInteger count = [selectedObjects count];
377 if (count != 1U) {
378 DCHECK_LT(count, 1U) << "User was able to select more than 1 cookie node!";
379 [self setRemoveButtonEnabled:NO];
380 return;
381 }
382
383 // Go through the selection's indexPath and make sure that the node that is
384 // being referenced actually exists in the Cocoa model.
385 NSIndexPath* selection = [treeController_ selectionIndexPath];
386 NSUInteger length = [selection length];
387 CocoaCookieTreeNode* node = [self cocoaTreeModel];
388 for (NSUInteger i = 0; i < length; ++i) {
389 NSUInteger childIndex = [selection indexAtPosition:i];
390 if (childIndex >= [[node children] count]) {
391 [self setRemoveButtonEnabled:NO];
392 return;
393 }
394 node = [[node children] objectAtIndex:childIndex];
395 }
396
397 // If there is a valid selection, make sure that the remove
398 // button is enabled.
399 [self setRemoveButtonEnabled:YES];
400 }
401
402 #pragma mark Unit Testing
403
404 - (CookiesTreeModelObserverBridge*)modelObserver {
405 return modelObserver_.get();
406 }
407
408 - (NSArray*)icons {
409 return icons_.get();
410 }
411
412 // Re-initializes the |treeModel_|, creates a new observer for it, and re-
413 // builds the |cocoaTreeModel_|. We use this to initialize the controller and
414 // to rebuild after the user clears browsing data. Because the models get
415 // clobbered, we rebuild the icon cache for safety (though they do not change).
416 - (void)loadTreeModelFromProfile {
417 treeModel_.reset(new CookiesTreeModel(
418 profile_->GetRequestContext()->GetCookieStore()->GetCookieMonster(),
419 databaseHelper_,
420 storageHelper_,
421 NULL,
422 appcacheHelper_,
423 indexedDBHelper_));
424 modelObserver_.reset(new CookiesTreeModelObserverBridge(self));
425 treeModel_->AddObserver(modelObserver_.get());
426
427 // Convert the model's icons from Skia to Cocoa.
428 std::vector<SkBitmap> skiaIcons;
429 treeModel_->GetIcons(&skiaIcons);
430 icons_.reset([[NSMutableArray alloc] init]);
431 for (std::vector<SkBitmap>::iterator it = skiaIcons.begin();
432 it != skiaIcons.end(); ++it) {
433 [icons_ addObject:gfx::SkBitmapToNSImage(*it)];
434 }
435
436 // Default icon will be the last item in the array.
437 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
438 // TODO(rsesek): Rename this resource now that it's in multiple places.
439 [icons_ addObject:rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER)];
440
441 // Create the Cocoa model.
442 CookieTreeNode* root = static_cast<CookieTreeNode*>(treeModel_->GetRoot());
443 scoped_nsobject<CocoaCookieTreeNode> model(
444 [[CocoaCookieTreeNode alloc] initWithNode:root]);
445 [self setCocoaTreeModel:model.get()]; // Takes ownership.
446 }
447
448 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698