Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Source/EntryListController.m
/* |
File: EntryListController.m |
Abstract: Use the PubSub framework to create a simple RSS news reader. |
Version: <1.0> |
Disclaimer: IMPORTANT: This Apple software is supplied to you by |
Apple Inc. ("Apple") in consideration of your agreement to the |
following terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of these |
terms. If you do not agree with these terms, please do not use, |
install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. |
may be used to endorse or promote products derived from the Apple |
Software without specific prior written permission from Apple. Except |
as expressly stated in this notice, no other rights or licenses, express |
or implied, are granted by Apple herein, including but not limited to |
any patent rights that may be infringed by your derivative works or by |
other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright © 2006-2007 Apple Inc. All Rights Reserved. |
*/ |
#import "EntryListController.h" |
#import "FeedListController.h" |
#import "ContentFormatting.h" |
#import "NewsReaderApp.h" |
#import <PubSub/PubSub.h> |
#pragma mark - |
#pragma mark ENTRYLISTCONTROLLER: |
// Time since update until activity level decays to zero |
#define kActivityThresholdTime (2 * 60 * 60) |
@implementation EntryListController |
static NSFont *kNormalFont, *kBoldFont; |
- (void) awakeFromNib |
{ |
// We're going to swap back and forth between these fonts later, depending upon what's happening |
// with the entry that's being displayed. Save these off. |
kNormalFont = [[[[_table tableColumns] objectAtIndex: 0] dataCell] font]; |
kBoldFont = [[NSFontManager sharedFontManager] convertFont: kNormalFont toHaveTrait: NSBoldFontMask]; |
// Double clicks in the table view should call openEntryInBrowser: |
[_table setDoubleAction: @selector(openEntryInBrowser:)]; |
} |
// The feed for which entries are currently being displayed |
- (PSFeed*) currentFeed |
{ |
return _currentFeed; |
} |
// Change the feed for which entries are being displayed |
- (void) setCurrentFeed: (PSFeed*)feed |
{ |
if( feed != _currentFeed ) { |
NSLog(@"EntryListController: Now showing %@",feed); |
// If we're changing our notion of currentFeed, we'll want to remove ourself as observer |
// for its PSFeedEntriesChangedNotification |
if( _currentFeed ) |
[[NSNotificationCenter defaultCenter] removeObserver: self |
name: PSFeedEntriesChangedNotification |
object: _currentFeed]; |
// Then, remember the new one |
_currentFeed = feed; |
// Reload our table view (using the new feed, or, it'll empty out if the input feed is nil) |
[self reloadEntries]; |
// And add ourself as observer of this new feed's PSFeedEntriesChangedNotification |
if( _currentFeed ) |
[[NSNotificationCenter defaultCenter] addObserver: self |
selector: @selector(_entriesChanged:) |
name: PSFeedEntriesChangedNotification |
object: _currentFeed]; |
} |
} |
// Sorts the table view using the columns' sort descriptors |
- (void) _sortEntryList |
{ |
// Remember what's selected at the moment, so that we can restore selection after the sort |
PSEntry *sel = [self selectedEntry]; |
_entries = [[_entrySet allObjects] sortedArrayUsingDescriptors: [_table sortDescriptors]]; |
[_table reloadData]; |
[_table noteNumberOfRowsChanged]; |
[_table setNeedsDisplay: YES]; |
self.selectedEntry = sel; |
} |
// Reloads the table view with the PSEntry objects from the currentFeed |
- (void) reloadEntries |
{ |
NSArray *entries = [_currentFeed entries]; |
if( ! entries ) { |
if( _currentFeed ) |
[gApp fatalError: @"Couldn't get list of entries"]; |
entries = [NSArray array]; |
} |
_entrySet = [[NSMutableSet alloc] initWithArray: entries]; |
[self _sortEntryList]; |
} |
// Apply updates to entries in our mutable set |
- (void) updateEntries: (NSArray*)updated |
{ |
if( [updated count] ) { |
NSSet* updatedSet = [NSSet setWithArray: updated]; |
[_entrySet minusSet: updatedSet]; // Replace old copies of entries |
[_entrySet unionSet: updatedSet]; // with new (updated) ones |
} |
} |
// Called when we receive a PSFeedEntriesChangedNotification |
- (void) _entriesChanged: (NSNotification*)n |
{ |
if( [n object] != _currentFeed ) { |
// Got PSFeedEntriesChangedNotification for wrong feed! |
return; |
} |
NSLog(@"EntryListController: Entries changed in %@",_currentFeed); |
// Information as to what exactly changed is included in the userInfo dictionary |
NSDictionary *info = [n userInfo]; |
// If we've removed entries, just call reloadEntries to resync our table view with the PSFeed's entries |
if( [[info objectForKey: PSFeedRemovedEntriesKey] count] ) |
[self reloadEntries]; |
else { |
// If we've added entries, union them with our existing set of entries |
NSArray *added = [info objectForKey: PSFeedAddedEntriesKey]; |
if( added ) |
[_entrySet unionSet: [NSSet setWithArray: added]]; |
// If it's state changes for existing objects in our set of entries, just update them |
[self updateEntries: [info objectForKey: PSFeedUpdatedEntriesKey]]; |
[self updateEntries: [info objectForKey: PSFeedDidChangeEntryFlagsKey]]; |
} |
// Resort, to accomodate the changes we've just applied |
[self _sortEntryList]; |
} |
// The currently selected entry |
- (PSEntry*) selectedEntry |
{ |
int row = [_table selectedRow]; |
return row>=0 ?[_entries objectAtIndex: row] :nil; |
} |
// Sets the current entry selection, or deselects all if the requested entry isn't present |
// in the table view |
- (void) setSelectedEntry: (PSEntry*)entry |
{ |
NSUInteger row = entry ?[_entries indexOfObject: entry] :NSNotFound; |
if( row != NSNotFound ) { |
[_table selectRow: row byExtendingSelection: NO]; |
[_table scrollRowToVisible: row]; |
} else { |
[_table deselectAll: self]; |
} |
} |
// Open the current entry's alternateURL in the user's default web browser |
- (IBAction) openEntryInBrowser: (id)sender |
{ |
NSURL *url = [[self selectedEntry] alternateURL]; |
if( ! url || ! [[NSWorkspace sharedWorkspace] openURL: url] ) |
NSBeep(); |
} |
#pragma mark - |
#pragma mark REDRAWING: |
// If we don't already have a pending scheduled redraw, schedule a call to |
// _redrawTable in 60 seconds. |
- (void) _scheduleTableRedraw |
{ |
if( ! _scheduledTableRedraw ) { |
[self performSelector: @selector(_redrawTable) |
withObject: nil |
afterDelay: 60.0]; |
_scheduledTableRedraw = YES; |
} |
} |
// Redraw our table view |
- (void) _redrawTable |
{ |
_scheduledTableRedraw = NO; |
[_table setNeedsDisplay: YES]; |
} |
#pragma mark - |
#pragma mark TABLE VIEW DATA SOURCE: |
// NSTableView datasource protocol |
// We have as many rows as we have entries |
- (int)numberOfRowsInTableView:(NSTableView *)tableView |
{ |
return [_entries count]; |
} |
// Our table uses KVC (key-value coding) to obtain values for itself. We use this method |
// to modify those values, if desired, e.g. to change the formating of a date string, |
// or provide a "look" for a boolean value. |
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn |
row:(int)row |
{ |
PSEntry *entry = [_entries objectAtIndex: row]; |
NSString *ident = [tableColumn identifier]; |
id value = [entry valueForKey: ident]; // Use KVC to get property value |
if( [ident isEqualToString: @"isRead"] ) { |
if( [value boolValue]==NO ) |
value = @"*"; |
else if( [entry isFlagged] ) |
value = @"F"; |
else |
value = @""; |
} else if( [value isKindOfClass: [NSDate class]] ) { |
value = ShortDateTimeString(value); |
} |
return value; |
} |
// When a titleForDisplay cell in our table is going to be displayed, we'd like to bold its font if the entry |
// is unread. The newest items also are color-coded. |
- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row; |
{ |
PSEntry *entry = [_entries objectAtIndex: row]; |
NSString *ident = [tableColumn identifier]; |
if( [ident isEqualToString: @"titleForDisplay"] ) { |
NSFont *font; |
NSColor *color; |
if( [entry isRead] ) { |
font = kNormalFont; |
color = nil; |
} else { |
font = kBoldFont; |
color = ActivityColorForDate([entry localDateCreated]); |
} |
[cell setFont: font]; |
if( color ) { |
[cell setTextColor: color]; |
// Redraw the table in a minute so the color fades |
[self _scheduleTableRedraw]; |
} else { |
[cell setTextColor: [NSColor controlTextColor]]; |
} |
} else if( [ident isEqualToString: @"dateForDisplay"] ) { |
// Dates that aren't from the feed should be grayed-out |
// (If the XML feed contained no date information, we'll use the date PubSub downloaded and stored |
// the entry into its database.) |
BOOL hasRealDate = [entry datePublished] != nil; |
[cell setTextColor: (hasRealDate ?[NSColor controlTextColor] :[NSColor disabledControlTextColor])]; |
} |
} |
// The table view is telling us that we might want to resort by calling this. We do. |
- (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors |
{ |
[self _sortEntryList]; |
} |
// When we're notified that the table view's selection has changed, we tell our application that |
// its selected entry has been set to the new selection's value. |
- (void) tableViewSelectionDidChange: (NSTableView*)tableView |
{ |
[gApp setSelectedEntry: [self selectedEntry]]; |
} |
@end |
// Different colors indicate the newness of an entry, to convey urgency or freshness for the newest. |
NSColor* ActivityColorForDate( NSDate *updatedDate ) |
{ |
if( updatedDate ) { |
float activityLevel = 1.0 - (-[updatedDate timeIntervalSinceNow]) / kActivityThresholdTime; |
if( activityLevel > 0.0 ) { |
activityLevel = MIN(1.0, activityLevel); |
return [NSColor colorWithCalibratedHue: 0.0 // red (green is 0.333, blue is 0.666) |
saturation: 1.0 |
brightness: activityLevel |
alpha: 1.0]; |
} |
} |
return nil; |
} |
Copyright © 2007 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2007-06-01