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

Side by Side Diff: chrome/browser/ui/cocoa/content_exceptions_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/content_exceptions_window_controller.h"
6
7 #include "app/l10n_util.h"
8 #include "app/l10n_util_mac.h"
9 #include "base/command_line.h"
10 #import "base/mac/mac_util.h"
11 #import "base/scoped_nsobject.h"
12 #include "base/sys_string_conversions.h"
13 #include "chrome/browser/content_exceptions_table_model.h"
14 #include "chrome/browser/content_setting_combo_model.h"
15 #include "chrome/common/notification_registrar.h"
16 #include "chrome/common/notification_service.h"
17 #include "grit/generated_resources.h"
18 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
19 #include "ui/base/models/table_model_observer.h"
20
21 @interface ContentExceptionsWindowController (Private)
22 - (id)initWithType:(ContentSettingsType)settingsType
23 settingsMap:(HostContentSettingsMap*)settingsMap
24 otrSettingsMap:(HostContentSettingsMap*)otrSettingsMap;
25 - (void)updateRow:(NSInteger)row
26 withEntry:(const HostContentSettingsMap::PatternSettingPair&)entry
27 forOtr:(BOOL)isOtr;
28 - (void)adjustEditingButtons;
29 - (void)modelDidChange;
30 - (NSString*)titleForIndex:(size_t)index;
31 @end
32
33 ////////////////////////////////////////////////////////////////////////////////
34 // PatternFormatter
35
36 // A simple formatter that accepts text that vaguely looks like a pattern.
37 @interface PatternFormatter : NSFormatter
38 @end
39
40 @implementation PatternFormatter
41 - (NSString*)stringForObjectValue:(id)object {
42 if (![object isKindOfClass:[NSString class]])
43 return nil;
44 return object;
45 }
46
47 - (BOOL)getObjectValue:(id*)object
48 forString:(NSString*)string
49 errorDescription:(NSString**)error {
50 if ([string length]) {
51 if (ContentSettingsPattern(
52 base::SysNSStringToUTF8(string)).IsValid()) {
53 *object = string;
54 return YES;
55 }
56 }
57 if (error)
58 *error = @"Invalid pattern";
59 return NO;
60 }
61
62 - (NSAttributedString*)attributedStringForObjectValue:(id)object
63 withDefaultAttributes:(NSDictionary*)attribs {
64 return nil;
65 }
66 @end
67
68 ////////////////////////////////////////////////////////////////////////////////
69 // UpdatingContentSettingsObserver
70
71 // UpdatingContentSettingsObserver is a notification observer that tells a
72 // window controller to update its data on every notification.
73 class UpdatingContentSettingsObserver : public NotificationObserver {
74 public:
75 UpdatingContentSettingsObserver(ContentExceptionsWindowController* controller)
76 : controller_(controller) {
77 // One would think one could register a TableModelObserver to be notified of
78 // changes to ContentExceptionsTableModel. One would be wrong: The table
79 // model only sends out changes that are made through the model, not for
80 // changes made directly to its backing HostContentSettings object (that
81 // happens e.g. if the user uses the cookie confirmation dialog). Hence,
82 // observe the CONTENT_SETTINGS_CHANGED notification directly.
83 registrar_.Add(this, NotificationType::CONTENT_SETTINGS_CHANGED,
84 NotificationService::AllSources());
85 }
86 virtual void Observe(NotificationType type,
87 const NotificationSource& source,
88 const NotificationDetails& details);
89 private:
90 NotificationRegistrar registrar_;
91 ContentExceptionsWindowController* controller_;
92 };
93
94 void UpdatingContentSettingsObserver::Observe(
95 NotificationType type,
96 const NotificationSource& source,
97 const NotificationDetails& details) {
98 [controller_ modelDidChange];
99 }
100
101 ////////////////////////////////////////////////////////////////////////////////
102 // Static functions
103
104 namespace {
105
106 NSString* GetWindowTitle(ContentSettingsType settingsType) {
107 switch (settingsType) {
108 case CONTENT_SETTINGS_TYPE_COOKIES:
109 return l10n_util::GetNSStringWithFixup(IDS_COOKIE_EXCEPTION_TITLE);
110 case CONTENT_SETTINGS_TYPE_IMAGES:
111 return l10n_util::GetNSStringWithFixup(IDS_IMAGES_EXCEPTION_TITLE);
112 case CONTENT_SETTINGS_TYPE_JAVASCRIPT:
113 return l10n_util::GetNSStringWithFixup(IDS_JS_EXCEPTION_TITLE);
114 case CONTENT_SETTINGS_TYPE_PLUGINS:
115 return l10n_util::GetNSStringWithFixup(IDS_PLUGINS_EXCEPTION_TITLE);
116 case CONTENT_SETTINGS_TYPE_POPUPS:
117 return l10n_util::GetNSStringWithFixup(IDS_POPUP_EXCEPTION_TITLE);
118 default:
119 NOTREACHED();
120 }
121 return @"";
122 }
123
124 const CGFloat kButtonBarHeight = 35.0;
125
126 } // namespace
127
128 ////////////////////////////////////////////////////////////////////////////////
129 // ContentExceptionsWindowController implementation
130
131 static ContentExceptionsWindowController*
132 g_exceptionWindows[CONTENT_SETTINGS_NUM_TYPES] = { nil };
133
134 @implementation ContentExceptionsWindowController
135
136 + (id)controllerForType:(ContentSettingsType)settingsType
137 settingsMap:(HostContentSettingsMap*)settingsMap
138 otrSettingsMap:(HostContentSettingsMap*)otrSettingsMap {
139 if (!g_exceptionWindows[settingsType]) {
140 g_exceptionWindows[settingsType] =
141 [[ContentExceptionsWindowController alloc]
142 initWithType:settingsType
143 settingsMap:settingsMap
144 otrSettingsMap:otrSettingsMap];
145 }
146 return g_exceptionWindows[settingsType];
147 }
148
149 - (id)initWithType:(ContentSettingsType)settingsType
150 settingsMap:(HostContentSettingsMap*)settingsMap
151 otrSettingsMap:(HostContentSettingsMap*)otrSettingsMap {
152 NSString* nibpath =
153 [base::mac::MainAppBundle() pathForResource:@"ContentExceptionsWindow"
154 ofType:@"nib"];
155 if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
156 settingsType_ = settingsType;
157 settingsMap_ = settingsMap;
158 otrSettingsMap_ = otrSettingsMap;
159 model_.reset(new ContentExceptionsTableModel(
160 settingsMap_, otrSettingsMap_, settingsType_));
161 popup_model_.reset(new ContentSettingComboModel(settingsType_));
162 otrAllowed_ = otrSettingsMap != NULL;
163 tableObserver_.reset(new UpdatingContentSettingsObserver(self));
164 updatesEnabled_ = YES;
165
166 // TODO(thakis): autoremember window rect.
167 // TODO(thakis): sorting support.
168 }
169 return self;
170 }
171
172 - (void)awakeFromNib {
173 DCHECK([self window]);
174 DCHECK_EQ(self, [[self window] delegate]);
175 DCHECK(tableView_);
176 DCHECK_EQ(self, [tableView_ dataSource]);
177 DCHECK_EQ(self, [tableView_ delegate]);
178
179 [[self window] setTitle:GetWindowTitle(settingsType_)];
180
181 CGFloat minWidth = [[addButton_ superview] bounds].size.width +
182 [[doneButton_ superview] bounds].size.width;
183 [self setMinWidth:minWidth];
184
185 [self adjustEditingButtons];
186
187 // Initialize menu for the data cell in the "action" column.
188 scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"exceptionMenu"]);
189 for (int i = 0; i < popup_model_->GetItemCount(); ++i) {
190 NSString* title =
191 l10n_util::FixUpWindowsStyleLabel(popup_model_->GetItemAt(i));
192 scoped_nsobject<NSMenuItem> allowItem(
193 [[NSMenuItem alloc] initWithTitle:title action:NULL keyEquivalent:@""]);
194 [allowItem.get() setTag:popup_model_->SettingForIndex(i)];
195 [menu.get() addItem:allowItem.get()];
196 }
197 NSCell* menuCell =
198 [[tableView_ tableColumnWithIdentifier:@"action"] dataCell];
199 [menuCell setMenu:menu.get()];
200
201 NSCell* patternCell =
202 [[tableView_ tableColumnWithIdentifier:@"pattern"] dataCell];
203 [patternCell setFormatter:[[[PatternFormatter alloc] init] autorelease]];
204
205 if (!otrAllowed_) {
206 [tableView_
207 removeTableColumn:[tableView_ tableColumnWithIdentifier:@"otr"]];
208 }
209 }
210
211 - (void)setMinWidth:(CGFloat)minWidth {
212 NSWindow* window = [self window];
213 [window setMinSize:NSMakeSize(minWidth, [window minSize].height)];
214 if ([window frame].size.width < minWidth) {
215 NSRect frame = [window frame];
216 frame.size.width = minWidth;
217 [window setFrame:frame display:NO];
218 }
219 }
220
221 - (void)windowWillClose:(NSNotification*)notification {
222 // Without this, some of the unit tests fail on 10.6:
223 [tableView_ setDataSource:nil];
224
225 g_exceptionWindows[settingsType_] = nil;
226 [self autorelease];
227 }
228
229 - (BOOL)editingNewException {
230 return newException_.get() != NULL;
231 }
232
233 // Let esc cancel editing if the user is currently editing a pattern. Else, let
234 // esc close the window.
235 - (void)cancel:(id)sender {
236 if ([tableView_ currentEditor] != nil) {
237 [tableView_ abortEditing];
238 [[self window] makeFirstResponder:tableView_]; // Re-gain focus.
239
240 if ([tableView_ selectedRow] == model_->RowCount()) {
241 // Cancel addition of new row.
242 [self removeException:self];
243 }
244 } else {
245 [self closeSheet:self];
246 }
247 }
248
249 - (void)keyDown:(NSEvent*)event {
250 NSString* chars = [event charactersIgnoringModifiers];
251 if ([chars length] == 1) {
252 switch ([chars characterAtIndex:0]) {
253 case NSDeleteCharacter:
254 case NSDeleteFunctionKey:
255 // Delete deletes.
256 if ([[tableView_ selectedRowIndexes] count] > 0)
257 [self removeException:self];
258 return;
259 case NSCarriageReturnCharacter:
260 case NSEnterCharacter:
261 // Return enters rename mode.
262 if ([[tableView_ selectedRowIndexes] count] == 1) {
263 [tableView_ editColumn:0
264 row:[[tableView_ selectedRowIndexes] lastIndex]
265 withEvent:nil
266 select:YES];
267 }
268 return;
269 }
270 }
271 [super keyDown:event];
272 }
273
274 - (void)attachSheetTo:(NSWindow*)window {
275 [NSApp beginSheet:[self window]
276 modalForWindow:window
277 modalDelegate:self
278 didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
279 contextInfo:nil];
280 }
281
282 - (void)sheetDidEnd:(NSWindow*)sheet
283 returnCode:(NSInteger)returnCode
284 contextInfo:(void*)context {
285 [sheet close];
286 [sheet orderOut:self];
287 }
288
289 - (IBAction)addException:(id)sender {
290 if (newException_.get()) {
291 // The invariant is that |newException_| is non-NULL exactly if the pattern
292 // of a new exception is currently being edited - so there's nothing to do
293 // in that case.
294 return;
295 }
296 newException_.reset(new HostContentSettingsMap::PatternSettingPair);
297 newException_->first = ContentSettingsPattern(
298 l10n_util::GetStringUTF8(IDS_EXCEPTIONS_SAMPLE_PATTERN));
299 newException_->second = CONTENT_SETTING_BLOCK;
300 [tableView_ reloadData];
301
302 [self adjustEditingButtons];
303 int index = model_->RowCount();
304 NSIndexSet* selectedSet = [NSIndexSet indexSetWithIndex:index];
305 [tableView_ selectRowIndexes:selectedSet byExtendingSelection:NO];
306 [tableView_ editColumn:0 row:index withEvent:nil select:YES];
307 }
308
309 - (IBAction)removeException:(id)sender {
310 updatesEnabled_ = NO;
311 NSIndexSet* selection = [tableView_ selectedRowIndexes];
312 [tableView_ deselectAll:self]; // Else we'll get a -setObjectValue: later.
313 DCHECK_GT([selection count], 0U);
314 NSUInteger index = [selection lastIndex];
315 while (index != NSNotFound) {
316 if (index == static_cast<NSUInteger>(model_->RowCount()))
317 newException_.reset();
318 else
319 model_->RemoveException(index);
320 index = [selection indexLessThanIndex:index];
321 }
322 updatesEnabled_ = YES;
323 [self modelDidChange];
324 }
325
326 - (IBAction)removeAllExceptions:(id)sender {
327 updatesEnabled_ = NO;
328 [tableView_ deselectAll:self]; // Else we'll get a -setObjectValue: later.
329 newException_.reset();
330 model_->RemoveAll();
331 updatesEnabled_ = YES;
332 [self modelDidChange];
333 }
334
335 - (IBAction)closeSheet:(id)sender {
336 [NSApp endSheet:[self window]];
337 }
338
339 // Table View Data Source -----------------------------------------------------
340
341 - (NSInteger)numberOfRowsInTableView:(NSTableView*)table {
342 return model_->RowCount() + (newException_.get() ? 1 : 0);
343 }
344
345 - (id)tableView:(NSTableView*)tv
346 objectValueForTableColumn:(NSTableColumn*)tableColumn
347 row:(NSInteger)row {
348 const HostContentSettingsMap::PatternSettingPair* entry;
349 int isOtr;
350 if (newException_.get() && row >= model_->RowCount()) {
351 entry = newException_.get();
352 isOtr = 0;
353 } else {
354 entry = &model_->entry_at(row);
355 isOtr = model_->entry_is_off_the_record(row) ? 1 : 0;
356 }
357
358 NSObject* result = nil;
359 NSString* identifier = [tableColumn identifier];
360 if ([identifier isEqualToString:@"pattern"]) {
361 result = base::SysUTF8ToNSString(entry->first.AsString());
362 } else if ([identifier isEqualToString:@"action"]) {
363 result =
364 [NSNumber numberWithInt:popup_model_->IndexForSetting(entry->second)];
365 } else if ([identifier isEqualToString:@"otr"]) {
366 result = [NSNumber numberWithInt:isOtr];
367 } else {
368 NOTREACHED();
369 }
370 return result;
371 }
372
373 // Updates exception at |row| to contain the data in |entry|.
374 - (void)updateRow:(NSInteger)row
375 withEntry:(const HostContentSettingsMap::PatternSettingPair&)entry
376 forOtr:(BOOL)isOtr {
377 // TODO(thakis): This apparently moves an edited row to the back of the list.
378 // It's what windows and linux do, but it's kinda sucky. Fix.
379 // http://crbug.com/36904
380 updatesEnabled_ = NO;
381 if (row < model_->RowCount())
382 model_->RemoveException(row);
383 model_->AddException(entry.first, entry.second, isOtr);
384 updatesEnabled_ = YES;
385 [self modelDidChange];
386
387 // For now, at least re-select the edited element.
388 int newIndex = model_->IndexOfExceptionByPattern(entry.first, isOtr);
389 DCHECK(newIndex != -1);
390 [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex]
391 byExtendingSelection:NO];
392 }
393
394 - (void) tableView:(NSTableView*)tv
395 setObjectValue:(id)object
396 forTableColumn:(NSTableColumn*)tableColumn
397 row:(NSInteger)row {
398 // -remove: and -removeAll: both call |tableView_|'s -deselectAll:, which
399 // calls this method if a cell is currently being edited. Do not commit edits
400 // of rows that are about to be deleted.
401 if (!updatesEnabled_) {
402 // If this method gets called, the pattern filed of the new exception can no
403 // longer be being edited. Reset |newException_| to keep the invariant true.
404 newException_.reset();
405 return;
406 }
407
408 // Get model object.
409 bool isNewRow = newException_.get() && row >= model_->RowCount();
410 HostContentSettingsMap::PatternSettingPair originalEntry =
411 isNewRow ? *newException_ : model_->entry_at(row);
412 HostContentSettingsMap::PatternSettingPair entry = originalEntry;
413 bool isOtr =
414 isNewRow ? 0 : model_->entry_is_off_the_record(row);
415 bool wasOtr = isOtr;
416
417 // Modify it.
418 NSString* identifier = [tableColumn identifier];
419 if ([identifier isEqualToString:@"pattern"]) {
420 entry.first = ContentSettingsPattern(base::SysNSStringToUTF8(object));
421 }
422 if ([identifier isEqualToString:@"action"]) {
423 int index = [object intValue];
424 entry.second = popup_model_->SettingForIndex(index);
425 }
426 if ([identifier isEqualToString:@"otr"]) {
427 isOtr = [object intValue] != 0;
428 }
429
430 // Commit modification, if any.
431 if (isNewRow) {
432 newException_.reset();
433 if (![identifier isEqualToString:@"pattern"]) {
434 [tableView_ reloadData];
435 [self adjustEditingButtons];
436 return; // Commit new rows only when the pattern has been set.
437 }
438 int newIndex = model_->IndexOfExceptionByPattern(entry.first, false);
439 if (newIndex != -1) {
440 // The new pattern was already in the table. Focus existing row instead of
441 // overwriting it with a new one.
442 [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex]
443 byExtendingSelection:NO];
444 [tableView_ reloadData];
445 [self adjustEditingButtons];
446 return;
447 }
448 }
449 if (entry != originalEntry || wasOtr != isOtr || isNewRow)
450 [self updateRow:row withEntry:entry forOtr:isOtr];
451 }
452
453
454 // Table View Delegate --------------------------------------------------------
455
456 // When the selection in the table view changes, we need to adjust buttons.
457 - (void)tableViewSelectionDidChange:(NSNotification*)notification {
458 [self adjustEditingButtons];
459 }
460
461 // Private --------------------------------------------------------------------
462
463 // This method appropriately sets the enabled states on the table's editing
464 // buttons.
465 - (void)adjustEditingButtons {
466 NSIndexSet* selection = [tableView_ selectedRowIndexes];
467 [removeButton_ setEnabled:([selection count] > 0)];
468 [removeAllButton_ setEnabled:([tableView_ numberOfRows] > 0)];
469 }
470
471 - (void)modelDidChange {
472 // Some calls on |model_|, e.g. RemoveException(), change something on the
473 // backing content settings map object (which sends a notification) and then
474 // change more stuff in |model_|. If |model_| is deleted when the notification
475 // is sent, this second access causes a segmentation violation. Hence, disable
476 // resetting |model_| while updates can be in progress.
477 if (!updatesEnabled_)
478 return;
479
480 // The model caches its data, meaning we need to recreate it on every change.
481 model_.reset(new ContentExceptionsTableModel(
482 settingsMap_, otrSettingsMap_, settingsType_));
483
484 [tableView_ reloadData];
485 [self adjustEditingButtons];
486 }
487
488 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698