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

Revision 1015, 9.6 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 "ProxyScriptEditor.h"
18#import "GBDebug.h"
19#import "ClickActionTextField.h"
20#import "ServerCommunication.h"
21#import "Xml.h"
22#import "Prefs.h"
23#import "NamedEnum.h"
24#import "AlertHelper.h"
25
26NamedEnum* proxyTypeEnum;
27
28@interface ProxyScriptEditor ()
29
30- (BOOL)isJavascriptTextStorage:(NSTextStorage*)storage;
31
32- (BOOL)validateSyntax;
33
34- (void)startTimerWithImmediateExecute:(BOOL)immediate;
35- (void)stopTimer;
36- (void)timerCallback:(NSTimer*)theTimer;
37
38- (void)updateNetworkCodeView;
39
40@end
41
42@implementation ProxyScriptEditor
43
44+ (void)initProxyScriptEditor
45{
46    if (proxyTypeEnum)
47        return;
48    proxyTypeEnum = [[NamedEnum alloc] initWithSemicolonList:@"+none;javascript;static"];
49}
50
51- (id)initWithServerCommunication:(ServerCommunication*)comm
52                       withBundle:(NSBundle*)bundle
53                     withDelegate:(id<ProxyScriptEditorDelegate>)delegate
54{
55    if (!(self = [super init]))
56        return nil;
57    NSAssert([NSBundle loadNibNamed: @"ProxyScriptEdit" owner: self],
58             @"Can't load nib 'ProxyScriptEdit'");
59    NSAssert(scriptSheet, @"scriptSheet is null");
60    //
61    _bundle = [bundle retain];
62    _delegate = delegate;
63    _comm = [comm retain];
64    _prefs = [[_comm prefs] retain];
65    _alertHelper = [[_prefs alertHelper] retain];
66    _codeEditHelper = [[CodeEditHelper alloc] initWithDelegate:self];
67    //
68    [tabView selectFirstTabViewItem:self];
69    [tabView setDelegate:self];
70    [scriptHelpTextLink setTarget:self selector:@selector(scriptHelpAction:)];
71    _helpMessage = [[scriptMessageField stringValue] retain];
72    [networkCodeView setEditable:NO];
73    //
74    [_codeEditHelper setCodeTextView:scriptCodeView
75                            withText:[_prefs stringForXPath:@"network-settings/proxy-script"]];
76    [scriptCodeView setDelegate:self];
77    [[scriptCodeView layoutManager] setAllowsNonContiguousLayout:YES];
78    [[scriptCodeView textStorage] setDelegate:self];
79    [_codeEditHelper syntaxHighlight:[scriptCodeView textStorage] isJavascript:YES];
80    //
81    NSXMLElement* scriptE = (NSXMLElement*)[_prefs nodeForXPath:@"network-settings/proxy-script"];
82    _oldSettingsData = scriptE ? [[Xml subtreeToData:scriptE] retain] : NULL;
83    //
84    [scriptSheet setFrameUsingName:@"ProxyScriptEditSheet"];
85    [self retain]; // until sheet is dismissed.
86    [NSApp beginSheet: scriptSheet
87       modalForWindow: [_alertHelper window]
88        modalDelegate: nil
89       didEndSelector: nil
90          contextInfo: nil];
91    [self startTimerWithImmediateExecute:YES];
92    return self;
93}
94
95- (void)dealloc
96{
97    [self stopTimer];
98    [_alertHelper release];
99    [_prefs release];
100    [_comm release];
101    [_codeEditHelper release];
102    [_oldSettingsData release];
103    [_helpMessage release];
104    [_bundle release];
105    [scriptSheet release];
106    [super dealloc];
107}
108
109- (BOOL)isJavascriptTextStorage:(NSTextStorage*)storage
110{
111    return YES;
112}
113
114- (void)textStorageDidProcessEditing:(NSNotification *)noti
115{
116    [_codeEditHelper textStorageDidProcessEditing:noti];
117    [self startTimerWithImmediateExecute:NO];
118}
119
120//====================================================================================
121
122- (void)updateNetworkCodeView
123{
124    NSTask* task = [[NSTask alloc] init];
125    NSPipe *readPipe = NULL;
126    NSFileHandle *readHandle = NULL;
127    NSMutableData* outputData = [[NSMutableData dataWithCapacity:0] retain];
128    NSAssert(_bundle, @"_bundle");
129    NSLog(@"bundle path: %@", [_bundle bundlePath]);
130    @try {
131        NSString *path = [NSString stringWithFormat:@"%@%@%@",
132                          [_bundle bundlePath],
133                          @"/Contents/GlimmerBlockerProxy.app",
134                          @"/Contents/MacOS/GBNetworkConfigurationListener"];
135        NSLog(@"NHT: %@", path);
136        [task setLaunchPath:path];
137        [task setCurrentDirectoryPath:@"/tmp"];
138        readPipe = [[NSPipe pipe] retain];
139        readHandle = [[readPipe fileHandleForReading] retain];
140        [task setStandardOutput:readPipe];
141        [task launch];
142        while (1) {
143            NSData* data = [readHandle availableData];
144            if (!data || ![data length])
145                break;
146            [outputData appendData:data];
147        }
148        [task waitUntilExit];
149        //DebugNSLog(@"task retains: %d", [task retainCount]);
150    }
151    @catch (NSException* ex) {
152        NSLog(@"GBNetworkConfigurationListener failed with exception: %@", [ex name]);
153        [GBDebug debugDumpStackframe];
154    }
155    if ([task isRunning])
156        [task terminate];
157    [task release];
158    [readHandle release];
159    [readPipe release];
160    NSString* json = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding];
161    [outputData release];
162    if ([json isEqual:[networkCodeView string]])
163        return; // no need to spend time reformatting code.
164    [networkCodeView setString:json];
165    [_codeEditHelper syntaxHighlight:[networkCodeView textStorage] isJavascript:YES];
166}
167
168//====================================================================================
169
170- (void)startTimerWithImmediateExecute:(BOOL)immediate
171{
172    [self stopTimer];
173    _timer = [[NSTimer scheduledTimerWithTimeInterval:(immediate ? 0.1 : 1.2)
174                                               target:self
175                                             selector:@selector(timerCallback:)
176                                             userInfo:NULL
177                                              repeats:NO] retain];
178}
179
180- (void)stopTimer
181{
182    if (!_timer)
183        return;
184    [_timer invalidate];
185    [_timer release];
186    _timer = NULL;
187}
188
189- (void)timerCallback:(NSTimer*)theTimer
190{
191    [self validateSyntax];
192}
193
194//====================================================================================
195
196- (BOOL)textView:(NSTextView*)aTextView doCommandBySelector:(SEL)aSelector
197{
198#ifdef DEBUG_DCBS
199    DebugNSLog(@"doCommandBySelector: %s", aSelector);
200#endif
201    BOOL newlineSelector = (aSelector == @selector(insertNewline:));
202    // I can't find noop: anywhere, so I don't know what the expected return type is.
203    BOOL noopSelector = [NSStringFromSelector(aSelector) isEqual:@"noop:"];
204    if (!newlineSelector && !noopSelector)
205        return NO; // No, I did not handle it.
206    NSEvent* event = [NSApp currentEvent];
207    if ([event type] != NSKeyDown || [event isARepeat])
208        return NO; // No, I did not handle it.
209#ifdef DEBUG_DCBS
210    DebugNSLog(@" current keydown event: %@", [NSApp currentEvent]);
211#endif
212    NSUInteger modifiers = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask;
213    NSString* s = [event charactersIgnoringModifiers];
214    BOOL enter = newlineSelector && !modifiers && [s length] == 1 && [s characterAtIndex:0] == 3;
215    BOOL appleReturn = noopSelector && (modifiers == NSCommandKeyMask) && [s length] == 1 && [s characterAtIndex:0] == 13;
216#ifdef DEBUG_DCBS
217    DebugNSLog(@"KeyDown(%d = %x) for %d, enter = %d, appleReturn = %d", modifiers, modifiers, [s characterAtIndex:0], enter, appleReturn);
218#endif
219    if (!enter && !appleReturn)
220        return NO; // No, I did not handle it.
221#ifdef DEBUG_DCBS
222    DebugNSLog(@"KeyDown executes self.commitAction:");
223#endif
224    [saveBtn highlight:YES];
225    [self performSelectorOnMainThread:@selector(commitAction:)
226                           withObject:self
227                        waitUntilDone:NO];
228    return YES; // Yes, I did handle it.
229}
230
231//====================================================================================
232
233- (IBAction)scriptHelpAction:(id)sender
234{
235    NSURL* url = [NSURL URLWithString:@"http://glimmerblocker.org/wiki/Proxies"];
236    [[NSWorkspace sharedWorkspace] openURL:url];
237}
238
239- (void)dismissSheet
240{
241    [scriptSheet saveFrameUsingName:@"ProxyScriptEditSheet"];
242    [NSApp endSheet:scriptSheet];
243    [scriptSheet orderOut:self];
244}
245
246- (IBAction)cancelAction:(id)sender
247{
248    [self stopTimer];
249    [self dismissSheet];
250    [self autorelease];
251}
252
253- (BOOL)validateSyntax
254{
255    NSString* js = [_codeEditHelper trimmedTextViewString:scriptCodeView];
256    NSString* pathArgs = [NSString stringWithFormat:@"/site-proxy/validate?javascript=%@",
257                          [GBDebug percentEscapeUrlParameterValue:js]];
258    XmlDoc* doc = [_comm curlToXml:pathArgs withActionDescription:NULL];
259    if (!doc) {
260        DebugNSLog(@"Problem validating JS.");
261        return YES; // don't stop the user saving it, if the proxy failed running.
262    }
263    NSString* msg = [doc stringForXPath:@"@proxy-error"];
264    if ([msg length]) {
265        [scriptMessageField setStringValue:msg];
266        [scriptMessageField setTextColor:[NSColor redColor]];
267        return NO;
268    } else {
269        [scriptMessageField setStringValue:_helpMessage];
270        [scriptMessageField setTextColor:[NSColor blackColor]];
271        return YES;
272    }
273}
274
275- (void)saveSettingsInElement:(NSXMLElement*)scriptE
276{
277    NSString* js = [_codeEditHelper trimmedTextViewString:scriptCodeView];
278    //DebugNSLog(@"Script: {{{%@}}}", js);
279    if (![js length])
280        return;
281    [Xml setCData:js forXPath:@"." withRoot:scriptE];
282    [Xml setAttribute:@"js" withName:@"language" inElement:scriptE];
283    [Xml setAttribute:@"1" withName:@"version" inElement:scriptE];
284}
285
286- (BOOL)isDirty
287{
288    NSXMLElement* scriptE = [Xml createElementWithName:@"proxy-script"];
289    [self saveSettingsInElement:scriptE];
290    NSData* data = [Xml subtreeToData:scriptE];
291    return ![data isEqualToData:_oldSettingsData];
292}
293
294- (IBAction)commitAction:(id)sender
295{
296    [self stopTimer];
297    BOOL dirty = [self isDirty];
298    if (dirty) {
299        NSXMLElement* scriptE = [_prefs singletonElementForXPath:@"network-settings/proxy-script"];
300        [self saveSettingsInElement:scriptE];
301        [_prefs markAsDirty];
302        [_comm saveDirtyPrefsAndNotify];
303    }
304    [self dismissSheet];
305    [_delegate notifyProxyScriptEditorCommitted:self];
306    [self autorelease];
307}
308
309@end
310
311@implementation ProxyScriptEditor(NSTabViewDelegate)
312
313- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
314{
315    if ([[tabViewItem identifier] isEqual:@"network"])
316        [self updateNetworkCodeView];
317}
318
319@end
Note: See TracBrowser for help on using the repository browser.