Feed icons! '/favicon.ico' download, storage and display.
This commit is contained in:
@@ -60,8 +60,8 @@ ToDo
|
||||
- [x] Auto fix 301 Redirect or ask user
|
||||
- [x] Make `feed://` URLs clickable
|
||||
- [ ] Feeds with authentication
|
||||
- [ ] Show proper feed icon
|
||||
- [ ] Download and store icon file
|
||||
- [x] Show proper feed icon
|
||||
- [x] Download and store icon file
|
||||
|
||||
|
||||
- [ ] Other
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
540CD14921C094A2004AB594 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 540CD14821C094A2004AB594 /* README.md */; };
|
||||
540F704521B6C16C0022E69D /* FeedMeta+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 540F704421B6C16C0022E69D /* FeedMeta+Ext.m */; };
|
||||
54195883218A061100581B79 /* Feed+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54195882218A061100581B79 /* Feed+Ext.m */; };
|
||||
54195886218E1BDB00581B79 /* NSMenu+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54195885218E1BDB00581B79 /* NSMenu+Ext.m */; };
|
||||
@@ -73,6 +74,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
540CD14821C094A2004AB594 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
540F704321B6C16C0022E69D /* FeedMeta+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FeedMeta+Ext.h"; sourceTree = "<group>"; };
|
||||
540F704421B6C16C0022E69D /* FeedMeta+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FeedMeta+Ext.m"; sourceTree = "<group>"; };
|
||||
54195881218A061100581B79 /* Feed+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Feed+Ext.h"; sourceTree = "<group>"; };
|
||||
@@ -208,6 +210,7 @@
|
||||
54ACC27321061B3B0020715F = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
540CD14821C094A2004AB594 /* README.md */,
|
||||
54ACC27E21061B3B0020715F /* baRSS */,
|
||||
54CC042D2162532800A48795 /* baRSS-Helper */,
|
||||
54ACC27D21061B3B0020715F /* Products */,
|
||||
@@ -364,6 +367,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
540CD14921C094A2004AB594 /* README.md in Resources */,
|
||||
546FC4472118A8E6007CC3A3 /* Preferences.xib in Resources */,
|
||||
54E88321211B509D00064188 /* ModalFeedEdit.xib in Resources */,
|
||||
54ACC28621061B3C0020715F /* Assets.xcassets in Resources */,
|
||||
|
||||
@@ -33,4 +33,5 @@
|
||||
- (NSArray<FeedArticle*>*)sortedArticles;
|
||||
- (int)markAllItemsRead;
|
||||
- (int)markAllItemsUnread;
|
||||
- (NSImage*)iconImage16;
|
||||
@end
|
||||
|
||||
@@ -21,11 +21,14 @@
|
||||
// SOFTWARE.
|
||||
|
||||
#import "Feed+Ext.h"
|
||||
#import "Constants.h"
|
||||
#import "DrawImage.h"
|
||||
#import "FeedMeta+Ext.h"
|
||||
#import "FeedGroup+Ext.h"
|
||||
#import "FeedIcon+CoreDataClass.h"
|
||||
#import "FeedArticle+CoreDataClass.h"
|
||||
#import "Constants.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <RSXML/RSXML.h>
|
||||
|
||||
@implementation Feed (Ext)
|
||||
@@ -214,4 +217,19 @@
|
||||
return newCount - oldCount;
|
||||
}
|
||||
|
||||
/**
|
||||
@return Return @c 16x16px image. Either from core data storage or generated default RSS icon.
|
||||
*/
|
||||
- (NSImage*)iconImage16 {
|
||||
NSData *imgData = self.icon.icon;
|
||||
if (imgData) {
|
||||
return [[NSImage alloc] initWithData:imgData];
|
||||
} else {
|
||||
static NSImage *defaultRSSIcon;
|
||||
if (!defaultRSSIcon)
|
||||
defaultRSSIcon = [RSSIcon iconWithSize:16];
|
||||
return defaultRSSIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -33,6 +33,7 @@ typedef NS_ENUM(int16_t, FeedGroupType) {
|
||||
|
||||
+ (instancetype)newGroup:(FeedGroupType)type inContext:(NSManagedObjectContext*)context;
|
||||
- (void)setName:(NSString*)name andRefreshString:(NSString*)refreshStr;
|
||||
- (NSImage*)groupIconImage16;
|
||||
// Handle children and parents
|
||||
- (NSString*)indexPathString;
|
||||
- (NSMutableArray<FeedGroup*>*)allParents;
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#import "FeedMeta+Ext.h"
|
||||
#import "Feed+Ext.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@implementation FeedGroup (Ext)
|
||||
/// Enum tpye getter see @c FeedGroupType
|
||||
- (FeedGroupType)typ { return (FeedGroupType)self.type; }
|
||||
@@ -46,6 +48,16 @@
|
||||
if (![self.refreshStr isEqualToString:refreshStr]) self.refreshStr = refreshStr;
|
||||
}
|
||||
|
||||
/// @return Return static @c 16x16px NSImageNameFolder image.
|
||||
- (NSImage*)groupIconImage16 {
|
||||
static NSImage *groupIcon;
|
||||
if (!groupIcon) {
|
||||
groupIcon = [NSImage imageNamed:NSImageNameFolder];
|
||||
groupIcon.size = NSMakeSize(16, 16);
|
||||
}
|
||||
return groupIcon;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Handle Children And Parents -
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#ifndef Constants_h
|
||||
#define Constants_h
|
||||
|
||||
// TODO: Add support for media player?
|
||||
// TODO: Add support for media player? image feed?
|
||||
// <enclosure url="https://url.mp3" length="63274022" type="audio/mpeg" />
|
||||
// TODO: Disable 'update all' menu item during update?
|
||||
|
||||
@@ -31,6 +31,7 @@ static NSString *kNotificationFeedUpdated = @"baRSS-notification-feed-updated";
|
||||
static NSString *kNotificationNetworkStatusChanged = @"baRSS-notification-network-status-changed";
|
||||
static NSString *kNotificationTotalUnreadCountChanged = @"baRSS-notification-total-unread-count-changed";
|
||||
static NSString *kNotificationTotalUnreadCountReset = @"baRSS-notification-total-unread-count-reset";
|
||||
static NSString *kNotificationFaviconDownloadFinished = @"baRSS-notification-favicon-download-finished";
|
||||
|
||||
extern uint64_t dispatch_benchmark(size_t count, void (^block)(void));
|
||||
//void benchmark(char *desc, dispatch_block_t b){printf("%s: %llu ns\n", desc, dispatch_benchmark(1, b));}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedGroup" inverseName="children" inverseEntity="FeedGroup" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="FeedIcon" representedClassName="FeedIcon" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="icon" optional="YES" attributeType="Binary" customClassName="NSImage" syncable="YES"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" customClassName="NSImage" syncable="YES"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Feed" inverseName="icon" inverseEntity="Feed" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="FeedMeta" representedClassName="FeedMeta" syncable="YES" codeGenerationType="class">
|
||||
@@ -49,8 +49,8 @@
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Feed" positionX="-278.84765625" positionY="-112.953125" width="128" height="195"/>
|
||||
<element name="FeedGroup" positionX="-460.37890625" positionY="-111.62890625" width="130.52734375" height="150"/>
|
||||
<element name="FeedArticle" positionX="-96.77734375" positionY="-113.83984375" width="128" height="195"/>
|
||||
<element name="FeedGroup" positionX="-460.37890625" positionY="-111.62890625" width="130.52734375" height="150"/>
|
||||
<element name="FeedIcon" positionX="-202.79296875" positionY="137.71875" width="128" height="75"/>
|
||||
<element name="FeedMeta" positionX="-348.02734375" positionY="136.89453125" width="128" height="165"/>
|
||||
</elements>
|
||||
|
||||
@@ -23,15 +23,19 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <RSXML/RSXML.h>
|
||||
|
||||
@class Feed;
|
||||
|
||||
@interface FeedDownload : NSObject
|
||||
// Register for network change notifications
|
||||
+ (void)registerNetworkChangeNotification;
|
||||
+ (void)unregisterNetworkChangeNotification;
|
||||
// Scheduled feed update
|
||||
+ (void)newFeed:(NSString *)urlStr block:(void(^)(RSParsedFeed *feed, NSError *error, NSHTTPURLResponse *response))block;
|
||||
+ (void)autoDownloadAndParseURL:(NSString*)url;
|
||||
// Scheduling
|
||||
+ (void)scheduleUpdateForUpcomingFeeds;
|
||||
+ (void)forceUpdateAllFeeds;
|
||||
// Downloading
|
||||
+ (void)newFeed:(NSString *)urlStr block:(void(^)(RSParsedFeed *feed, NSError *error, NSHTTPURLResponse *response))block;
|
||||
+ (void)autoDownloadAndParseURL:(NSString*)urlStr;
|
||||
+ (void)backgroundDownloadFavicon:(NSString*)urlStr forFeed:(Feed*)feed;
|
||||
// User interaction
|
||||
+ (BOOL)allowNetworkConnection;
|
||||
+ (BOOL)isPaused;
|
||||
|
||||
@@ -150,14 +150,23 @@ static BOOL _nextUpdateIsForced = NO;
|
||||
|
||||
#pragma mark - Download RSS Feed -
|
||||
|
||||
/// @return Base URL part. E.g., https://stackoverflow.com/a/15897956/10616114 ==> https://stackoverflow.com/
|
||||
+ (NSURL*)hostURL:(NSString*)urlStr {
|
||||
return [[NSURL URLWithString:@"/" relativeToURL:[self fixURL:urlStr]] absoluteURL];
|
||||
}
|
||||
|
||||
/// @return New request with no caching policy and timeout interval of 30 seconds.
|
||||
+ (NSMutableURLRequest*)newRequestURL:(NSString*)urlStr {
|
||||
/// Check if any scheme is set. If not, prepend 'http://'.
|
||||
+ (NSURL*)fixURL:(NSString*)urlStr {
|
||||
NSURL *url = [NSURL URLWithString:urlStr];
|
||||
if (!url.scheme) {
|
||||
url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", urlStr]]; // usually will redirect to https if necessary
|
||||
}
|
||||
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
|
||||
return url;
|
||||
}
|
||||
|
||||
/// @return New request with no caching policy and timeout interval of 30 seconds.
|
||||
+ (NSMutableURLRequest*)newRequestURL:(NSString*)urlStr {
|
||||
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[self fixURL:urlStr]];
|
||||
req.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
||||
req.HTTPShouldHandleCookies = NO;
|
||||
// req.timeoutInterval = 30;
|
||||
@@ -274,9 +283,46 @@ static BOOL _nextUpdateIsForced = NO;
|
||||
newFeed.sortIndex = (int32_t)idx;
|
||||
[newFeed.feed calculateAndSetIndexPathString];
|
||||
[StoreCoordinator saveContext:moc andParent:YES];
|
||||
NSString *faviconURL = newFeed.feed.link;
|
||||
if (faviconURL.length == 0)
|
||||
faviconURL = meta.url;
|
||||
[FeedDownload backgroundDownloadFavicon:faviconURL forFeed:newFeed.feed];
|
||||
[moc reset];
|
||||
}
|
||||
|
||||
/**
|
||||
Try to download @c favicon.ico and save downscaled image to persistent store.
|
||||
*/
|
||||
+ (void)backgroundDownloadFavicon:(NSString*)urlStr forFeed:(Feed*)feed {
|
||||
NSManagedObjectID *oid = feed.objectID;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSImage *img = [self downloadFavicon:urlStr];
|
||||
if (img) {
|
||||
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
||||
[moc performBlock:^{
|
||||
Feed *f = [moc objectWithID:oid];
|
||||
if (!f.icon)
|
||||
f.icon = [[FeedIcon alloc] initWithEntity:FeedIcon.entity insertIntoManagedObjectContext:moc];
|
||||
f.icon.icon = [img TIFFRepresentation];
|
||||
[StoreCoordinator saveContext:moc andParent:YES];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFaviconDownloadFinished object:f.objectID];
|
||||
[moc reset];
|
||||
}];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Download favicon located at http://.../ @c favicon.ico and rescale image to @c 16x16.
|
||||
+ (NSImage*)downloadFavicon:(NSString*)urlStr {
|
||||
NSURL *favURL = [[self hostURL:urlStr] URLByAppendingPathComponent:@"favicon.ico"];
|
||||
NSImage *img = [[NSImage alloc] initWithContentsOfURL:favURL];
|
||||
if (!img) return nil;
|
||||
return [NSImage imageWithSize:NSMakeSize(16, 16) flipped:NO drawingHandler:^BOOL(NSRect dstRect) {
|
||||
[img drawInRect:dstRect];
|
||||
return YES;
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Network Connection & Reachability -
|
||||
|
||||
|
||||
@@ -110,14 +110,21 @@
|
||||
Set @c scheduled to a new date if refresh interval was changed.
|
||||
*/
|
||||
- (void)applyChangesToCoreDataObject {
|
||||
FeedMeta *meta = self.feedGroup.feed.meta;
|
||||
Feed *feed = self.feedGroup.feed;
|
||||
FeedMeta *meta = feed.meta;
|
||||
BOOL intervalChanged = [meta setURL:self.previousURL refresh:self.refreshNum.intValue unit:(int16_t)self.refreshUnit.indexOfSelectedItem];
|
||||
if (intervalChanged)
|
||||
[meta calculateAndSetScheduled]; // updateTimer will be scheduled once preferences is closed
|
||||
[self.feedGroup setName:self.name.stringValue andRefreshString:[meta readableRefreshString]];
|
||||
if (self.didDownloadFeed) {
|
||||
[meta setEtag:self.httpEtag modified:self.httpDate];
|
||||
[self.feedGroup.feed updateWithRSS:self.feedResult postUnreadCountChange:YES];
|
||||
[feed updateWithRSS:self.feedResult postUnreadCountChange:YES];
|
||||
}
|
||||
if (!feed.icon) {
|
||||
NSString *faviconURL = feed.link;
|
||||
if (faviconURL.length == 0)
|
||||
faviconURL = meta.url;
|
||||
[FeedDownload backgroundDownloadFavicon:faviconURL forFeed:feed];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +159,6 @@
|
||||
self.httpEtag = [response allHeaderFields][@"Etag"];
|
||||
self.httpDate = [response allHeaderFields][@"Date"]; // @"Expires", @"Last-Modified"
|
||||
[self updateTextFieldURL:response.URL.absoluteString andTitle:result.title];
|
||||
// TODO: add icon download
|
||||
// TODO: play error sound?
|
||||
[self.spinnerURL stopAnimation:nil];
|
||||
[self.spinnerName stopAnimation:nil];
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#import "SettingsFeeds.h"
|
||||
#import "Constants.h"
|
||||
#import "DrawImage.h"
|
||||
#import "StoreCoordinator.h"
|
||||
#import "ModalFeedEdit.h"
|
||||
#import "Feed+Ext.h"
|
||||
@@ -52,12 +51,31 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
||||
|
||||
self.dataStore.managedObjectContext = [StoreCoordinator createChildContext];
|
||||
self.dataStore.managedObjectContext.undoManager = self.undoManager;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(faviconDownloadFinished:) name:kNotificationFaviconDownloadFinished object:nil];
|
||||
}
|
||||
|
||||
- (void)saveChanges {
|
||||
[StoreCoordinator saveContext:self.dataStore.managedObjectContext andParent:YES];
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
/**
|
||||
Called when the backgroud download of a favicon finished.
|
||||
Notification object contains the updated @c Feed (object id).
|
||||
*/
|
||||
- (void)faviconDownloadFinished:(NSNotification*)notify {
|
||||
if ([notify.object isKindOfClass:[NSManagedObjectID class]]) {
|
||||
// TODO: Bug: Freshly ownloaded images are deleted on undo. Remove delete cascade rule?
|
||||
NSManagedObject *mo = [self.dataStore.managedObjectContext objectWithID:notify.object];
|
||||
if (!mo) return;
|
||||
[self.dataStore.managedObjectContext refreshObject:mo mergeChanges:YES];
|
||||
[self.dataStore rearrangeObjects];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UI Button Interaction
|
||||
|
||||
|
||||
- (IBAction)addFeed:(id)sender {
|
||||
[self showModalForFeedGroup:nil isGroupEdit:NO];
|
||||
}
|
||||
@@ -95,9 +113,14 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Insert & Edit Feed Items
|
||||
#pragma mark - Insert & Edit Feed Items / Modal Dialog
|
||||
|
||||
|
||||
/// Save core data changes of current object context to persistent store
|
||||
- (void)saveChanges {
|
||||
[StoreCoordinator saveContext:self.dataStore.managedObjectContext andParent:YES];
|
||||
}
|
||||
|
||||
/**
|
||||
Open a new modal window to edit the selected @c FeedGroup.
|
||||
@note isGroupEdit @c flag will be overwritten if @c FeedGroup parameter is not @c nil.
|
||||
@@ -134,9 +157,6 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Helper -
|
||||
|
||||
/// Insert @c FeedGroup item either after current selection or inside selected folder (if expanded)
|
||||
- (FeedGroup*)insertFeedGroupAtSelection:(FeedGroupType)type {
|
||||
FeedGroup *fg = [FeedGroup newGroup:type inContext:self.dataStore.managedObjectContext];
|
||||
@@ -270,16 +290,7 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
||||
return cellView; // the refresh cell is already skipped with the above if condition
|
||||
} else {
|
||||
cellView.textField.objectValue = fg.name;
|
||||
if (fg.typ == GROUP) {
|
||||
cellView.imageView.image = [NSImage imageNamed:NSImageNameFolder];
|
||||
} else {
|
||||
// TODO: load icon
|
||||
static NSImage *defaultRSSIcon;
|
||||
if (!defaultRSSIcon)
|
||||
defaultRSSIcon = [RSSIcon iconWithSize:cellView.imageView.frame.size.height];
|
||||
|
||||
cellView.imageView.image = defaultRSSIcon;
|
||||
}
|
||||
cellView.imageView.image = (fg.typ == GROUP ? [NSImage imageNamed:NSImageNameFolder] : [fg.feed iconImage16]);
|
||||
}
|
||||
// also for refresh column
|
||||
cellView.textField.textColor = (isFeed && refreshDisabled ? [NSColor disabledControlTextColor] : [NSColor controlTextColor]);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import "StoreCoordinator.h"
|
||||
#import "DrawImage.h"
|
||||
#import "UserPrefs.h"
|
||||
#import "Feed+Ext.h"
|
||||
#import "FeedGroup+Ext.h"
|
||||
|
||||
/// User preferences for displaying menu items
|
||||
@@ -106,46 +107,18 @@ typedef NS_ENUM(char, DisplaySetting) {
|
||||
self.submenu = [self.menu submenuWithIndex:fg.sortIndex isFeed:(fg.typ == FEED)];
|
||||
[self setTitleAndUnreadCount:fg]; // after submenu is set
|
||||
if (fg.typ == FEED) {
|
||||
[self configureAsFeed:fg];
|
||||
self.tag = ScopeFeed;
|
||||
self.toolTip = fg.feed.subtitle;
|
||||
self.enabled = (fg.feed.articles.count > 0);
|
||||
self.image = [fg.feed iconImage16];
|
||||
} else {
|
||||
[self configureAsGroup:fg];
|
||||
self.tag = ScopeGroup;
|
||||
self.enabled = (fg.children.count > 0);
|
||||
self.image = [fg groupIconImage16];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Configure menu item to be used as a container for @c FeedArticle entries (incl. feed icon).
|
||||
*/
|
||||
- (void)configureAsFeed:(FeedGroup*)fg {
|
||||
self.tag = ScopeFeed;
|
||||
self.toolTip = fg.feed.subtitle;
|
||||
self.enabled = (fg.feed.articles.count > 0);
|
||||
// set icon
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
static NSImage *defaultRSSIcon;
|
||||
if (!defaultRSSIcon)
|
||||
defaultRSSIcon = [RSSIcon iconWithSize:16];
|
||||
self.image = defaultRSSIcon;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Configure menu item to be used as a container for multiple feeds.
|
||||
*/
|
||||
- (void)configureAsGroup:(FeedGroup*)fg {
|
||||
self.tag = ScopeGroup;
|
||||
self.enabled = (fg.children.count > 0);
|
||||
// set icon
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
static NSImage *groupIcon;
|
||||
if (!groupIcon) {
|
||||
groupIcon = [NSImage imageNamed:NSImageNameFolder];
|
||||
groupIcon.size = NSMakeSize(16, 16);
|
||||
}
|
||||
self.image = groupIcon;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Populate @c NSMenuItem based on the attributes of a @c FeedArticle.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user