source: trunk/PrefsPane/src/FilterTabController.m @ 1046

Revision 1046, 45.4 KB checked in by speck, 3 days ago (diff)

printf %d formatting fixes.
1.5b23

Line 
1/* Copyright (C) 2008-2009 Peter Speck
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#import "FilterTabController.h"
18#import "GBDebug.h"
19#import "XmlDoc.h"
20#import "Xml.h"
21#import "ServerCommunication.h"
22#import "AlertHelper.h"
23#import "Prefs.h"
24#import "Filter.h"
25#import "FilterListView.h"
26#import "FilterPublisher.h"
27#import "FilterRuleEdit.h"
28#import "FilterRuleTableView.h"
29#import "Rule.h"
30#import "HostTextFieldCell.h"
31
32@interface FilterTabController ()
33- (void)loadRulesWithForceUpdate:(BOOL)forceUpdate;
34- (void)sortRules;
35
36- (void)ruleEditAction:(id)sender;
37
38- (Filter*)createFilterForNewElement:(NSXMLElement*)filterE;
39- (Filter*)createFilterForExistingElement:(NSXMLElement*)filterE;
40- (Filter*)createFilterForExistingElement:(NSXMLElement*)filterE atIndex:(NSUInteger)idx;
41
42- (void)releaseUpdateWithCancel:(BOOL)cancel;
43@end
44
45//=======================================================================================================
46
47@implementation FilterTabController
48
49+ (NSImage*)getPngImage:(NSString*)name inBundle:(NSBundle*)bundle
50{
51    NSString* path = [bundle pathForResource:name ofType:@"png"];
52    NSAssert1(path, @"Missing Image: %@.png", name);
53    return [[[NSImage alloc] initWithContentsOfFile:path] autorelease];
54}
55
56- (id)initWithServerCommunication:(ServerCommunication*)comm
57                withContainerView:(NSView*)containerView
58                      withTabView:(NSTabView*)tabView
59           withFiltersTabViewItem:(NSTabViewItem*)filtersTabViewItem
60                       withBundle:(NSBundle*)bundle
61{
62    if (!(self = [super init]))
63        return nil;
64    NSAssert([NSBundle loadNibNamed: @"FilterTabView" owner: self], @"Can't load nib 'FilterTabView'");
65    NSAssert(filterVertViewFlow, @"filterVertViewFlow is null");
66    [filterVertViewFlow setDelegate:self];
67    _tabView = tabView; // no retain: owned by superview
68    _filtersTabViewItem = filtersTabViewItem; // no retain: owned by superview
69    _comm = [comm retain];
70    _prefs = [[comm prefs] retain];
71    _alertHelper = [[_prefs alertHelper] retain];
72    _bundle = [bundle retain];
73    [filterVertViewFlow setController:self];
74    [containerView addSubview:subView];
75    _subscriptionGlobeImage = [[FilterTabController getPngImage:@"SubscriptionGlobe" inBundle:bundle] retain];
76    _publishGlobeImage = [[FilterTabController getPngImage:@"PublishGlobe" inBundle:bundle] retain];
77    _publishGlobeDirtyImage = [[FilterTabController getPngImage:@"PublishGlobeDirty" inBundle:bundle] retain];
78    _warningImage = [[FilterTabController getPngImage:@"WarningIcon" inBundle:bundle] retain];
79    _updateAvailableImage = [[FilterTabController getPngImage:@"UpdateAvailableIcon" inBundle:bundle] retain];
80    _ruleElementArray = [[NSMutableArray arrayWithCapacity:40] retain];
81    _rulesOwnerFilter = NULL;
82    [splitView setDelegate:self];
83    [rulesTableView registerForDraggedTypes:[rulesTableView getPasteboardTypes]];
84    [rulesTableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:YES];
85    [rulesTableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
86    return self;
87}
88
89- (void)updateRuleSortingIcon
90{
91    NSUInteger v = [_rulesSortColumnIndexes count] ? [[_rulesSortColumnIndexes objectAtIndex:0] intValue] : 0;
92    NSUInteger selectedIndex = v / 2;
93    BOOL ascending = (v & 1);
94    //
95    NSArray* cols = [rulesTableView tableColumns];
96    for (NSUInteger idx = 0; idx < [cols count]; idx++) {
97        NSImage* img = NULL;
98        if (idx == selectedIndex) {
99            NSString* imageName = ascending ? @"NSAscendingSortIndicator" :  @"NSDescendingSortIndicator";
100            img = [NSImage imageNamed:imageName];
101        }
102        [rulesTableView setIndicatorImage:img inTableColumn:[cols objectAtIndex:idx]];
103    }
104}
105
106- (void)addSortingColumn:(NSString*)columnId
107             isAscending:(BOOL)ascending
108               forUpdate:(BOOL)updating
109{
110    NSInteger columnIndex = [rulesTableView columnWithIdentifier:columnId];
111    if (columnIndex < 0)
112        return;
113    NSUInteger num = [_rulesSortColumnIndexes count];
114    for (NSUInteger i = 0; i < num; i++) {
115        if ([[_rulesSortColumnIndexes objectAtIndex:i] intValue] / 2 != columnIndex)
116            continue;
117        if (!updating)
118            return; // already appended
119        [_rulesSortColumnIndexes removeObjectAtIndex:i];
120        break;
121    }
122    NSNumber* newMagic = [NSNumber numberWithLong:(columnIndex * 2 + ascending)];
123    if (updating) {
124        [_rulesSortColumnIndexes insertObject:newMagic atIndex:0];
125    } else {
126        [_rulesSortColumnIndexes addObject:newMagic];
127        return;
128    }
129    [_prefs removeElementsForXPath:@"iu/filters/rule-column"];
130    NSXMLElement* parentE = [_prefs singletonElementForXPath:@"iu/filters"];
131    NSArray* cols = [rulesTableView tableColumns];
132    for (NSUInteger i = 0; i < num; i++) {
133        int v = [[_rulesSortColumnIndexes objectAtIndex:i] intValue];
134        NSXMLElement* e = [Xml createElementWithName:@"rule-column"];
135        NSTableColumn* col = [cols objectAtIndex:v/2];
136        [Xml setAttribute:[col identifier] withName:@"column-id" inElement:e];
137        if (v & 1)
138            [Xml setAttribute:@"1" withName:@"ascending" inElement:e];
139        [parentE addChild:e];
140    }
141    [_prefs markAsDirty];
142}
143
144- (void)restoreRuleSorting
145{
146    _rulesSortColumnIndexes = [[NSMutableArray arrayWithCapacity:10] retain];
147    for (NSXMLElement* e in [_prefs nodesForXPath:@"iu/filters/rule-column"]) {
148        NSString* columnId = [Xml getAttribute:e withName:@"column-id"];
149        if (![columnId length])
150            continue;
151        BOOL ascending = [Xml getBoolAttribute:e withName:@"ascending"];
152        [self addSortingColumn:columnId isAscending:ascending forUpdate:NO];
153    }
154    [self addSortingColumn:@"host" isAscending:YES forUpdate:NO]; // default to host if list is empty.
155    NSArray* cols = [rulesTableView tableColumns];
156    for (NSUInteger idx = 0; idx < [cols count]; idx++) {
157        NSTableColumn* col = [cols objectAtIndex:idx];
158        [self addSortingColumn:[col identifier] isAscending:YES forUpdate:NO];
159    }
160    [self updateRuleSortingIcon];
161}
162
163- (void)restoreFilterSelection
164{
165    [self restoreRuleSorting];
166    NSUInteger num = [filterVertViewFlow rowCount];
167    if (!num)
168        return;
169    NSInteger filterId = [_prefs intForXPath:@"iu/filters/@selected"];
170    if (filterId) {
171        for (NSUInteger i = 0; i < num; i++) {
172            Filter* filter = [filterVertViewFlow rowByIndex:i];
173            if ([filter filterId] == filterId) {
174                [filterVertViewFlow setSelectionByIndex:i];
175                return;
176            }
177        }
178    }
179    [filterVertViewFlow setSelectionByIndex:0];
180}
181
182- (void)postInit
183{
184    if (_hasPostInited || ![_comm isServerRunning])
185        return;
186    _hasPostInited = YES;
187    [filterVertViewFlow setFreezeUpdates:YES];
188    for (NSXMLElement* filterE in [[_prefs filtersE] elementsForName:@"filter"])
189        [self createFilterForExistingElement:filterE];
190    [filterVertViewFlow setFreezeUpdates:NO];
191    [rulesTableView setDelegate:self];
192    [rulesTableView setDataSource:self];
193    [rulesTableView setTarget:self];
194    [rulesTableView setDoubleAction:@selector(ruleEditAction:)];
195    [rulesTableView reloadData];
196    [self restoreFilterSelection];
197#ifdef DEBUG
198    [subAddUrlTextField setStringValue:@"http://172.16.0.2/site/filters/default/WorldWideAdProviders.xml"];
199    [subAddUrlTextField setStringValue:@"http://glimmerblocker.org/site/filters/Test.xml"];
200#else
201    [subAddUrlTextField setStringValue:@""];
202#endif
203}
204
205- (void)dealloc
206{
207    DebugNSLog(@"FilterTabController.dealloc");
208    [self releaseUpdateWithCancel:YES];
209    [filterVertViewFlow setFreezeUpdates:YES];
210    [filterVertViewFlow setDelegate:NULL];
211    [_alertHelper release];
212    [_prefs release];
213    [_comm release];
214    [_bundle release];
215    [_ruleElementArray release];
216    [_rulesOwnerFilter release];
217    [_rulesSortColumnIndexes release];
218    //
219    [_subscriptionGlobeImage release];
220    [_publishGlobeImage release];
221    [_publishGlobeDirtyImage release];
222    [_warningImage release];
223    //
224    [subView removeFromSuperview];
225    [subView release];
226    [subAddSheet release];
227    [filterActionMenu release];
228    [ruleActionMenu release];
229    _tabView = NULL; // no retain: owned by superview
230    _filtersTabViewItem = NULL; // no retain: owned by superview
231    [super dealloc];
232}
233
234- (NSImage*)subscriptionGlobeImage
235{
236    return _subscriptionGlobeImage;
237}
238
239- (NSImage*)publishGlobeImage
240{
241    return _publishGlobeImage;
242}
243
244- (NSImage*)publishGlobeDirtyImage
245{
246    return _publishGlobeDirtyImage;
247}
248
249- (NSImage*)warningImage
250{
251    return _warningImage;
252}
253
254- (NSImage*)updateAvailableImage
255{
256    return _updateAvailableImage;
257}
258
259#pragma mark ------------------------ filters
260
261- (void)vertPatchTableChangedSelection:(id)sender
262{
263    [self updateTabViewWithForceUpdate:YES];
264    Filter* filter = [self selectedFilter];
265    if (!filter)
266        return;
267    if (![_prefs hasGlimmerAuth])
268        return;
269    [_prefs setInt:[filter filterId] forXPath:@"iu/filters/@selected"];
270    [_prefs markAsDirty];
271}
272
273- (void)redrawRulesTable
274{
275    [rulesTableView reloadData];
276}
277
278- (void)updateTabViewWithForceUpdate:(BOOL)forceUpdate
279{
280    BOOL hasAuth = [_prefs hasGlimmerAuth];
281    Filter* filter = [self selectedFilter];
282    [filterAddBtn setEnabled:hasAuth];
283    [filterRemoveBtn setEnabled:hasAuth && [filter canDelete]];
284    [filterActionBtn setEnabled:hasAuth];
285    [filterCommentsTV setEditable:hasAuth && [filter canEditComments]];
286    [filterCommentsTV setDrawsBackground:[filter canEditComments]];
287    [filterCommentsTV setBordered:[filter canEditComments]];
288    [ruleAddBtn setEnabled:hasAuth && [filter canEditRules]];
289    [ruleRemoveBtn setEnabled:hasAuth && [filter canEditRules] && [rulesTableView selectedRow] >= 0];
290    [self loadRulesWithForceUpdate:forceUpdate];
291    if (!filter) {
292        [filterCommentsLabelTV setStringValue:@"Comments for selected filter:"];
293        [filterCommentsTV setStringValue:@""];
294        [rulesTableHeader setStringValue:@"Rules in selected filter:"];
295        return;
296    }
297    //
298    [filterCommentsTV setStringValue:([filter canEditComments] ? [filter comments] : [filter description])];
299    if ([filter canEditComments])
300        [filterCommentsLabelTV setStringValue:[NSString stringWithFormat:@"Your comments for this filter:"]];
301    else if ([[filter description] length])
302        [filterCommentsLabelTV setStringValue:[NSString stringWithFormat:@"Description:"]];
303    else
304        [filterCommentsLabelTV setStringValue:@""];
305    NSString* s = [filter enabled] ? @"Rules in “%@”:" : @"Rules in disabled filter “%@”:";
306    [rulesTableHeader setStringValue:[NSString stringWithFormat:s, [filter name]]];
307}
308
309- (void)loadRulesWithForceUpdate:(BOOL)forceUpdate
310{
311    Filter* filter = [self selectedFilter];
312    if (!filter) {
313        if (!_rulesOwnerFilter)
314            return;
315        DebugNSLog(@"Clears rules as no filter is selected anymore.");
316        [_ruleElementArray removeAllObjects];
317        [rulesTableView reloadData];
318        _rulesOwnerFilter = NULL;
319        return;
320    }
321    BOOL hideRules = [filter isSubscription] && ![_comm isServerRunning];
322    [rulesWithoutServer setHidden:!hideRules];
323    [rulesTableHeader setHidden:hideRules];
324    [rulesTableScrollView setHidden:hideRules];
325    [ruleAddBtn setHidden:hideRules];
326    [ruleRemoveBtn setHidden:hideRules];
327    if (filter == _rulesOwnerFilter && !forceUpdate)
328        return;
329    //DebugNSLog(@"Reloads rules, selected filter = %@", [filter name]);
330    [_ruleElementArray removeAllObjects];
331    [_rulesOwnerFilter release];
332    _rulesOwnerFilter = [filter retain];
333    [_ruleElementArray setArray:[[filter filterDataE] elementsForName:@"rule"]];
334    [self sortRules];
335    [rulesTableView reloadData];
336    // Sometimes the 'enabled' checkbox in the first row isn't updated/redrawn when selecting another filter.
337    // So do one more update async.
338    [rulesTableView performSelectorOnMainThread:@selector(reloadData)
339                                     withObject:NULL
340                                  waitUntilDone:NO];
341    NSString* selectedRules = [Xml getAttribute:[_rulesOwnerFilter filterE] withName:@"selected-rules"];
342    NSMutableSet* ruleSet = [NSMutableSet setWithCapacity:5];
343    for (NSString* s in [selectedRules componentsSeparatedByString:@","])
344        [ruleSet addObject:[NSNumber numberWithInt:[s intValue]]];
345    NSUInteger num = [_ruleElementArray count];
346    NSMutableIndexSet* indexes = [[[NSMutableIndexSet alloc] init] autorelease];
347    for (NSUInteger i = 0; i < num; i++) {
348        NSXMLElement* ruleE = [_ruleElementArray objectAtIndex:i];
349        NSNumber* key = [NSNumber numberWithInt:[FilterRuleEdit getRuleId:ruleE]];
350        if ([ruleSet containsObject:key])
351            [indexes addIndex:i];
352    }
353    [rulesTableView selectRowIndexes:indexes byExtendingSelection:NO];
354    [rulesTableView scrollRowToVisible:[indexes firstIndex]];
355}
356
357- (BOOL)hasSubscription
358{
359    NSUInteger num = [filterVertViewFlow rowCount];
360    for (NSUInteger i = 0; i < num; i++) {
361        Filter* filter = [filterVertViewFlow rowByIndex:i];
362        if ([filter isSubscription])
363            return YES;
364    }
365    return NO;
366}
367
368- (Filter*)createFilterForExistingElement:(NSXMLElement*)filterE atIndex:(NSUInteger)idx
369{
370    Filter* filter = [[Filter alloc] initWithFilterElement:filterE
371                                                  withComm:_comm
372                                         withTabController:self
373                                        withFilterListView:filterVertViewFlow
374                                                   atIndex:idx];
375    [filter autorelease]; // table view has retain. clang-sa doesn't know, so use autorelease instead of release.
376    return filter;
377}
378
379// The element must already have been added to the DOM.
380- (Filter*)createFilterForExistingElement:(NSXMLElement*)filterE
381{
382    NSUInteger idx = 0;
383    for (NSXMLNode* e = [filterE previousSibling]; e; e = [e previousSibling]) {
384        if ([e kind] == NSXMLElementKind)
385            idx++;
386    }
387    return [self createFilterForExistingElement:filterE atIndex:idx];
388}
389
390- (Filter*)createFilterForNewElement:(NSXMLElement*)filterE
391{
392    NSXMLElement *filtersE = [_prefs filtersE];
393    Filter* selectedFilter = [filterVertViewFlow lastSelectedRow];
394    // Cocoa DOM doesn't have insertBefore but only insertChild:atIndex
395    NSUInteger idx = selectedFilter ? [[filtersE children] indexOfObject:[selectedFilter filterE]] : NSNotFound;
396    if (idx == NSNotFound)
397        [filtersE addChild:filterE];
398    else if (idx >= [filtersE childCount])
399        [filtersE addChild:filterE];
400    else
401        [filtersE insertChild:filterE atIndex:idx + 1];
402    Filter* filter = [self createFilterForExistingElement:filterE];
403    [filterVertViewFlow setSelectionByView:filter];
404    if ([filter canEditName])
405        [filter startEditingName];
406    return filter;
407}
408
409- (BOOL)hasFilterWithName:(NSString*)name
410{
411    for (Filter* filter in [filterVertViewFlow rows]) {
412        if ([[filter name] isEqual:name])
413            return YES;
414    }
415    return NO;
416}
417
418- (Filter*)createNewUntitledFilter
419{
420    NSString* prefix = @"My untitled filter";
421    NSString* name = prefix;
422    if ([self hasFilterWithName:name]) {
423        for (int i = 2; YES; i++) {
424            name = [NSString stringWithFormat:@"%@-%d", prefix, i];
425            if (![self hasFilterWithName:name])
426                break;
427        }
428    }
429    NSXMLElement *filterE = [Xml createElementWithName:@"filter"];
430    [Xml setInt:[_prefs makeUniqueId] withName:@"id" inElement:filterE];
431    NSXMLElement *filterDataE = [Xml createElementWithName:@"filter-data"];
432    [filterE addChild:filterDataE];
433    //
434    [Xml setAttribute:name withName:@"name" inElement:filterDataE];
435    [Xml setAttribute:@"1" withName:@"enabled" inElement:filterE];
436    //
437    return [self createFilterForNewElement:filterE];
438    // don't save filter until changed.
439}
440
441- (IBAction)filterAddAction:(id)sender
442{
443    // If option-key is down, duplicate current filter instead of adding new empty filter.
444    NSEvent* event = [NSApp currentEvent];
445    NSUInteger modifiers = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask;
446    if (modifiers == NSAlternateKeyMask) {
447        [self filterActionDuplicateAction:sender];
448        return;
449    }
450    [self createNewUntitledFilter];
451}
452
453- (IBAction)filterActionDuplicateAction:(id)sender
454{
455    Filter* srcFilter = [self selectedFilter];
456    if (!srcFilter) {
457        NSLog(@"filterActionDuplicateAction without selected filter");
458        return;
459    }
460    NSXMLElement *srcFilterDataE = [srcFilter filterDataE];
461    if (!srcFilterDataE) {
462        [_alertHelper prepareCriticalAlertWithTitle:@"Can't duplicate subscriptions when GlimmerBlocker is not running"
463                                withInformativeText:@"You must activate GlimmerBlocker before duplicating the filter"];
464        [_alertHelper showPreparedAlert];
465        return;
466    }
467    NSXMLElement *newFilterE = [Xml createElementWithName:@"filter"];
468    [Xml setInt:[_prefs makeUniqueId] withName:@"id" inElement:newFilterE];
469    NSXMLElement *newFilterDataE = [Xml createElementWithName:@"filter-data"];
470    [newFilterE addChild:newFilterDataE];
471    //
472    NSString* name = [NSString stringWithFormat:@"%@ copy", [srcFilter name]];
473    [Xml setAttribute:name withName:@"name" inElement:newFilterDataE];
474    //
475    NSString* comments = [srcFilter description];
476    [Xml setAttribute:comments withName:@"comments" inElement:newFilterE];
477    //
478    [Xml setAttribute:[srcFilter enabled] ? @"1" : @"" withName:@"enabled" inElement:newFilterE];
479    //
480    for (NSXMLElement* ruleE in [[srcFilter filterDataE] elementsForName:@"rule"])
481        [newFilterDataE addChild:[[ruleE copy] autorelease]];
482    //
483    [self createFilterForNewElement:newFilterE];
484}
485
486- (Filter*)selectedFilter
487{
488    return [filterVertViewFlow firstSelectedRow];
489}
490
491- (Filter*)selectFilterById:(NSInteger)filterId
492{
493    for (Filter* filter in [filterVertViewFlow rows]) {
494        if ([filter filterId] == filterId) {
495            [filterVertViewFlow setSelectionByView:filter];
496            return filter;
497        }
498    }
499    return NULL;
500}
501
502- (Filter*)getFilterById:(NSInteger)filterId
503{
504    for (Filter* filter in [filterVertViewFlow rows]) {
505        if ([filter filterId] == filterId)
506            return filter;
507    }
508    return NULL;
509}
510
511- (void)confirmFilterRemoveWithReturnCode:(NSInteger)returnCode
512{
513    if (returnCode != NSAlertFirstButtonReturn)
514        return;
515    NSUInteger idx = [[self selectedFilter] rowNumber];
516    [[self selectedFilter] deleteFilter];
517    if (idx < [filterVertViewFlow rowCount])
518        [filterVertViewFlow setSelectionByIndex:idx];
519    else if ([filterVertViewFlow rowCount])
520        [filterVertViewFlow setSelectionByIndex:idx - 1];
521    else
522        [self updateTabViewWithForceUpdate:YES];
523    [_comm saveDirtyPrefsAndNotify];
524}
525
526- (IBAction)filterRemoveAction:(id)sender
527{
528    [_alertHelper prepareWarningAlertWithTitle:@"Remove filter?"
529                            withInformativeText:[[self selectedFilter] name]];
530    [_alertHelper addButtonWithTitle:@"OK"];
531    [[_alertHelper addButtonWithTitle:@"Cancel"] setKeyEquivalent:@"\e"];
532    [_alertHelper setDelegate:self
533                 withSelector:@selector(confirmFilterRemoveWithReturnCode:)];
534    [_alertHelper showPreparedAlert];
535}
536
537- (IBAction)filterEditAction:(id)sender
538{
539    if ([[self selectedFilter] isSubscription])
540        [[self selectedFilter] startEditingSubscription:NO];
541}
542
543#pragma mark ------------------------ rules
544
545- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)col row:(NSInteger)row
546{
547    if (tableView == rulesTableView) {
548        Filter* filter = [self selectedFilter];
549        return filter && [filter canEditRules];
550    }
551    NSAssert(NO, @"Unknown table in shouldEditTableColumn");
552    return NO;
553}
554
555- (void)setSavedRuleSelection:(NSArray*)ruleList
556{
557    NSMutableString* sb = [NSMutableString stringWithCapacity:(10 * [ruleList count])];
558    for (NSXMLElement* ruleE in ruleList) {
559        if ([sb length])
560            [sb appendString:@","];
561        [sb appendFormat:@"%d", [FilterRuleEdit getRuleId:ruleE]];
562    }
563    [Xml setAttribute:sb withName:@"selected-rules" inElement:[_rulesOwnerFilter filterE]];
564    [_prefs markAsDirty];
565}
566
567- (BOOL)deleteRowData:(NSTableView *)tableView forRow:(NSUInteger)row
568{
569    if (tableView == rulesTableView) {
570        NSXMLElement* ruleE = [_ruleElementArray objectAtIndex:row];
571        [ruleE detach];
572        [_ruleElementArray removeObjectAtIndex:row];
573        [_rulesOwnerFilter notifyDirty];
574        return YES;
575    }
576    NSAssert(NO, @"Unknown table in deleteRowData");
577    return NO;
578}
579
580- (IBAction)filterActionAction:(id)sender
581{
582    DebugNSLog(@"filterActionAction, t9DuplicateMI = %@, t9AddByUrlMI = %@", filterDuplicateMI, filterAddByUrlMI);
583    [filterActionMenu setAutoenablesItems:NO];
584    NSRect frame = [filterActionBtn frame];
585    frame.origin.x = 0.0f;
586    frame.origin.y = 0.0f;
587    NSPopUpButtonCell* cell = [[[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:YES] autorelease];
588    [cell setMenu:filterActionMenu];
589    Filter* filter = [self selectedFilter];
590    [filterDuplicateMI setEnabled:!!filter];
591    [filterEditMI setEnabled:[filter isSubscription]];
592    [filterUpdateSelectedMI setEnabled:[filter isSubscription]];
593    [filterUpdateAllMI setEnabled:[self hasSubscription]];
594    if (filter && ![filter isSubscription]) {
595        [filterPublishWebDavMI setEnabled:![filter isMobileMePublished]];
596        [filterPublishMobileMeMI setEnabled:![filter isWebDavPublished]];
597    } else {
598        [filterPublishWebDavMI setEnabled:NO];
599        [filterPublishMobileMeMI setEnabled:NO];
600    }
601    [cell performClickWithFrame:frame inView:filterActionBtn];
602}
603
604- (IBAction)filterCommentsAction:(id)sender
605{
606    Filter* filter = [self selectedFilter];
607    if (!filter || ![filter canEditComments])
608        return;
609    [filter setComments:[filterCommentsTV stringValue]];
610    [_comm saveDirtyPrefsAndNotify];
611}
612
613- (void)ruleEditCommit:(NSXMLElement*)ruleE
614          withFilter:(Filter*)filter
615                isVirgin:(BOOL)virgin
616{
617    [filterVertViewFlow setSelectionByView:filter];
618    [self updateTabViewWithForceUpdate:YES]; // force to get sorting updated.
619    NSInteger idx = [_ruleElementArray indexOfObject:ruleE];
620    if (idx == NSNotFound) {
621        NSLog(@"Added rule disappeared from _ruleElementArray");
622    } else {
623        NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
624        [rulesTableView selectRowIndexes:indexes byExtendingSelection:NO];
625        [rulesTableView scrollRowToVisible:idx];
626    }
627    // Select the tab views we're inside.
628    [_tabView selectTabViewItem:_filtersTabViewItem];
629    [filter notifyDirty];
630    [_prefs saveIfDirty:self];
631    [_comm sendReloadPreferencesForModifiedFilter:[filter filterId] withRule:[FilterRuleEdit getRuleId:ruleE]];
632    [[_tabView window] performSelectorOnMainThread:@selector(makeFirstResponder:)
633                                        withObject:rulesTableView
634                                     waitUntilDone:NO];
635}
636
637- (IBAction)ruleAddAction:(id)sender
638{
639    Filter* filter = [self selectedFilter];
640    if (!filter) {
641        NSLog(@"ruleAddAction without selected filter");
642        return;
643    }
644    // If option-key is down, duplicate current rule instead of adding new empty rule.
645    NSEvent* event = [NSApp currentEvent];
646    NSUInteger modifiers = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask;
647    if (modifiers == NSAlternateKeyMask) {
648        [self ruleActionDuplicateAction:sender];
649        return;
650    }
651    [[[FilterRuleEdit alloc] initWithFilterTabController:self
652                                         withOwnerFilter:filter
653                                         withRuleElement:NULL
654                                      withSuspectElement:NULL] autorelease];
655}
656
657- (IBAction)ruleRemoveAction:(id)sender
658{
659    if ([rulesTableView selectedRow] < 0)
660        return;
661    [self removeSelectedRows:rulesTableView];
662    [_comm saveDirtyPrefsAndNotify];
663}
664
665- (IBAction)ruleActionAction:(id)sender
666{
667    [ruleActionMenu setAutoenablesItems:NO];
668    NSRect frame = [ruleActionBtn frame];
669    frame.origin.x = 0.0f;
670    frame.origin.y = 0.0f;
671    NSPopUpButtonCell* cell = [[[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:YES] autorelease];
672    [cell setMenu:ruleActionMenu];
673    NSInteger row = [rulesTableView selectedRow];
674    [ruleDuplicateMI setEnabled:(row >= 0 && ![_rulesOwnerFilter isSubscription])];
675    [cell performClickWithFrame:frame inView:ruleActionBtn];
676}
677
678- (IBAction)ruleActionDuplicateAction:(id)sender
679{
680    NSIndexSet* set = [rulesTableView selectedRowIndexes];
681    for (NSInteger row = [set lastIndex]; row != NSNotFound; row = [set indexLessThanIndex:row]) {
682        NSXMLElement* oldRuleE = [_ruleElementArray objectAtIndex:row];
683        NSXMLElement* parentE = (NSXMLElement*)[oldRuleE parent];
684        NSXMLElement* newRuleE = [[oldRuleE copy] autorelease];
685        NSInteger idx = [[parentE children] indexOfObject:oldRuleE];
686        [parentE insertChild:newRuleE atIndex:idx];
687        [Xml setAttribute:NULL withName:@"rule-id" inElement:newRuleE]; // remove id, so it gets a new one.
688        [FilterRuleEdit getRuleId:newRuleE]; // ensures it has a rule-id
689    }
690    [self updateTabViewWithForceUpdate:YES];
691}
692
693- (void)ruleEditAction:(id)sender
694{
695    NSInteger row = [rulesTableView clickedRow];
696    if (row < 0)
697        return;
698    Filter* filter = [self selectedFilter];
699    if (!filter) {
700        NSLog(@"ruleEditAction without selected filter");
701        return;
702    }
703    [[[FilterRuleEdit alloc] initWithFilterTabController:self
704                                         withOwnerFilter:filter
705                                         withRuleElement:[_ruleElementArray objectAtIndex:row]
706                                      withSuspectElement:NULL] autorelease];
707}
708
709#pragma mark ------------------------ add subscription
710
711- (void)subAddConfirmRedisplaySheet:(int)returnCode
712{
713    [self filterActionSubscribeAction:self];
714}
715
716- (void)showSimpleAddSubscriptionNetworkError:(NSString*)msg
717{
718    [_alertHelper dismissAlert];
719    [_alertHelper prepareCriticalAlertWithTitle:@"Could not get subscription"
720                            withInformativeText:msg];
721    [_alertHelper setDelegate:self
722                 withSelector:@selector(subAddConfirmRedisplaySheet:)];
723    [_alertHelper showPreparedAlert];
724}
725
726- (IBAction)filterActionSubscribeAction:(id)sender
727{
728    if (![_comm isServerRunning]) {
729        [_alertHelper prepareCriticalAlertWithTitle:@"Can't add subscriptions when GlimmerBlocker is not running"
730                                withInformativeText:@"You must check activate GlimmerBlocker to add subscriptions."];
731        [_alertHelper showPreparedAlert];
732        return;
733    }
734    _addSubFilterId = [_prefs makeUniqueId];
735    [NSApp beginSheet: subAddSheet
736       modalForWindow: [subView window]
737        modalDelegate: nil
738       didEndSelector: nil
739          contextInfo: nil];
740}
741
742- (IBAction)subAddCancelAction:(id)sender
743{
744    [NSApp endSheet:subAddSheet];
745    [subAddSheet orderOut:self];
746}
747
748- (void)cancelledAddSubscription:(int)returnCode
749{
750    DebugNSLog(@"cancelledAddSubscription called");
751    [self releaseUpdateWithCancel:YES];
752}
753
754- (IBAction)subAddConfirmAction:(id)sender
755{
756    [NSApp endSheet:subAddSheet];
757    [subAddSheet orderOut:self];
758    NSString* s = [subAddUrlTextField stringValue];
759    while ([s length] && [s characterAtIndex:0] <= 32)
760        s = [s substringFromIndex:1];
761    while ([s length] && [s characterAtIndex:[s length] - 1] <= 32)
762        s = [s substringToIndex:[s length] - 1]; // index included
763    if (![s length]) {
764        DebugNSLog(@"subAddConfirmAction: empty text field.");
765        return;
766    }
767    NSString* urlSyntaxError = [Xml getHttpUrlSyntaxError:s];
768    if (!urlSyntaxError && ![s hasSuffix:@".xml"])
769        urlSyntaxError = @"The URL must end with '.xml'";
770    if (urlSyntaxError) {
771        [_alertHelper prepareCriticalAlertWithTitle:@"The URL you entered is not valid"
772                                withInformativeText:urlSyntaxError];
773        [_alertHelper setDelegate:self
774                     withSelector:@selector(subAddConfirmRedisplaySheet:)];
775        [_alertHelper showPreparedAlert];
776        return;
777    }
778    DebugNSLog(@"add subscription url: %@", s);
779    [_alertHelper prepareInformativeAlertWithTitle:@"Adding subscription" withInformativeText:@"Downloading"];
780    [_alertHelper setDelegate:self withSelector:@selector(cancelledAddSubscription:)];
781    [[_alertHelper addButtonWithTitle:@"Cancel"] setKeyEquivalent:@"\e"];
782    NSProgressIndicator* spinner = [[[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, 440, 20)] autorelease];
783    [spinner setStyle:NSProgressIndicatorBarStyle];
784    [spinner setIndeterminate:YES];
785    [spinner setDisplayedWhenStopped:NO];
786    [_alertHelper setAccessoryView:spinner];
787    [_alertHelper showPreparedAlert];
788    [spinner startAnimation:self];
789    //
790    [self releaseUpdateWithCancel:NO];
791    _addSubPathArgs = [[NSString stringWithFormat:@"!/filters/subscription/add?url=%@&filter-id=%d",
792                        [GBDebug percentEscapeUrlParameterValue:s],
793                        _addSubFilterId] retain];
794    _addSubUrl = [[NSURL URLWithString:[_comm getAdminUrl:_addSubPathArgs]] retain];
795    NSURLRequest* req = [NSURLRequest requestWithURL:_addSubUrl
796                                         cachePolicy:NSURLRequestReloadIgnoringLocalCacheData // want fresh data
797                                     timeoutInterval:60.0];
798    _addSubData = [[NSMutableData data] retain];
799    _addSubConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self];
800    if (!_addSubConnection) {
801        [self releaseUpdateWithCancel:NO];
802        [self showSimpleAddSubscriptionNetworkError:NULL];
803        return;
804    }
805}
806
807- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
808{
809    // called for each response, i.e. for each redirect
810    [_addSubData setLength:0];
811#ifdef DEBUG
812    NSHTTPURLResponse* http = (NSHTTPURLResponse*)response;
813    DebugNSLog(@"didReceiveResponse: %ld   %@", [http statusCode], [http allHeaderFields]);
814#endif
815}
816
817- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
818{
819    [_addSubData appendData:data];
820}
821
822- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
823{
824    NSLog(@"Add subscription request failed: %@", error);
825    NSString* a = [error localizedDescription];
826    NSString* b = [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey];
827    NSString* updateError = [[NSString stringWithFormat:@"%@ %@", a, b] retain];
828    [self showSimpleAddSubscriptionNetworkError:updateError];
829    [self releaseUpdateWithCancel:NO];
830}
831
832- (void)connectionDidFinishLoading:(NSURLConnection *)connection
833{
834    DebugNSLog(@"Got connectionDidFinishLoading, result size = %ld bytes", [_addSubData length]);
835    [_alertHelper dismissAlert];
836    XmlDoc* doc = [[[XmlDoc alloc] initWithURL:_addSubUrl withData:_addSubData] autorelease];
837    [self releaseUpdateWithCancel:NO];
838    if (![doc rootE]) {
839        [self showSimpleAddSubscriptionNetworkError:@"Could not get proper xml response from server"];
840        return;
841    }
842    NSString* error = [Xml getAttribute:[doc rootE] withName:@"error"];
843    if (error) {
844        [self showSimpleAddSubscriptionNetworkError:error];
845        return;
846    }
847    NSString* pathArgs = [_addSubPathArgs stringByAppendingString:@"&commit=1"];
848    doc = [_comm curlToXml:pathArgs withActionDescription:@"Could not add subscription"];
849    if (!doc) {
850        NSLog(@"AddSub: failed getting doc");
851        return;
852    }
853    NSXMLElement* filterContainerE = [doc byXPath:@"filter-container"];
854    if (!filterContainerE) {
855        [_alertHelper prepareCriticalAlertWithTitle:@"Failed getting subscription"
856                                withInformativeText:@"Missing the <filter-container> element"];
857        [_alertHelper setDelegate:self
858                     withSelector:@selector(subAddConfirmRedisplaySheet:)];
859        [_alertHelper showPreparedAlert];
860        return;
861    }
862    NSXMLElement* filterDataE = [doc byXPath:@"filter-container/filter-data"];
863    if (!filterDataE) {
864        [_alertHelper prepareCriticalAlertWithTitle:@"Failed getting subscription"
865                                withInformativeText:@"Missing the <filter-data> element"];
866        [_alertHelper setDelegate:self
867                     withSelector:@selector(subAddConfirmRedisplaySheet:)];
868        [_alertHelper showPreparedAlert];
869        return;
870    }
871    DebugNSLog(@"AddSub success.");
872    NSXMLElement* filterE = [Xml createElementWithName:@"filter"];
873    [Xml setAttribute:[Xml getAttribute:filterContainerE withName:@"url"] withName:@"url" inElement:filterE];
874    [Xml setAttribute:@"1" withName:@"enabled" inElement:filterE];
875    [Xml setInt:_addSubFilterId withName:@"id" inElement:filterE];
876    [Xml setAttribute:updatePolicies[[subAddUpdatesMatrix selectedRow]] withName:@"update-policy" inElement:filterE];
877    [Xml setAttribute:safetyPolicies[[subAddSafetyMatrix selectedRow]] withName:@"safety-policy" inElement:filterE];
878    [self createFilterForNewElement:filterE];
879    DebugNSLog(@"AddSub: Saves settings");
880    [_comm saveDirtyPrefsAndNotify];
881}
882
883- (void)releaseUpdateWithCancel:(BOOL)cancel
884{
885    if (!_addSubUrl)
886        return;
887    DebugNSLog(@"#### releaseUpdateWithCancel, data size = %ld", [_addSubData length]);
888    if (cancel)
889        [_addSubConnection cancel];
890    [_addSubConnection release];
891    _addSubConnection = NULL;
892    [_addSubData release];
893    _addSubData = NULL;
894    [_addSubUrl release];
895    _addSubUrl = NULL;
896}
897
898#pragma mark ------------------------ update subscription
899
900- (IBAction)updateSelectedSubscriptionAction:(id)sender
901{
902    DebugNSLog(@"updateSelectedSubscriptionAction");
903    Filter* filter = [self selectedFilter];
904    if ([filter isSubscription])
905        [filter startUpdateSubscription];
906    else
907        NSLog(@"Selected filter is not a subscription");
908}
909
910- (IBAction)updateAllSubscriptionsAction:(id)sender
911{
912    DebugNSLog(@"updateAllSubscriptionsAction");
913    NSUInteger num = [filterVertViewFlow rowCount];
914    for (NSUInteger i = 0; i < num; i++) {
915        Filter* filter = [filterVertViewFlow rowByIndex:i];
916        if ([filter isSubscription])
917            [filter startUpdateSubscription];
918    }
919}
920
921- (void)notifyUpdatedFilter:(Filter*)filter
922{
923    if (filter == [self selectedFilter])
924        [self updateTabViewWithForceUpdate:YES];
925}
926
927#pragma mark ------------------------ Publish
928
929- (IBAction)publishWebDavAction:(id)sender
930{
931    Filter* filter = [self selectedFilter];
932    if (!filter)
933        return;
934    [[[FilterPublisher alloc] initWithFilterTabController:self
935                                               withFilter:filter
936                                            usingMobileMe:NO] autorelease];
937}
938
939- (IBAction)publishMobileMeAction:(id)sender
940{
941    Filter* filter = [self selectedFilter];
942    if (!filter)
943        return;
944    [[[FilterPublisher alloc] initWithFilterTabController:self
945                                               withFilter:filter
946                                            usingMobileMe:YES] autorelease];
947}
948
949#pragma mark ------------------------ Utils
950
951- (NSArray*)filterArray
952{
953    return [filterVertViewFlow rows];
954}
955
956- (NSArray*)ruleElementArray
957{
958    return _ruleElementArray;
959}
960
961- (NSBundle*)bundle
962{
963    return _bundle;
964}
965
966- (Prefs*)prefs
967{
968    return _prefs;
969}
970
971- (AlertHelper*)alertHelper
972{
973    return _alertHelper;
974}
975
976- (ServerCommunication*)comm
977{
978    return _comm;
979}
980
981- (FilterRuleTableView*)rulesTableView
982{
983    return rulesTableView;
984}
985
986- (void)notifyUpdatedAuthorizationState
987{
988    [self updateTabViewWithForceUpdate:NO];
989    for (Filter* filter in [filterVertViewFlow rows])
990        [filter notifyUpdatedAuthorizationState];
991    [rulesTableView reloadData];
992}
993
994#pragma mark ------------------------ Sorting
995
996+ (NSString*)ruleActionText:(NSXMLElement*)ruleE
997{
998    switch ([Rule getRuleType:ruleE]) {
999        case BlockRuleType:
1000            return @"block";
1001        case WhitelistRuleType:
1002            return @"whitelist";
1003        case ModifyErrorStatusRuleType:
1004            return @"fix!ok";
1005        case ModifyContentRuleType:
1006            return @"fix";
1007        case KeywordRuleType:
1008            return @"keyword";
1009        case RequestRuleType:
1010            return @"request";
1011        default:
1012            return @"???";
1013    }
1014}
1015
1016static NSString* dotReverse(NSString* s)
1017{
1018    NSArray* a = [s componentsSeparatedByString:@"."];
1019    // missing reverse method.
1020    NSMutableString* sb = [NSMutableString stringWithCapacity:[s length]];
1021    for (NSUInteger i = [a count] - 1; true; i--) {
1022        [sb appendString:[a objectAtIndex:i]];
1023        if (i > 0)
1024            [sb appendString:@"."];
1025        else
1026            break;
1027    }
1028    return sb;
1029}
1030
1031static int getPriority(NSXMLElement* ruleE)
1032{
1033    if (!ruleE)
1034        return 9;
1035    NSString* s = [Xml getAttribute:ruleE withName:@"priority"];
1036    return s ? [s intValue] : 2;
1037}
1038
1039+ (NSString*)getHostSortingKey:(NSXMLElement*)ruleE
1040{
1041    NSString* s = [Xml getAttribute:ruleE withName:@"host"];
1042    if (s)
1043        return dotReverse(s);
1044    s = [Xml getAttribute:ruleE withName:@"keyword"];
1045    if (s)
1046        return [@" 0 " stringByAppendingString:s]; // make sort before normal rules.
1047    return @" 1";
1048}
1049
1050- (NSInteger)compareRule:(NSXMLElement*)rule1E toRule:(NSXMLElement*)rule2E
1051{
1052    for (NSNumber* m in _rulesSortColumnIndexes) {
1053        NSInteger diff = 0;
1054        NSString* a;
1055        NSString* b;
1056        NSNumber* n1;
1057        NSNumber* n2;
1058        switch ([m intValue] / 2) {
1059            case 0: // enable-overwrite
1060                n1 = [_rulesOwnerFilter getRuleSubscriptionState:[Xml getAttribute:rule1E withName:@"rule-id"]];
1061                n2 = [_rulesOwnerFilter getRuleSubscriptionState:[Xml getAttribute:rule2E withName:@"rule-id"]];
1062                diff = [n1 intValue] - [n2 intValue];
1063                break;
1064            case 1: // priority
1065                diff = getPriority(rule1E) - getPriority(rule2E);
1066                break;
1067            case 2: // action
1068                a = [FilterTabController ruleActionText:rule1E];
1069                b = [FilterTabController ruleActionText:rule2E];
1070                diff = [a compare:b];
1071                break;
1072            case 3: // host
1073                a = [FilterTabController getHostSortingKey:rule1E];
1074                b = [FilterTabController getHostSortingKey:rule2E];
1075                diff = [a caseInsensitiveCompare:b];
1076                break;
1077            case 4: // path
1078                a = soft([Xml getAttribute:rule1E withName:@"path"]);
1079                b = soft([Xml getAttribute:rule2E withName:@"path"]);
1080                diff = [a caseInsensitiveCompare:b];
1081                break;
1082            case 5: // comments
1083                a = soft([Xml stringForXPath:@"comments" withRoot:rule1E]);
1084                b = soft([Xml stringForXPath:@"comments" withRoot:rule2E]);
1085                diff = [a caseInsensitiveCompare:b];
1086        }
1087        if (diff)
1088            return ([m intValue] & 1) ? diff : -diff;
1089    }
1090    return 0;
1091}
1092
1093
1094static NSInteger compareRules(id rule1E, id rule2E, void *context)
1095{
1096    FilterTabController* self = (FilterTabController*)context;
1097    return [self compareRule:(NSXMLElement *)rule1E toRule:(NSXMLElement *)rule2E];
1098}
1099
1100- (void)sortRules
1101{
1102    //DebugNSLog(@"sortRules: %@", _rulesSortColumnIndexes);
1103    NSArray* a = [_ruleElementArray sortedArrayUsingFunction:&compareRules context:self];
1104    [_ruleElementArray setArray:a];
1105    [rulesTableView reloadData];
1106}
1107
1108#pragma mark ------------------------ Table Delegate
1109
1110
1111- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
1112{
1113    [self updateTabViewWithForceUpdate:NO];
1114    if (!_rulesOwnerFilter)
1115        return;
1116    NSIndexSet* set = [rulesTableView selectedRowIndexes];
1117    NSMutableArray* a = [NSMutableArray arrayWithCapacity:[set count]];
1118    for (NSInteger row = [set firstIndex]; row != NSNotFound; row = [set indexGreaterThanIndex:row])
1119        [a addObject:[_ruleElementArray objectAtIndex:row]];
1120    [self setSavedRuleSelection:a];
1121}
1122
1123- (NSAttributedString*)textWithStrikeThrough:(NSString*)txt
1124{
1125    NSDictionary *attrsDictionary =[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:1]
1126                                                               forKey:NSStrikethroughStyleAttributeName];
1127    return [[[NSAttributedString alloc] initWithString:txt attributes:attrsDictionary] autorelease];
1128}
1129
1130- (NSString *)tableView:(NSTableView *)aTableView
1131         toolTipForCell:(NSCell *)aCell
1132                   rect:(NSRectPointer)rect
1133            tableColumn:(NSTableColumn *)col
1134                    row:(NSInteger)row
1135          mouseLocation:(NSPoint)mouseLocation
1136{
1137    if (aTableView != rulesTableView || ![_rulesOwnerFilter isSubscription])
1138        return @"";
1139    NSString* cid = [col identifier];
1140    if (![cid isEqual:@"enabled"])
1141        return @"";
1142    NSXMLElement* ruleE = [_ruleElementArray objectAtIndex:row];
1143    NSString* ruleId = [Xml getAttribute:ruleE withName:@"rule-id"];
1144    if (!ruleId)
1145        return @"";
1146    int state = [[_rulesOwnerFilter getRuleSubscriptionState:ruleId] intValue];
1147    BOOL enabled = ![Xml getBoolAttribute:ruleE withName:@"disabled"];
1148    if (state == NSMixedState) {
1149        if (enabled)
1150            return @"Using authors default, which is to enable the rule.";
1151        else
1152            return @"Using authors default, which is to disable the rule.";
1153    }
1154    if (state == NSOffState) {
1155        if (enabled)
1156            return @"You have forced the rule to always be disabled and the authors default to enable the rule is ignored.";
1157        else
1158            return @"You have forced the rule to always be disabled and the authors default to disable the rule is ignored.";
1159    } else {
1160        if (enabled)
1161            return @"You have forced the rule to always be enabled and the authors default to enable the rule is ignored.";
1162        else
1163            return @"You have forced the rule to always be enabled and the authors default to disable the rule is ignored.";
1164    }
1165}
1166
1167#pragma mark ------------------------ SplitView Delegate
1168
1169- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview
1170{
1171    return NO;
1172}
1173
1174- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset
1175{
1176    //DebugNSLog(@"constrainMaxCoordinate: proposedMax = %f,  ofSubviewAt = %d", proposedMax, offset);
1177    return [sender frame].size.height - 144;
1178}
1179
1180- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset
1181{
1182    //DebugNSLog(@"constrainMinCoordinate: proposedMax = %f,  ofSubviewAt = %d", proposedMin, offset);
1183    return 119;
1184}
1185
1186/*
1187- (CGFloat)splitView:(NSSplitView *)sender constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)offset
1188{
1189    NSLog(@"constrainSplitPosition: proposedPosition = %f,  ofSubviewAt = %d", proposedPosition, offset);
1190    return proposedPosition;
1191}
1192*/
1193
1194@end
1195
1196@implementation FilterTabController(NSTableDataSource)
1197
1198- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
1199{
1200    if (tableView == rulesTableView)
1201        return [_ruleElementArray count];
1202    NSLog(@"numberOfRowsInTableView called for unknown NSTableView: %@", tableView);
1203    return 0;
1204}
1205
1206- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)col row:(NSInteger)row
1207{
1208    NSString* cid = [col identifier];
1209    if (tableView == rulesTableView) {
1210        //DebugNSLog(@"  rule.oVfTc(%@), row %d", cid, row);
1211        if ((NSUInteger)row >= [_ruleElementArray count]) {
1212            NSLog(@"Invalid row = %ld in objectValueForTableColumn, has only %ld rules", (long)row, (long)[_ruleElementArray count]);
1213            return NULL;
1214        }
1215        NSXMLElement* ruleE = [_ruleElementArray objectAtIndex:row];
1216        NSString* txt;
1217        if ([cid isEqual:@"enabled"]) {
1218            NSString* ruleId = [Xml getAttribute:ruleE withName:@"rule-id"];
1219            if (!_rulesOwnerFilter || !ruleId)
1220                return [NSNumber numberWithInt:NSMixedState];
1221            if ([_rulesOwnerFilter isSubscription])
1222                return [_rulesOwnerFilter getRuleSubscriptionState:ruleId];
1223            return [NSNumber numberWithInt:![Xml getBoolAttribute:ruleE withName:@"disabled"]];
1224        } else if ([cid isEqual:@"priority"]) {
1225            return [NSString stringWithFormat:@"%d", getPriority(ruleE)]; // NSAttributedString doesn't work with alignment.
1226        } else if ([cid isEqual:@"action"]) {
1227            txt = [FilterTabController ruleActionText:ruleE];
1228            if ([Xml getBoolAttribute:ruleE withName:@"disabled"])
1229                return [self textWithStrikeThrough:txt];
1230        } else if ([cid isEqual:@"host"]) {
1231            return @"dummy";
1232        } else if ([cid isEqual:@"path"]) {
1233            txt = [Xml getAttribute:ruleE withName:@"path"];
1234        } else if ([cid isEqual:@"comments"]) {
1235            txt = [Xml stringForXPath:@"comments" withRoot:ruleE];
1236            if (!txt)
1237                txt = @"";
1238            NSRange range = [txt rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]];
1239            if (range.location != NSNotFound)
1240                txt = [txt substringToIndex:range.location];
1241        } else {
1242            NSLog(@"objectValueForTableColumn: Unknown rule column: %@", cid);
1243            return NULL;
1244        }
1245        if (txt && [_rulesOwnerFilter safetySimpleRulesOnly] && ![Rule ruleIsSafe:ruleE])
1246            return [self textWithStrikeThrough:txt];
1247        else
1248            return txt;
1249    }
1250    NSLog(@"objectValueForTableColumn called for unknown NSTableView: %@", tableView);
1251    return NULL;
1252}
1253
1254static NSColor* green;
1255static NSColor* red;
1256static NSColor* black;
1257
1258- (void)tableView:(NSTableView*)aTableView
1259  willDisplayCell:(id)aCell
1260   forTableColumn:(NSTableColumn *)aTableColumn
1261              row:(NSInteger)row
1262{
1263    if (aTableView != rulesTableView)
1264        return;
1265    if ([aCell isKindOfClass:[NSButtonCell class]]) {
1266        [aCell setEnabled:[_prefs hasGlimmerAuth]];
1267        [aCell setAllowsMixedState:[_rulesOwnerFilter isSubscription]];
1268        return;
1269    }
1270    if ([aCell isKindOfClass:[HostTextFieldCell class]]) {
1271        HostTextFieldCell* htfc = (HostTextFieldCell*)aCell;
1272        NSXMLElement* ruleE = [_ruleElementArray objectAtIndex:row];
1273        [htfc setRuleE:ruleE filter:_rulesOwnerFilter];
1274    }
1275    if (![aCell isKindOfClass:[NSTextFieldCell class]])
1276        return;
1277    if (!green) {
1278        green = [[NSColor colorWithDeviceRed:0 green:0.75f blue:0 alpha:1] retain];
1279        red = [[NSColor redColor] retain];
1280        black = [[NSColor blackColor] retain];
1281    }
1282    NSColor* color = NULL;
1283    if (_rulesOwnerFilter && [_rulesOwnerFilter isSubscription]) {
1284        NSXMLElement* ruleE = [_ruleElementArray objectAtIndex:row];
1285        NSString* ruleId = [Xml getAttribute:ruleE withName:@"rule-id"];
1286        if ([_rulesOwnerFilter safetySimpleRulesOnly] && ![Rule ruleIsSafe:ruleE])
1287            color = [NSColor grayColor];
1288        else if (ruleId) {
1289            switch ([[_rulesOwnerFilter getRuleSubscriptionState:ruleId] intValue]) {
1290                case NSOnState:
1291                    color = green;
1292                    break;
1293                case NSOffState:
1294                    color = red;
1295                    break;
1296            }
1297        }
1298    }
1299    [aCell setTextColor:(color ? color : black)];
1300}
1301
1302
1303- (void)tableView:(NSTableView *)aTableView
1304   setObjectValue:(id)value
1305   forTableColumn:(NSTableColumn *)aTableColumn
1306              row:(NSInteger)row
1307{
1308    //DebugNSLog(@"setObjectValue(%@) for col %@ row %d", value, [aTableColumn identifier], row);
1309    NSXMLElement* ruleE = [_ruleElementArray objectAtIndex:row];
1310    NSString* ruleId = [Xml getAttribute:ruleE withName:@"rule-id"];
1311    //DebugNSLog(@"  ruleId = %@, owner = %x, class = %d", ruleId, _rulesOwnerFilter, [value isKindOfClass:[NSNumber class]]);
1312    if (!_rulesOwnerFilter || !ruleId || ![value isKindOfClass:[NSNumber class]])
1313        return;
1314    if ([_rulesOwnerFilter isSubscription]) {
1315        [_rulesOwnerFilter setRuleSubscriptionState:value forRuleId:ruleId];
1316    } else {
1317        [Xml setAttribute:([value intValue] ? NULL : @"1") withName:@"disabled" inElement:ruleE];
1318        [_prefs markAsDirty];
1319        [_rulesOwnerFilter notifyDirty];
1320        [_comm saveDirtyPrefsAndNotify];
1321    }
1322    [rulesTableView setNeedsDisplayInRect:[rulesTableView rectOfRow:row]];
1323}
1324
1325- (void)tableView:(NSTableView *)tableView didClickTableColumn:(NSTableColumn *)col
1326{
1327    if (tableView != rulesTableView)
1328        return;
1329    BOOL ascending = YES;
1330    if ([_rulesSortColumnIndexes count]) {
1331        int v = [[_rulesSortColumnIndexes objectAtIndex:0] intValue];
1332        NSTableColumn* currentCol = [[rulesTableView tableColumns] objectAtIndex:v/2];
1333        if (currentCol == col && (v & 1))
1334            ascending = NO;
1335    }
1336    [self addSortingColumn:[col identifier] isAscending:ascending forUpdate:YES];
1337    [self updateRuleSortingIcon];
1338    [self sortRules];
1339    [_prefs markAsDirty];
1340}
1341
1342- (BOOL)tableView:(NSTableView*)tableView writeRowsWithIndexes:(NSIndexSet*)rowIndexes toPasteboard:(NSPasteboard*)pb
1343{
1344    if (tableView != rulesTableView)
1345        return NO;
1346    DebugNSLog(@"writeRowsWithIndexes for rulesTableView");
1347    // Copy the row numbers to the pasteboard.
1348    [rulesTableView copyRows:rowIndexes toPasteboard:pb];
1349    return YES;
1350}
1351
1352- (NSDragOperation)tableView:(NSTableView*)tableView
1353                validateDrop:(id <NSDraggingInfo>)info
1354                 proposedRow:(NSInteger)row
1355       proposedDropOperation:(NSTableViewDropOperation)operation
1356{
1357    if (tableView != rulesTableView || operation != NSTableViewDropAbove || [info draggingSource] == rulesTableView)
1358        return NSDragOperationNone;
1359    //DebugNSLog(@"validate Drop, info: %@", info);
1360    if (![rulesTableView canModifyRules])
1361        return NSDragOperationNone;
1362    NSData* data = [rulesTableView getValidDataFromPasteboard:[info draggingPasteboard]];
1363    return data ? NSDragOperationCopy : NSDragOperationNone;
1364}
1365
1366- (BOOL)tableView:(NSTableView *)tableView
1367       acceptDrop:(id <NSDraggingInfo>)info
1368              row:(NSInteger)row
1369    dropOperation:(NSTableViewDropOperation)operation
1370{
1371    if (tableView != rulesTableView || operation != NSTableViewDropAbove || [info draggingSource] == rulesTableView)
1372        return NSDragOperationNone;
1373    DebugNSLog(@"acceptDrop: %@", info);
1374    if (![rulesTableView canModifyRules])
1375        return NSDragOperationNone;
1376    NSData* data = [rulesTableView getValidDataFromPasteboard:[info draggingPasteboard]];
1377    if (!data)
1378        return NO;
1379    [rulesTableView insertRuleData:data];
1380    return YES;
1381}
1382
1383@end
Note: See TracBrowser for help on using the repository browser.