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

Revision 1015, 31.5 KB checked in by speck, 10 months ago (diff)

Lots of changes for xcode 4.1 (added delegate declarations, fixed deprecated function calls, upgraded SDK and platforms).

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 "Filter.h"
18#import "GBDebug.h"
19#import "AlertHelper.h"
20#import "Prefs.h"
21#import "XmlDoc.h"
22#import "Xml.h"
23#import "ServerCommunication.h"
24#import "FilterTabController.h"
25#import "FilterPublisher.h"
26#import "FilterListView.h"
27
28NSString* updatePolicies[] = {
29    @"auto", @"notify", @"manual", NULL
30};
31
32NSString* safetyPolicies[] = {
33    @"simple", @"modification", NULL
34};
35
36//==============================================================================================
37
38@interface LabelTextField : NSTextField
39{
40    Filter* _filter;
41}
42
43- (id)initWithFrame:(NSRect)frameRect
44         withFilter:(Filter*)filter;
45- (void)dealloc;
46
47@end
48
49@implementation LabelTextField
50
51- (id)initWithFrame:(NSRect)frameRect
52         withFilter:(Filter*)filter
53{
54    if (!(self = [super initWithFrame:frameRect]))
55        return nil;
56    _filter = filter; // not retained, as it would create a ref-counter loop.
57    return self;
58}
59
60- (void)dealloc  // clang-sa wants a dealloc even though there's nothing to do.
61{
62    [super dealloc];
63}
64
65- (void)textDidEndEditing:(NSNotification *)aNotification
66{
67    NSString*s = [self stringValue];
68    [_filter endEditing];
69    if ([s length]) {
70        [_filter setName:s];
71        [[_filter comm] saveDirtyPrefsAndNotify];
72    }
73    [_filter makeTableFirstResponder];
74}
75
76@end
77
78//==============================================================================================
79
80@interface Filter()
81
82- (NSString*) getPathArgs:(NSString*)cmd;
83- (void)updateViews;
84
85- (void)releaseUpdateWithCancel:(BOOL)cancel;
86- (void)makeGlobeImageView:(NSImage*)image;
87
88- (void)enabledAction:(id)sender;
89- (void)clickedGlobe:(id)sender;
90
91- (void)updateEditingSubscriptionDates;
92-(void)startEditingSubscriptionOneShot:(id)sender;
93@end
94
95@implementation Filter
96
97- (id)initWithFilterElement:(NSXMLElement*)filterE
98                   withComm:(ServerCommunication*)comm
99          withTabController:(FilterTabController*)tabController
100         withFilterListView:(FilterListView*)filterListView
101                    atIndex:(NSUInteger)idx
102{
103    if (!(self = [super initWithFrame:NSMakeRect(0, 0, 265, 19)]))
104        return nil;
105    _comm = [comm retain];
106    _prefs = [[comm prefs] retain];
107    _alertHelper = [[_prefs alertHelper] retain];
108    _filterE = [filterE retain];
109    _filterId = [[Xml getAttribute:_filterE withName:@"id"] intValue];
110    _url = [[Xml getAttribute:_filterE withName:@"url"] retain];
111    _filterTabController = tabController; // not retained as it always lives longer.
112    if ([[filterE elementsForName:@"filter-data"] count]) {
113        _isSubscription = NO;
114        NSArray* a = [_filterE elementsForName:@"filter-data"];
115        if ([a count] != 1) {
116            NSLog(@"Invalid <filter> in panel settings: %@", filterE);
117            [self release];
118            return nil;
119        }
120        _filterDataE = [[a objectAtIndex:0] retain];
121    } else {
122        _isSubscription = YES;
123        NSString* pathArgs = [self getPathArgs:@"/filters/subscription/get"];
124        DebugNSLog(@"Getting sub info: %@", pathArgs);
125        XmlDoc* doc = [_comm curlToXml:pathArgs withActionDescription:NULL];
126        if (!doc) {
127            _filterContainerE = [[Xml createElementWithName:@"filter-container"] retain];
128            _filterDataE = [[Xml createElementWithName:@"filter-data"] retain];
129            [Xml setAttribute:@"(Subscription not downloaded)" withName:@"name" inElement:_filterDataE];
130            _subscriptionIsMissingDownload = YES;
131        } else {
132            _filterContainerE = [[doc byXPath:@"filter-container"] retain];
133            _filterDataE = [[doc byXPath:@"filter-container/filter-data"] retain];
134            if (!_filterDataE) {
135                NSLog(@"Invalid subscription xml: missing filter-data root child.");
136                [self release];
137                return nil;
138            }
139        }
140        _subscriptionRuleStates = [[NSMutableDictionary dictionaryWithCapacity:10] retain];
141    }
142    [self readAttributesFromXml];
143    if (_isSubscription) {
144        _subscriptionDataFilePath = [[NSString stringWithFormat:@"/Library/GlimmerBlocker/Filter subscriptions/%d.xml", _filterId] retain];
145        NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:_subscriptionDataFilePath error:NULL];
146        _subscriptionDataFileModdate = [[attrs objectForKey:NSFileModificationDate] timeIntervalSince1970];
147    }
148    _filterListView = [filterListView retain];
149    [_filterListView addRow:self atIndex:idx];
150    //
151    _checkbox = [[NSButton alloc] initWithFrame:NSMakeRect(3, 1, 16, 16)]; // size by mag.glass
152    [_checkbox setButtonType:NSSwitchButton];
153    [_checkbox setTitle:@""];
154    [_checkbox setTarget:self];
155    [_checkbox setAction:@selector(enabledAction:)];
156    [self addSubview:_checkbox];
157    //
158    if (![self isSubscription]) {
159        [self updateViews];
160        return self;
161    }
162    //
163    _warningButton = [[NSButton alloc] initWithFrame:NSMakeRect(226, 2, 15, 15)];
164    [_warningButton setImagePosition:NSImageOnly];
165    [_warningButton setButtonType:NSMomentaryLightButton];
166    [_warningButton setBordered:NO];
167    [_warningButton setImage:[_filterTabController warningImage]];
168    [self addSubview:_warningButton];
169    [_warningButton setHidden:!_updateError];
170    [_warningButton setTarget:self];
171    [_warningButton setAction:@selector(startEditingSubscriptionOneShot:)];
172    [_warningButton sendActionOn:NSLeftMouseDownMask];
173    //
174    _spinner = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(225, 1, 17, 17)];
175    [_spinner setStyle:NSProgressIndicatorSpinningStyle];
176    [_spinner setControlSize:NSSmallControlSize];
177    [_spinner setDisplayedWhenStopped:NO];
178    [_spinner sizeToFit];
179    //DebugNSLog(@"spinner size: %@", NSStringFromSize([_spinner frame].size));
180    [self addSubview:_spinner];
181//  if (idx == 1)
182//      [self performSelector:@selector(startSpinner) withObject:NULL afterDelay:0.01];
183    // don't know why it must be delayed.
184    [self updateViews];
185    return self;
186}
187
188- (void)startSpinner
189{
190    [_spinner startAnimation:self];
191}
192
193- (void)dealloc
194{
195    DebugNSLog(@"Deallocs filter: %@", _name);
196    [self releaseUpdateWithCancel:YES];
197    [_filterListView removeRow:self];
198    [_alertHelper release];
199    [_prefs release];
200    [_name release];
201    [_comments release];
202    [_description release];
203    [_filterE release];
204    [_filterContainerE release];
205    [_filterDataE release];
206    [_url release];
207    [_updateError release];
208    [_subscriptionRuleStates release];
209    [_subscriptionDataFilePath release];
210    _filterTabController = NULL; // not retained
211    //
212    [_spinner release];
213    [_globeButton release];
214    [_warningButton release];
215    [_label release];
216    [_checkbox release];
217    [_filterListView release];
218    //
219    [editSheet release];
220    [super dealloc];
221}
222
223- (void)makeGlobeImageView:(NSImage*)image
224{
225    if ([_globeButton image] == image)
226        return;
227    if (!_globeButton) {
228        _globeButton = [[NSButton alloc] initWithFrame:NSMakeRect(245, 1, 17, 17)];
229        [_globeButton setImagePosition:NSImageOnly];
230        [_globeButton setButtonType:NSMomentaryLightButton];
231        [_globeButton setBordered:NO];
232        [self addSubview:_globeButton];
233        [_globeButton setTarget:self];
234        [_globeButton setAction:@selector(clickedGlobe:)];
235        [_globeButton sendActionOn:NSLeftMouseDownMask];
236    }
237    [_globeButton setImage:image];
238}
239
240- (NSImage*)getInfoImage
241{
242    if (_updateUrl)
243        return NULL;
244    if (_updateError)
245        return [_filterTabController warningImage];
246    if (_filterContainerE && [[Xml nodesForXPath:@"available-update-last-modified" withRoot:_filterContainerE] count])
247        return [_filterTabController updateAvailableImage];
248    return NULL;
249}
250
251- (void)updateViews
252{
253    [_checkbox setEnabled:[_prefs hasGlimmerAuth]];
254    [_checkbox setState:[self enabled]];
255    NSImage* infoImage = [self getInfoImage];
256    [_warningButton setHidden:!infoImage];
257    if (infoImage)
258        [_warningButton setImage:infoImage];
259    if (_updateUrl)
260        [_spinner startAnimation:self];
261    else
262        [_spinner stopAnimation:self];
263}
264
265- (void)decodeSubscriptionRuleStates:(NSString*)encoded withState:(NSNumber*)state
266{
267    if (![encoded length])
268        return;
269    for (NSString* s in [encoded componentsSeparatedByString:@";"])
270        [_subscriptionRuleStates setObject:state forKey:s];
271}
272
273- (void)readSubscriptionRuleStates
274{
275    if (![self isSubscription])
276        return;
277    [_subscriptionRuleStates removeAllObjects];
278    [self decodeSubscriptionRuleStates:[Xml getAttribute:_filterE withName:@"rules-enabled"]
279                             withState:[NSNumber numberWithInt:NSOnState]];
280    [self decodeSubscriptionRuleStates:[Xml getAttribute:_filterE withName:@"rules-disabled"]
281                             withState:[NSNumber numberWithInt:NSOffState]];
282}
283
284- (void)setUpdateError:(NSString*)s
285{
286    DebugNSLog(@"setUpdateError(%@): %@", _name, s);
287    [_updateError release];
288    _updateError = [s retain];
289}
290
291- (void)readAttributesFromXml
292{
293    _enabled = [[Xml getAttribute:_filterE withName:@"enabled"] intValue];
294    //
295    [_name release];
296    _name = [[Xml getAttribute:_filterDataE withName:@"name"] retain];
297    //
298    [_description release];
299    _description = [Xml stringForXPath:@"description" withRoot:_filterDataE];
300    if (![_description length])
301        _description = [Xml getAttribute:_filterDataE withName:@"description"]; // old format stores comments in attribute.
302    [_description retain];
303    //
304    [_comments release];
305    _comments = [Xml stringForXPath:@"comments" withRoot:_filterE];
306    if (![_comments length])
307        _comments = [Xml getAttribute:_filterE withName:@"comments"]; // old format stores comments in attribute.
308    [_comments retain];
309    //
310    _safetySimpleRulesOnly = _isSubscription && ![[Xml getAttribute:_filterE withName:@"safety-policy"] isEqual:@"modification"];
311    //
312    [self readSubscriptionRuleStates];
313    if ([self isSubscription]) {
314        [self makeGlobeImageView:[_filterTabController subscriptionGlobeImage]];
315        NSString* tip = [NSString stringWithFormat:@"Subscription:\n%@", [self url]];
316        NSString* s = [Xml dateFromMillisecsXPath:@"last-modified/@msecs" withRoot:_filterContainerE];
317        if (s)
318            tip = [NSString stringWithFormat:@"%@\n\nLast modification by author:\n        %@", tip, s];
319        s = [Xml dateFromMillisecsXPath:@"last-update-check/@msecs" withRoot:_filterContainerE];
320        if (s)
321            tip = [NSString stringWithFormat:@"%@\nLast check for updates:\n        %@", tip, s];
322        tip = [tip stringByReplacingOccurrencesOfString:@" " withString:@"\u00a0"]; // tried to avoid line breaking...
323        [_globeButton setToolTip:tip];
324        [self setUpdateError:[Xml getAttribute:_filterContainerE withName:@"network-error"]];
325    } else if ([self url]) {
326        if ([self isDirty])
327            [self makeGlobeImageView:[_filterTabController publishGlobeDirtyImage]];
328        else
329            [self makeGlobeImageView:[_filterTabController publishGlobeImage]];
330        NSString* tip = [NSString stringWithFormat:@"Published at:\n%@", [self url]];
331        NSString* s = [Xml dateFromMillisecsXPath:@"last-publish-date/@msecs" withRoot:_filterE];
332        if (s)
333            tip = [NSString stringWithFormat:@"%@\n\nTime of latest publication:\n        %@", tip, s];
334        s = [Xml dateFromMillisecsXPath:@"first-dirty-date/@msecs" withRoot:_filterE];
335        if (s)
336            tip = [NSString stringWithFormat:@"%@\nHas unpublished changes since:\n        %@", tip, s];
337        tip = [tip stringByReplacingOccurrencesOfString:@" " withString:@"\u00a0"]; // tried to avoid line breaking...
338        [_globeButton setToolTip:tip];
339    } else {
340        [_globeButton setToolTip:@""];
341    }
342    //
343    [self updateViews];
344}
345
346- (NSNumber*)getRuleSubscriptionState:(NSString*)ruleId
347{
348    NSNumber* n = [_subscriptionRuleStates objectForKey:ruleId];
349    return n ? n : [NSNumber numberWithInt:NSMixedState];
350}
351
352- (void)setRuleSubscriptionState:(NSNumber*)state forRuleId:(NSString*)ruleId
353{
354    if ([state intValue] == NSMixedState)
355        [_subscriptionRuleStates removeObjectForKey:ruleId];
356    else
357        [_subscriptionRuleStates setObject:state forKey:ruleId];
358    NSMutableString* enabled = [NSMutableString stringWithCapacity:100];
359    NSMutableString* disabled = [NSMutableString stringWithCapacity:100];
360    for (NSXMLElement* ruleE in [[self filterDataE] elementsForName:@"rule"]) {
361        ruleId = [Xml getAttribute:ruleE withName:@"rule-id"];
362        if (!ruleId)
363            continue;
364        state = [_subscriptionRuleStates objectForKey:ruleId];
365        if (!state)
366            continue;
367        NSMutableString* ms = ([state intValue] == NSOnState) ? enabled : disabled;
368        if ([ms length])
369            [ms appendString:@";"];
370        [ms appendString:ruleId];
371    }
372    [Xml setAttribute:enabled withName:@"rules-enabled" inElement:_filterE];
373    [Xml setAttribute:disabled withName:@"rules-disabled" inElement:_filterE];
374    [_prefs markAsDirty];
375    [_comm saveDirtyPrefsAndNotify];
376}
377
378//---------------------------------------
379- (BOOL)deleteFilter
380{
381    if (![self isSubscription]) {
382        [_filterE detach];
383        [_filterListView removeRow:self];
384        [_prefs markAsDirty];
385        return YES;
386    }
387    if (![_comm isServerRunning]) {
388        [_alertHelper prepareWarningAlertWithTitle:@"Can't delete subscriptions when GlimmerBlocker is not activated"
389                               withInformativeText:@"You must activate GlimmerBlocker before deleting the filter"];
390        [_alertHelper showPreparedAlert];
391        return NO;
392    }
393    NSString* pathArgs = [self getPathArgs:@"!/filters/subscription/remove"];
394    XmlDoc* doc = [_comm curlToXml:pathArgs withActionDescription:@"Failed deleting subscription"];
395    if (!doc)
396        return NO;
397    [_filterE detach];
398    [_filterListView removeRow:self];
399    [_prefs markAsDirty];
400    [_comm saveDirtyPrefsAndNotify];
401    return YES;
402}
403
404//---------------------------------------
405
406- (Prefs*)prefs
407{
408    return _prefs;
409}
410
411- (ServerCommunication*)comm
412{
413    return _comm;
414}
415
416- (NSXMLElement*)filterE
417{
418    return _filterE;
419}
420
421- (NSXMLElement*)filterDataE
422{
423    return _filterDataE;
424}
425
426- (NSXMLElement*)ruleWithId:(int)ruleId
427{
428    if (!_filterDataE)
429        return NULL;
430    for (NSXMLElement* ruleE in [_filterDataE elementsForName:@"rule"]) {
431        if ([Xml getIntAttribute:ruleE withName:@"rule-id"] == ruleId)
432            return ruleE;
433    }
434    return NULL;
435}
436
437- (BOOL)isDirty
438{
439    if (![self url] || [self isSubscription])
440        return NO;
441    return [[_filterE elementsForName:@"first-dirty-date"] count] > 0;
442}
443
444- (void)notifyDirty
445{
446    [_prefs markAsDirty];
447    if (![self url] || [self isSubscription])
448        return;
449    [Xml singletonDateElement:[NSDate date] forXPath:@"first-dirty-date" fromParent:_filterE updateExisting:NO];
450    [self readAttributesFromXml];
451}
452
453- (void)notifyPublished
454{
455    NSAssert([self url] && ![self isSubscription], @"is-subscription");
456    for (NSXMLElement* e in [_filterE elementsForName:@"first-dirty-date"])
457        [e detach];
458    [Xml singletonDateElement:[NSDate date] forXPath:@"last-publish-date" fromParent:_filterE updateExisting:YES];
459}
460
461- (void)notifyUpdatedAuthorizationState
462{
463    [self updateViews];
464}
465//---------------------------------------
466
467- (BOOL)enabled
468{
469    return _enabled;
470}
471
472- (void)filterEnabled:(BOOL)enabled
473{
474    if (enabled == _enabled)
475        return;
476    _enabled = enabled;
477    [Xml setAttribute:(enabled ? @"1" : @"") withName:@"enabled" inElement:_filterE];
478    [_prefs markAsDirty];
479}
480
481- (void)enabledAction:(id)sender
482{
483    [self filterEnabled:[sender state]];
484    [_filterTabController updateTabViewWithForceUpdate:NO];
485    [_comm saveDirtyPrefsAndNotify];
486    if ([sender state]) {
487        // make the server check for an update if the filter hasn't been updated for a while.
488        NSURLResponse* response = NULL;
489        [_comm curlToData:@"!/filters/subscription/check-updates?softcheck=1" withResponse:&response withErrorRef:NULL];
490    }
491}
492//---------------------------------------
493
494- (int)filterId
495{
496    return _filterId;
497}
498
499//---------------------------------------
500
501- (NSString*)name
502{
503    return _name ? _name : @"";
504}
505
506- (void)setName:(NSString*)s
507{
508    NSAssert([self canEditName], @"setName without canEditName");
509    if ([s isEqual:[self name]])
510        return;
511    [_name release];
512    _name = [s retain];
513    [_label setStringValue:_name];
514    [Xml setAttribute:_name withName:@"name" inElement:_filterDataE];
515    [self notifyDirty];
516}
517
518//---------------------------------------
519
520- (NSString*)description
521{
522    return _description ? _description : @"";
523}
524
525//---------------------------------------
526
527- (NSString*)comments
528{
529    return _comments ? _comments : @"";
530}
531
532- (void)setComments:(NSString*)s
533{
534    NSAssert([self canEditComments], @"setComments without canEditComments");
535    if ([s isEqual:[self comments]])
536        return;
537    [_comments release];
538    _comments = [s retain];
539    [Xml setCData:_comments forXPath:@"comments" withRoot:_filterE];
540    [Xml setAttribute:NULL withName:@"comments" inElement:_filterE]; // storing in attribute is old format.
541    [self notifyDirty];
542}
543
544//---------------------------------------
545
546- (BOOL)isSubscription
547{
548    return _isSubscription;
549}
550
551+ (NSString*)extractHostFromStringUrl:(NSString*)url
552{
553    NSString* host;
554    if ([url hasPrefix:@"http://"])
555        host = [url substringFromIndex:7];
556    else if ([url hasPrefix:@"https://"])
557        host = [url substringFromIndex:8];
558    else
559        return NULL;
560    NSInteger len = [host length];
561    for (int i = 0; i < len; i++) {
562        unichar ch = [host characterAtIndex:i];
563        if (ch == ':' || ch == '/')
564            return [host substringToIndex:i];
565    }
566    return host;
567}
568
569- (NSString*)publishHost
570{
571    return _isSubscription ? NULL : [Filter extractHostFromStringUrl:_url];
572}
573
574- (BOOL)isPublished
575{
576    return !_isSubscription && _url;
577}
578
579- (BOOL)isMobileMePublished
580{
581    NSString* host = [self publishHost];
582    return [host isEqual:@"idisk.mac.com"] || [host isEqual:@"idisk.me.com"];
583}
584
585- (BOOL)isWebDavPublished
586{
587    return [self publishHost] && ![self isMobileMePublished];
588}
589
590-(BOOL)safetySimpleRulesOnly
591{
592    return _safetySimpleRulesOnly;
593}
594
595- (NSString*)url
596{
597    return _url;
598}
599
600- (void)setUrl:(NSString*)url
601{
602    [_url release];
603    _url = [url retain];
604    [Xml setAttribute:url withName:@"url" inElement:_filterE];
605    [self notifyDirty];
606    [self setNeedsDisplay:YES];
607}
608
609- (BOOL)canEditName
610{
611    return ![self isSubscription];
612}
613
614- (BOOL)canEditComments
615{
616    return ![self isSubscription];
617}
618
619- (BOOL)canEditRules
620{
621    return ![self isSubscription];
622}
623
624- (BOOL)canDelete
625{
626    NSString* url = [Xml getAttribute:_filterE withName:@"url"];
627    return !url || ![url hasPrefix:@"http://glimmerblocker.org/site/filters/default/"];
628}
629
630- (void)startEditingPart2
631{
632    [[self window] makeFirstResponder:_label];
633}
634
635- (void)startEditingName
636{
637    if (_label)
638        return;
639    [_filterListView setSelectionByView:self];
640    // must be 1 pixel to the left of the drawString.
641    _label = [[LabelTextField alloc] initWithFrame:NSMakeRect(25, 2, 200, 17) withFilter:self];
642    [_label setEditable:YES];
643    [_label setSelectable:YES];
644    [_label setDrawsBackground:YES];
645    [_label setBezeled:NO];
646    [_label setBordered:YES]; // If the border is omitted, the focus frame is not drawn????
647    [_label setAlignment:NSLeftTextAlignment];
648    [_label setStringValue:[self name]];
649    [_label setDelegate:self];
650    [self addSubview:_label];
651    [self performSelector:@selector(startEditingPart2) withObject:NULL afterDelay:0.1];
652}
653
654- (BOOL)isEditing
655{
656    return !!_label;
657}
658
659- (void)endEditing
660{
661    if (!_label)
662        return;
663    DebugNSLog(@"Filter endEditing");
664    [_label removeFromSuperview];
665    [_label release];
666    _label = NULL;
667}
668
669- (BOOL)control:(NSControl*)control
670       textView:(NSTextView*)fieldEditor doCommandBySelector:(SEL)commandSelector
671{
672    if (_label && commandSelector == @selector(insertNewline:)) {
673        [self endEditing];
674        return YES;
675    }
676    return NO;
677}
678
679- (void)drawLabelUsingFocusColor:(BOOL)usingFocusColor
680{
681    NSMutableDictionary* attrs = [NSMutableDictionary dictionaryWithCapacity:3];
682    [attrs setObject:[NSFont systemFontOfSize:[NSFont systemFontSize]] forKey:NSFontAttributeName];
683    NSColor* color;
684    if (usingFocusColor)
685        color = [NSColor alternateSelectedControlTextColor];
686    else if (_subscriptionIsMissingDownload)
687        color = [NSColor redColor];
688    else
689        color = [NSColor blackColor];
690    [attrs setObject:color forKey:NSForegroundColorAttributeName];
691    NSRect labelRect = NSMakeRect(26, 0, 200, 19);
692    NSString* s = [self name];
693    [s drawInRect:labelRect withAttributes:attrs];
694}
695
696- (void)drawRect:(NSRect)r
697{
698    [super drawRect:r];
699    if (!_label)
700        [self drawLabelUsingFocusColor:[self shouldDrawUsingFocusColor]];
701}
702
703- (void)mouseUp:(NSEvent *)theEvent
704{
705    NSTimeInterval dur = [theEvent timestamp] - _lastMouseDownTime;
706    if (dur > 1) // any NSxxx ?
707        return;
708    if ([self isSubscription]) {
709        if (_mouseUpIsDoubleClick)
710            [self startEditingSubscription:NO];
711        return;
712    }
713    if (_mouseUpCanStartEditing && [self canEditName] && [_prefs hasGlimmerAuth])
714        [self startEditingName];
715}
716
717+ (NSString*)draggingDataType
718{
719    return @"dk.vitality.glimmerblocker.filter";
720}
721
722- (void)mouseDown:(NSEvent *)theEvent
723{
724    //DebugNSLog(@"mouseDown: %@", theEvent);
725    _lastMouseDownTime = [theEvent timestamp];
726    BOOL tableWasFirstResponder = [_filterListView isFirstResponder];
727    [[self window] makeFirstResponder:_filterListView];
728    BOOL wasSelected = [self selected];
729    [_filterListView setSelectionByView:self];
730    _mouseClickLocation = [self convertPointFromBase:[theEvent locationInWindow]];
731    NSUInteger cc = [theEvent clickCount];
732    _mouseUpCanStartEditing = (cc == 2 || (cc == 1 && wasSelected && tableWasFirstResponder));
733    _mouseUpIsDoubleClick = (cc == 2);
734}
735
736- (void)mouseDragged:(NSEvent *)theEvent
737{
738    //DebugNSLog(@"Mouse dragged: %@", theEvent);
739    NSPoint pos = [self convertPointFromBase:[theEvent locationInWindow]];
740    if (ABS(pos.x - _mouseClickLocation.x) >= 10 || ABS(pos.y - _mouseClickLocation.y) < 5)
741        return;
742    DebugNSLog(@"Starts drag");
743    // Make image snapshot
744    NSSize imgSize = [self bounds].size;
745    NSImage* img2 = [[[NSImage alloc] initWithSize:imgSize] autorelease];
746    [img2 lockFocus];
747    [self drawLabelUsingFocusColor:NO];
748    [img2 unlockFocus];
749    // Lighten it up, so it seems transparent
750    NSImage* image = [[[NSImage alloc] initWithSize:imgSize] autorelease];
751    [image lockFocus];
752    [img2 compositeToPoint:NSMakePoint(0, 0) operation:NSCompositePlusLighter fraction:0.5f];
753    [image unlockFocus];
754    // Start the dragging operation
755    [_filterListView startDrag:self withImage:image withEvent:theEvent];
756}
757
758- (void)makeTableFirstResponder
759{
760    //DebugNSLog(@"makeTableFirstResponder");
761    [[self window] makeFirstResponder:_filterListView];
762}
763
764- (void)releaseUpdateWithCancel:(BOOL)cancel
765{
766    if (!_updateUrl)
767        return;
768    if (cancel)
769        [_updateConnection cancel];
770    [_updateConnection release];
771    _updateConnection = NULL;
772    [_updateData release];
773    _updateData = NULL;
774    [_updateUrl release];
775    _updateUrl = NULL;
776}
777
778- (NSString*) getPathArgs:(NSString*)cmd
779{
780    NSString* url = [Xml getAttribute:_filterE withName:@"url"];
781    NSAssert(url, @"Must be a subscription");
782    return [NSString stringWithFormat:@"%@?filter-id=%d", cmd, _filterId];
783}
784
785- (void)startUpdateSubscription
786{
787    if (_updateUrl) {
788        NSLog(@"Ignores update-subscription request as already updating it: %@", _url);
789        return;
790    }
791    NSLog(@"Update subscription: %@", _url);
792    NSAssert([self isSubscription], @"Not a subscription");
793    NSString* pathArgs = [self getPathArgs:@"!/filters/subscription/update"];
794    _updateUrl = [[NSURL URLWithString:[_comm getAdminUrl:pathArgs]] retain];
795    NSURLRequest* req = [NSURLRequest requestWithURL:_updateUrl
796                                         cachePolicy:NSURLRequestReloadIgnoringLocalCacheData // want fresh data
797                                     timeoutInterval:60.0];
798    _updateData = [[NSMutableData data] retain];
799    _updateConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self];
800    if (!_updateConnection) {
801        [self releaseUpdateWithCancel:NO];
802        [self setUpdateError:@"Communication with proxy failed."];
803    }
804    [self updateViews];
805}
806
807- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
808{
809    // called for each response, i.e. for each redirect
810    DebugNSLog(@"FilterSub.didReceiveResponse: %@", response);
811    [_updateData setLength:0];
812}
813
814- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
815{
816    [_updateData appendData:data];
817}
818
819- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
820{
821    NSString* a = [error localizedDescription];
822    NSString* b = [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey];
823    [self setUpdateError:[NSString stringWithFormat:@"%@ %@", a, b]];
824    DebugNSLog(@"FilterSub.didFailWithError: %@", _updateError);
825    [self releaseUpdateWithCancel:NO];
826    [self updateViews];
827}
828
829- (void)didGetNewSubscriptionData
830{
831    _subscriptionIsMissingDownload = NO;
832    [self readAttributesFromXml];
833    [_filterTabController notifyUpdatedFilter:self];
834    [self updateEditingSubscriptionDates];
835}
836
837- (void)decodeUpdateResponse
838{
839    XmlDoc* doc = [[[XmlDoc alloc] initWithURL:_updateUrl withData:_updateData] autorelease];
840    if (![doc rootE]) {
841        [self setUpdateError:@"Could not get proper xml response from server"];
842        return;
843    }
844    [self setNeedsDisplay:YES];
845    NSString* s = [Xml getAttribute:[doc rootE] withName:@"error"];
846    if (s) {
847        [self setUpdateError:s];
848        return;
849    }
850    NSXMLElement* filterContainerE = [doc byXPath:@"filter-container"];
851    NSXMLElement* filterDataE = [doc byXPath:@"filter-container/filter-data"];
852    if (!filterContainerE || !filterDataE)
853        return; // not updated.
854    [_filterDataE release];
855    _filterDataE = [filterDataE retain];
856    [_filterContainerE release];
857    _filterContainerE = [filterContainerE retain];
858    [_prefs markAsDirty];
859    [self didGetNewSubscriptionData];
860}
861
862- (void)connectionDidFinishLoading:(NSURLConnection *)connection
863{
864    [self setUpdateError:NULL];
865    [self decodeUpdateResponse];
866    [self releaseUpdateWithCancel:NO];
867    [self updateViews];
868}
869
870- (void)clickedGlobeAlertCallbackWithReturnCode:(NSInteger)returnCode
871{
872    if (returnCode == NSAlertFirstButtonReturn)
873        [[[FilterPublisher alloc] initWithFilterTabController:_filterTabController
874                                                   withFilter:self
875                                                usingMobileMe:[self isMobileMePublished]] autorelease];
876}
877
878// callback from ImageButton
879- (void)clickedGlobe:(id)sender
880{
881    [_filterListView setSelectionByView:self];
882    if ([self isSubscription]) {
883        [self startEditingSubscription:NO];
884        //[_alertHelper prepareInformativeAlertWithTitle:[self name] withInformativeText:_url];
885        //[_alertHelper showPreparedAlert];
886        return;
887    }
888    NSString* info = [self url];
889    NSString* s = [Xml dateFromMillisecsXPath:@"last-publish-date/@msecs" withRoot:_filterE];
890    if (s)
891        info = [NSString stringWithFormat:@"%@\n\nTime of latest publication:\n        %@", info, s];
892    NSString* dirty = [Xml dateFromMillisecsXPath:@"first-dirty-date/@msecs" withRoot:_filterE];
893    if (dirty)
894        info = [NSString stringWithFormat:@"%@\n\nHas unpublished changes since:\n        %@", info, dirty];
895    [_alertHelper prepareInformativeAlertWithTitle:@"Published filter:" withInformativeText:info];
896    // ensure some reasonable min-width of alert so URL isn't wrapped.
897    [_alertHelper setAccessoryView:[[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 425, 1)] autorelease]];
898    NSButton* okBtn = [_alertHelper addButtonWithTitle:(dirty ? @"Publish changes" : @"Publish")];
899    if (!dirty)
900        [okBtn setKeyEquivalent:@""];
901    if (![_prefs hasGlimmerAuth])
902        [okBtn setEnabled:NO];
903    [_alertHelper addButtonWithTitle:@"Cancel"];
904    [_alertHelper setDelegate:self withSelector:@selector(clickedGlobeAlertCallbackWithReturnCode:)];
905    [_alertHelper showPreparedAlert];
906}
907
908- (void)checkReloadSubscriptionFilterData
909{
910    if (!_isSubscription)
911        return;
912    NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:_subscriptionDataFilePath error:nil];
913    NSTimeInterval moddate = [[attrs objectForKey:NSFileModificationDate] timeIntervalSince1970];
914    if (moddate == _subscriptionDataFileModdate)
915        return;
916    _subscriptionDataFileModdate = moddate;
917    DebugNSLog(@"Subscription changed on disk (%d.xml): %@", _filterId, [self name]);
918    NSString* pathArgs = [self getPathArgs:@"/filters/subscription/get"];
919    DebugNSLog(@"Getting sub info: %@", pathArgs);
920    XmlDoc* doc = [_comm curlToXml:pathArgs withActionDescription:NULL];
921    if (!doc)
922        return;
923    NSXMLElement* filterContainerE = [doc byXPath:@"filter-container"];
924    NSXMLElement* filterDataE = [doc byXPath:@"filter-container/filter-data"];
925    if (!filterContainerE || !filterDataE) {
926        NSLog(@"Invalid subscription xml: missing filter-data root child.");
927        return;
928    }
929    [filterContainerE retain];
930    [filterDataE retain];
931    //
932    [_filterContainerE release];
933    [_filterDataE release];
934    _filterContainerE = filterContainerE;
935    _filterDataE = filterDataE;
936    [self didGetNewSubscriptionData];
937}
938
939
940#pragma mark ------------------- Subscription edit
941
942- (void)checkSubscriptionUrlSyntax
943{
944    NSString* s = [editUrlTextField stringValue];
945    NSString* errorMsg = [Xml getHttpUrlSyntaxError:s];
946    if (!errorMsg && ![s hasSuffix:@".xml"])
947        errorMsg = @"URL must end with '.xml'";
948    [editUrlErrorField setStringValue:(errorMsg ? errorMsg : @"")];
949    [editOkBtn setEnabled:(!errorMsg && [_prefs hasGlimmerAuth])];
950}
951
952- (void)controlTextDidChange:(NSNotification *)aNotification
953{
954    if (editUrlTextField == [aNotification object])
955        [self checkSubscriptionUrlSyntax];
956}
957
958- (void)updateEditingSubscriptionDates
959{
960    NSMutableString* sb = [NSMutableString stringWithCapacity:0];
961    //
962    NSString* s = [Xml dateFromMillisecsXPath:@"last-modified/@msecs" withRoot:_filterContainerE];
963    [sb appendString:(s ? s : @"")];
964    //
965    [sb appendString:@"\n"];
966    s = [Xml dateFromMillisecsXPath:@"last-update-check/@msecs" withRoot:_filterContainerE];
967    [sb appendString:(s ? s : @"")];
968    //
969    [sb appendString:@"\n"];
970    s = [Xml dateFromMillisecsXPath:@"subscribed-date/@msecs" withRoot:_filterContainerE];
971    [sb appendString:(s ? s : @"")];
972    //
973    [editUpdateDatesTextField setStringValue:sb];
974    //
975    s = [Xml getAttribute:_filterContainerE withName:@"network-error"];
976    if (s) {
977        [editUpdateErrorTextField setTextColor:[editSafetyWarningTextField textColor]];
978        s = [@"Last update error:\n" stringByAppendingString:s];
979    } else {
980        s = [Xml dateFromMillisecsXPath:@"available-update-last-modified/@msecs" withRoot:_filterContainerE];
981        if (s) {
982            [editUpdateErrorTextField setTextColor:[NSColor colorWithDeviceRed:0 green:0.5f blue:0 alpha:1]];
983            s = [@"Available update:\n" stringByAppendingString:s];
984        }
985    }
986    [editUpdateErrorTextField setStringValue:(s ? s : @"")];
987}
988
989- (void)startEditingSubscription:(BOOL)oneshot
990{
991    if (!editSheet) {
992        NSAssert([NSBundle loadNibNamed: @"FilterSubscriptionEdit" owner: self], @"Can't load nib 'FilterSubscriptionEdit'");
993        NSAssert(editSheet, @"editSheet is null");
994    }
995    [editNameTextField setStringValue:[self name]];
996    [editUrlTextField setStringValue:[self url]];
997    [editUrlTextField setDelegate:self];
998    [self updateEditingSubscriptionDates];
999    [GBDebug setVertMatrix:editUpdatesMatrix
1000                    values:updatePolicies
1001             selectedValue:[Xml getAttribute:_filterE withName:@"update-policy"]
1002              defaultValue:@"notify"];
1003    [GBDebug setVertMatrix:editSafetyMatrix
1004                    values:safetyPolicies
1005             selectedValue:[Xml getAttribute:_filterE withName:@"safety-policy"]
1006              defaultValue:@"simple"];
1007    BOOL editable = [_prefs hasGlimmerAuth];
1008    [editUrlTextField setEnabled:editable];
1009    [editUpdatesMatrix setEnabled:editable];
1010    [editSafetyMatrix setEnabled:editable];
1011    [editUpdateNowBtn setEnabled:editable];
1012    [editOkBtn setEnabled:editable];
1013    [editUpdateNowBtn setState:oneshot];
1014    [self checkSubscriptionUrlSyntax];
1015    [NSApp beginSheet: editSheet
1016       modalForWindow: [self window]
1017        modalDelegate: nil
1018       didEndSelector: nil
1019          contextInfo: nil];
1020}
1021
1022-(void)startEditingSubscriptionOneShot:(id)sender
1023{
1024    [_filterListView setSelectionByView:self];
1025    [self startEditingSubscription:YES];
1026}
1027
1028- (IBAction)editCancelBtnAction:(id)sender
1029{
1030    [NSApp endSheet:editSheet];
1031    [editSheet orderOut:self];
1032}
1033
1034- (IBAction)editOkBtnAction:(id)sender
1035{
1036    [self editCancelBtnAction:self];
1037    [self setUrl:[editUrlTextField stringValue]];
1038    [Xml setAttribute:updatePolicies[[editUpdatesMatrix selectedRow]] withName:@"update-policy" inElement:_filterE];
1039    [Xml setAttribute:safetyPolicies[[editSafetyMatrix selectedRow]] withName:@"safety-policy" inElement:_filterE];
1040    [_prefs markAsDirty];
1041    [self readAttributesFromXml];
1042    [_comm saveDirtyPrefsAndNotify];
1043    [_filterTabController redrawRulesTable];
1044    if ([editUpdateNowBtn state])
1045        [self startUpdateSubscription];
1046}
1047
1048@end
1049
1050@implementation Filter(NSDraggingSource)
1051
1052- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
1053{
1054    NSLog(@"Filter.draggingSourceOperationMaskForLocal.%d", isLocal);
1055    return isLocal ? NSDragOperationNone : NSDragOperationNone;
1056}
1057
1058- (BOOL)ignoreModifierKeysWhileDragging
1059{
1060    return YES;
1061}
1062
1063- (void)draggedImage:(NSImage *)anImage beganAt:(NSPoint)aPoint
1064{
1065    NSLog(@"beganAt: %@", NSStringFromPoint(aPoint));
1066}
1067
1068@end
Note: See TracBrowser for help on using the repository browser.