#import #import "Controller.h" #import "MinMaxFormat.h" #import "ColorAsRGBADictionary.h" #import "TickerWindow.h" #import "RSSHandler.h" #import "CellAdditions.h" #import "HeadlineView.h" #import "TextWindow.h" #import "ArrayAdditions.h" #import "FeedWindow.h" @implementation Controller typedef struct { const char *what; int max; } allocinfo; + debugAllocation { static allocinfo classes[] = { { "GSUnicodeBufferString", 0 }, { "GSUnicodeInlineString", 0 }, { "GSCBufferString", 0 }, { "GSCInlineString", 0 }, { "NSURL", 0 }, { "GSHTTPURLHandle", 0 }, { "GSFileURLHandle", 0 }, { "NSMutableDataMalloc", 0 }, { "NSConcreteUnixTask", 0 }, { "NSPipe", 0 }, { "RSSNode", 0 }, { "RSSHandler", 0 }, { "AtomArticle", 0 }, { "RSSArticle", 0 }, { "TickerWindow", 0 }, { "FeedWindow", 0 }, { "TextWindow", 0 }, { NULL, 0 } }; allocinfo *clp = classes; BOOL first = YES; while(clp->what != NULL){ Class c = NSClassFromString([NSString stringWithCString:clp->what]); int count = GSDebugAllocationCount(c), total = GSDebugAllocationTotal(c), peak = GSDebugAllocationPeak(c); if(clp->max==0){ clp->max = peak; } if(clp->maxmax; clp->max = count; if(first==YES){ first = NO; NSLog(@"--------------"); } NSLog(@"%s +%d count %d total %d", clp->what, delta, count, total); } clp++; } return self; } static id _shared_controller = nil; + (Controller *)sharedController { return _shared_controller; } #define MARGIN 18 #define WIDTH 720 #define USEPROXY_WIDTH 48 #define USEPROXY_SEP 12 #define HEIGHT_NAME 48 #define TOTALY_NAME HEIGHT_NAME #define HEIGHT_URL 48 #define TOTALY_URL (TOTALY_NAME+HEIGHT_URL) #define HEIGHT_PROXY 48 #define TOTALY_PROXY (TOTALY_URL+HEIGHT_PROXY) #define HEIGHT_FG 80 #define TOTALY_FG (TOTALY_PROXY+HEIGHT_FG) #define HEIGHT_BG 80 #define TOTALY_BG (TOTALY_PROXY+HEIGHT_BG) #define HEIGHT_FONT 80 #define TOTALY_FONT (TOTALY_BG+HEIGHT_FONT) #define HEIGHT_REFRESH 50 #define TOTALY_REFRESH (TOTALY_FONT+HEIGHT_REFRESH) #define HEIGHT_TICK 50 #define TOTALY_TICK (TOTALY_REFRESH+HEIGHT_TICK) #define HEIGHT_UNTAG 48 #define TOTALY_UNTAG (TOTALY_TICK+HEIGHT_UNTAG) #define HEIGHT_BROWSE 48 #define TOTALY_BROWSE (TOTALY_UNTAG+HEIGHT_BROWSE) #define HEIGHT TOTALY_BROWSE - createFeedsPanel { feedsPanel = [[NSPanel alloc] initWithContentRect:NSMakeRect(100, 100, WIDTH+2*MARGIN, HEIGHT+2*MARGIN) styleMask:(NSTitledWindowMask | NSClosableWindowMask) backing:NSBackingStoreRetained defer:NO]; [feedsPanel setReleasedWhenClosed:NO]; [feedsPanel setTitle:_(@"Feeds")]; [feedsPanel setDelegate:self]; [feedsPanel setFrameUsingName: @"FeedsPanel"]; [feedsPanel setFrameAutosaveName: @"FeedsPanel"]; NSBox *box; box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, WIDTH/3, HEIGHT)]; [box setTitle:_(@"Feed")]; [[feedsPanel contentView] addSubview:box]; browser = [[NSBrowser alloc] initWithFrame:[[box contentView] frame]]; [browser setHasHorizontalScroller:NO]; [browser setMaxVisibleColumns:1]; [browser setAllowsMultipleSelection:NO]; [browser setTitled:NO]; [browser setDelegate:self]; [browser setTarget:self]; [browser setAction:@selector(setFeed:)]; NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu], *feedsMenu = [[[mainMenu itemWithTag:MENU_FEEDS] submenu] copy]; [feedsMenu setAutoenablesItems:NO]; NSMenuItem *item; NSEnumerator *en = [[feedsMenu itemArray] objectEnumerator]; while((item=[en nextObject])!=nil){ [item setEnabled:YES]; } [browser setMenu:feedsMenu]; [box setContentView:browser]; box = [[NSBox alloc] initWithFrame: NSMakeRect(WIDTH/3, HEIGHT-TOTALY_NAME, 2*WIDTH/3, HEIGHT_NAME)]; [box setTitle:_(@"Name")]; [[feedsPanel contentView] addSubview:box]; name = [[NSTextField alloc] initWithFrame:[[box contentView] frame]]; [name setStringValue:@"Ticker.app latest news"]; [name setFont:[NSFont userFixedPitchFontOfSize:12]]; [box setContentView:name]; box = [[NSBox alloc] initWithFrame: NSMakeRect(WIDTH/3, HEIGHT-TOTALY_URL, 2*WIDTH/3, HEIGHT_URL)]; [box setTitle:_(@"URL")]; [[feedsPanel contentView] addSubview:box]; url = [[NSTextField alloc] initWithFrame:[[box contentView] frame]]; [url setStringValue:@"http://www.gnustep.it/marko/Ticker/Ticker.xml"]; [url setFont:[NSFont userFixedPitchFontOfSize:12]]; [box setContentView:url]; box = [[NSBox alloc] initWithFrame: NSMakeRect(WIDTH/3, HEIGHT-TOTALY_PROXY, 2*WIDTH/3, HEIGHT_PROXY)]; [box setTitle:_(@"Proxy")]; [[feedsPanel contentView] addSubview:box]; NSRect cframe = [[box contentView] frame], subframe; cframe.origin.x = 0; cframe.origin.y = 0; subframe = cframe; subframe.size.width = USEPROXY_WIDTH; useproxy = [[NSButton alloc] initWithFrame:subframe]; [useproxy setTitle:_(@"Use")]; [useproxy setButtonType:NSSwitchButton]; [useproxy setImagePosition:NSImageRight]; [[box contentView] addSubview:useproxy]; NSForm *phostform, *pportform; subframe = cframe; subframe.origin.x += USEPROXY_WIDTH+USEPROXY_SEP; subframe.size.width = (cframe.size.width-USEPROXY_WIDTH-USEPROXY_SEP)/2.0; phostform = [[NSForm alloc] initWithFrame:subframe]; proxyhost = [phostform addEntry:_(@"Host:")]; [proxyhost setStringValue:@"localhost"]; [[box contentView] addSubview:phostform]; subframe.origin.x += subframe.size.width+USEPROXY_SEP; pportform = [[NSForm alloc] initWithFrame:subframe]; proxyport = [pportform addEntry:_(@"Port:")]; [proxyport setStringValue:@"3128"]; [[box contentView] addSubview:pportform]; box = [[NSBox alloc] initWithFrame: NSMakeRect(WIDTH/3, HEIGHT-TOTALY_FG, WIDTH/3, HEIGHT_FG)]; [box setTitle:_(@"Foreground")]; [[feedsPanel contentView] addSubview:box]; NSRect frame = [[box contentView] frame]; frame.size.width /= 3; frame.origin.x += frame.size.width; frame.origin.y = 0; foreground = [[NSColorWell alloc] initWithFrame:frame]; [foreground setColor:[NSColor blackColor]]; [foreground setTarget:self]; [foreground setAction:@selector(colorChanged:)]; [[box contentView] addSubview:foreground]; box = [[NSBox alloc] initWithFrame: NSMakeRect(2*WIDTH/3, HEIGHT-TOTALY_BG, WIDTH/3, HEIGHT_BG)]; [box setTitle:_(@"Background")]; [[feedsPanel contentView] addSubview:box]; frame = [[box contentView] frame]; frame.size.width /= 3; frame.origin.x += frame.size.width; frame.origin.y = 0; background = [[NSColorWell alloc] initWithFrame:frame]; [background setColor:[NSColor whiteColor]]; [background setTarget:self]; [background setAction:@selector(colorChanged:)]; [[box contentView] addSubview:background]; box = [[NSBox alloc] initWithFrame: NSMakeRect(WIDTH/3, HEIGHT-TOTALY_FONT, 2*WIDTH/3, HEIGHT_FONT)]; [box setTitle:_(@"Font")]; [[feedsPanel contentView] addSubview:box]; fontdisp = [[NSText alloc] initWithFrame:[[box contentView] frame]]; [fontdisp setString:_(@"The quick brown fox jumps over the lazy dog.")]; [fontdisp setAlignment:NSCenterTextAlignment]; [fontdisp setFont:[NSFont userFontOfSize:24]]; [fontdisp setUsesFontPanel:NO]; [fontdisp setSelectable:NO]; [box setContentView:fontdisp]; box = [[NSBox alloc] initWithFrame: NSMakeRect(WIDTH/3, HEIGHT-TOTALY_REFRESH, 2*WIDTH/3, HEIGHT_REFRESH)]; [box setTitle:_(@"Refresh (min)")]; [[feedsPanel contentView] addSubview:box]; frame = [[box contentView] frame]; frame.size.width = REFRESH_WIDTH; frame.origin.y = 0; refresh = [[NSTextField alloc] initWithFrame:frame]; [refresh setIntValue:REFRESH_DEFAULT]; [refresh setFont:[NSFont userFixedPitchFontOfSize:12]]; NSFormatter *fmt = [[MinMaxFormat alloc] initWithMin:REFRESH_MIN andMax:REFRESH_MAX]; [refresh setFormatter:fmt]; [[box contentView] addSubview:refresh]; frame.origin.x += REFRESH_WIDTH; frame.size.width = [[box contentView] frame].size.width -REFRESH_WIDTH-[box contentViewMargins].width; NSSlider *slider; slider = [[NSSlider alloc] initWithFrame:frame]; [slider setMinValue:REFRESH_MIN]; [slider setMaxValue:REFRESH_MAX]; [slider setIntValue:REFRESH_DEFAULT]; [slider setTarget:refresh]; [slider setAction:@selector(takeIntValueFrom:)]; [slider setTitle: [NSString stringWithFormat:_(@"min %d min, max %d min"), REFRESH_MIN, REFRESH_MAX]]; [[box contentView] addSubview:slider]; [refresh setTarget:slider]; [refresh setAction:@selector(takeIntValueFrom:)]; box = [[NSBox alloc] initWithFrame: NSMakeRect(WIDTH/3, HEIGHT-TOTALY_TICK, 2*WIDTH/3, HEIGHT_TICK)]; [box setTitle:_(@"Tick (secs)")]; [[feedsPanel contentView] addSubview:box]; frame = [[box contentView] frame]; frame.size.width = TICK_WIDTH; frame.origin.y = 0; tick = [[NSTextField alloc] initWithFrame:frame]; [tick setIntValue:TICK_DEFAULT]; [tick setFont:[NSFont userFixedPitchFontOfSize:12]]; fmt = [[MinMaxFormat alloc] initWithMin:TICK_MIN andMax:TICK_MAX]; [tick setFormatter:fmt]; [[box contentView] addSubview:tick]; frame.origin.x += TICK_WIDTH; frame.size.width = [[box contentView] frame].size.width -TICK_WIDTH-[box contentViewMargins].width; slider = [[NSSlider alloc] initWithFrame:frame]; [slider setMinValue:TICK_MIN]; [slider setMaxValue:TICK_MAX]; [slider setIntValue:TICK_DEFAULT]; [slider setTarget:tick]; [slider setAction:@selector(takeIntValueFrom:)]; [slider setTitle: [NSString stringWithFormat:_(@"min %d sec, max %d secs"), TICK_MIN, TICK_MAX]]; [[box contentView] addSubview:slider]; [tick setTarget:slider]; [tick setAction:@selector(takeIntValueFrom:)]; box = [[NSBox alloc] initWithFrame: NSMakeRect(WIDTH/3, HEIGHT-TOTALY_UNTAG, 2*WIDTH/3, HEIGHT_UNTAG)]; [box setTitle:_(@"Untag")]; [[feedsPanel contentView] addSubview:box]; untag = [[NSTextField alloc] initWithFrame:[[box contentView] frame]]; [untag setStringValue:@"/usr/bin/lynx -dump"]; [untag setFont:[NSFont userFixedPitchFontOfSize:12]]; [box setContentView:untag]; box = [[NSBox alloc] initWithFrame: NSMakeRect(WIDTH/3, HEIGHT-TOTALY_BROWSE, 2*WIDTH/3, HEIGHT_BROWSE)]; [box setTitle:_(@"Browse")]; [[feedsPanel contentView] addSubview:box]; browse = [[NSTextField alloc] initWithFrame:[[box contentView] frame]]; [browse setStringValue:@"/usr/bin/xterm -e /usr/bin/lynx -accept_all_cookies"]; [browse setFont:[NSFont userFixedPitchFontOfSize:12]]; [box setContentView:browse]; [name setNextKeyView:url]; [url setNextKeyView:useproxy]; [useproxy setNextKeyView:phostform]; [phostform setNextKeyView:pportform]; [pportform setNextKeyView:refresh]; [refresh setNextKeyView:tick]; [tick setNextKeyView:untag]; [untag setNextKeyView:browse]; [browse setNextKeyView:name]; NSEnumerator *svEnum = [[[feedsPanel contentView] subviews] objectEnumerator]; NSView *sv; while((sv = [svEnum nextObject])!=nil){ NSPoint sorigin = [sv frame].origin; sorigin.x += MARGIN; sorigin.y += MARGIN; [sv setFrameOrigin:sorigin]; } return self; } - colorChanged:(id)sender { if(sender==foreground){ [fontdisp setTextColor:[foreground color]]; } else{ [fontdisp setBackgroundColor:[background color]]; } return self; } - changeFont:(id)sender { NSFont *cfont = [[NSFontManager sharedFontManager] convertFont:[fontdisp font]]; [fontdisp setFont:cfont]; return self; } - (int)browser:(NSBrowser *)sender numberOfRowsInColumn:(int)column { return [sorted count]; } - (void)browser:(NSBrowser *)sender willDisplayCell:(id)cell atRow:(int)row column:(int)column { [cell setStringValue:[sorted objectAtIndex:row]]; [cell setLeaf:YES]; } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { _shared_controller = self; NSDictionary *dict; if((dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:DEFAULTS_FEED])!=nil){ feeds = [NSMutableDictionary dictionaryWithDictionary:dict]; } else{ feeds = [NSMutableDictionary dictionaryWithCapacity:DEFAULT_CAPACITY]; } [feeds retain]; updated = -1; sorted = nil; [self updateSortedAndSynchronize:NO]; [self createFeedsPanel]; windows = [[NSMutableArray alloc] initWithCapacity:DEFAULT_CAPACITY]; NSArray *restorewins = [[NSUserDefaults standardUserDefaults] arrayForKey:DEFAULTS_WINS]; if(restorewins!=nil){ NSEnumerator *en = [restorewins objectEnumerator]; NSDictionary *attr; while((attr=[en nextObject])!=nil){ NSString *title = [attr objectForKey:ATTR_NAME]; if(title==nil){ title = [attr objectForKey:ATTR_URL]; } [self makeTickerWindow:attr title:title]; } } if(restorewins==nil || ![restorewins count]){ [feedsPanel orderFrontRegardless]; } NSInvocation *inv; inv = [NSInvocation invocationWithMethodSignature: [self methodSignatureForSelector: @selector(controllerTick)]]; [inv setSelector:@selector(controllerTick)]; [inv setTarget:self]; [inv retain]; [NSTimer scheduledTimerWithTimeInterval:1.0 invocation:inv repeats:YES]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidTerminate:) name:NSTaskDidTerminateNotification object:nil]; } - updateSortedAndSynchronize:(BOOL)flag { if(sorted!=nil){ [sorted release]; } sorted = [[feeds allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; [sorted retain]; if(flag==YES){ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if(windows!=nil && [windows count]>0){ [defaults setObject:[windows mapSelector:@selector(attributes)] forKey:DEFAULTS_WINS]; } [defaults setObject:feeds forKey:DEFAULTS_FEED]; [defaults synchronize]; } return self; } - (void)applicationWillTerminate:(NSNotification *)aNotification { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:[windows mapSelector:@selector(attributes)] forKey:DEFAULTS_WINS]; [defaults synchronize]; } - addOrUpdateFeed:(id)sender { NSString *namestr = [name stringValue]; if(namestr==nil || ![namestr length]){ NSRunAlertPanel(_(@"Add/Update feed"), _(@"The feed name must not be empty."), _(@"Ok"), nil, nil); return self; } NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: namestr, ATTR_NAME, [url stringValue], ATTR_URL, ([useproxy state]==NSOnState ? @"On" : @"Off"), ATTR_PRXUSE, [proxyhost stringValue], ATTR_PRXHOST, [proxyport stringValue], ATTR_PRXPORT, [untag stringValue], ATTR_UNTAG, [browse stringValue], ATTR_BROWSE, [[foreground color] colorToRGBADictionary], ATTR_FG, [[background color] colorToRGBADictionary], ATTR_BG, [[fontdisp font] fontName], ATTR_FONTNAME, [NSString stringWithFormat:@"%f", [[fontdisp font] pointSize]], ATTR_FONTSIZE, [refresh stringValue], ATTR_REFRESH, [tick stringValue], ATTR_TICK, nil]; [feeds setObject:dict forKey:namestr]; [self updateSortedAndSynchronize:YES]; [browser loadColumnZero]; updated = [sorted indexOfObject:namestr]; [browser selectRow:updated inColumn:0]; [[browser selectedCell] setAttributedStringBackground: [NSColor greenColor]]; [browser displayColumn:0]; [browser becomeFirstResponder]; return self; } - makeTickerWindow:(NSDictionary *)attr title:(NSString *)aTitle { TickerWindow *twin = [[TickerWindow alloc] initWithAttributes:attr]; if([[twin title] isEqualToString:[twin loadtitle]]==YES){ [twin setTitle:aTitle]; } [twin setDelegate:self]; [twin orderFrontRegardless]; [twin makeMainWindow]; [windows addObject:twin]; return self; } - instantiateFeed:(id)sender { NSBrowserCell *cell = [browser selectedCell]; if(cell==nil){ NSBeep(); return self; } [self makeTickerWindow:[feeds objectForKey:[cell title]] title:[cell title]]; return self; } - removeFeed:(id)sender { NSBrowserCell *cell = [browser selectedCell]; if(cell==nil){ NSBeep(); return self; } [feeds removeObjectForKey:[cell title]]; [self updateSortedAndSynchronize:YES]; [browser loadColumnZero]; return self; } - setFeed:(id)sender { NSString *feedName = [[sender selectedCell] title]; NSDictionary *dict = [feeds objectForKey:feedName]; [name setStringValue:feedName]; [url setStringValue:[dict objectForKey:ATTR_URL]]; // for compatibilty with legacy defaults entries NSString *prxflag = [dict objectForKey:ATTR_PRXUSE]; if(prxflag!=nil){ [useproxy setState: ([prxflag isEqualToString:@"On"]==YES ? NSOnState : NSOffState)]; [proxyhost setStringValue:[dict objectForKey:ATTR_PRXHOST]]; [proxyport setStringValue:[dict objectForKey:ATTR_PRXPORT]]; } else{ [useproxy setState:NSOffState]; [proxyhost setStringValue:@""]; [proxyport setStringValue:@""]; } [untag setStringValue:[dict objectForKey:ATTR_UNTAG]]; [browse setStringValue:[dict objectForKey:ATTR_BROWSE]]; [foreground setColor: [NSColor colorFromRGBADictionary:[dict objectForKey:ATTR_FG]]]; [background setColor: [NSColor colorFromRGBADictionary:[dict objectForKey:ATTR_BG]]]; float pointSize; [[NSScanner scannerWithString:[dict objectForKey:ATTR_FONTSIZE]] scanFloat:&pointSize]; [fontdisp setFont: [NSFont fontWithName:[dict objectForKey:ATTR_FONTNAME] size:pointSize]]; [refresh setStringValue:[dict objectForKey:ATTR_REFRESH]]; [[refresh target] setIntValue:[refresh intValue]]; [tick setStringValue:[dict objectForKey:ATTR_TICK]]; [[tick target] setIntValue:[tick intValue]]; if(updated!=-1){ [[browser loadedCellAtRow:updated column:0] setAttributedStringBackground:nil]; [browser displayColumn:0]; updated = -1; } return self; } - displayFeedsPanel:(id)sender { [feedsPanel makeKeyAndOrderFront:self]; return self; } - controllerTick { unsigned long now = (unsigned long)[[NSDate date] timeIntervalSince1970]; int pos, max = [windows count]; for(pos=0; pos)menuItem { int tag = [menuItem tag]; if(tag >= MENU_ADDUPDATE && tag <= MENU_REMOVE){ if([feedsPanel isVisible]==NO){ return NO; } } if(tag == MENU_INSTANTIATE || tag == MENU_REMOVE){ if([browser selectedCell]==nil){ return NO; } } return YES; } @end