Skip to content

Commit

Permalink
use word-indexed caching of label-block images, rather than per-note …
Browse files Browse the repository at this point in the history
…caching, which should save memory if labels are used as expected; draw label-strings in a separate layer "out of" the filled bezier paths, rather than as intersected reverse-wound paths to avoid font-specific-winding problems
  • Loading branch information
scrod committed Mar 31, 2011
1 parent c544699 commit b6d50ec
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 145 deletions.
2 changes: 1 addition & 1 deletion AppController.m
Expand Up @@ -697,7 +697,7 @@ - (void)settingChangedForSelectorString:(NSString*)selectorString {

ResetFontRelatedTableAttributes();
[notesTableView updateTitleDereferencorState];
[notationController invalidateAllLabelPreviewImages];
[[notationController labelsListDataSource] invalidateCachedLabelImages];
[self _forceRegeneratePreviewsForTitleColumn];

if ([selectorString isEqualToString:SEL_STR(setTableColumnsShowPreview:sender:)]) [self updateNoteMenus];
Expand Down
19 changes: 10 additions & 9 deletions LabelColumnCell.m
Expand Up @@ -61,15 +61,16 @@ - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
[super drawWithFrame:cellFrame inView:controlView];
}

if (!isEditing) {
NSImage *img = ([self isHighlighted] && [tv isActiveStyle]) ? [noteObject highlightedLabelsPreviewImage] : [noteObject labelsPreviewImage];
if (img) {
[[NSGraphicsContext currentContext] saveGraphicsState];
NSRectClip(cellFrame);
NSPoint imgSpot = NSMakePoint(NSMinX(cellFrame), NSMaxY(cellFrame) - ceilf(((cellFrame.size.height + 1.0) - [img size].height)/2.0));
[img compositeToPoint:imgSpot operation:NSCompositeSourceOver];
[[NSGraphicsContext currentContext] restoreGraphicsState];
}
if (!isEditing && [labelsOfNote(noteObject) length]) {

[[NSGraphicsContext currentContext] saveGraphicsState];
NSRectClip(cellFrame);
NSRect blocksRect = cellFrame;
blocksRect.origin = NSMakePoint(NSMinX(cellFrame), NSMaxY(cellFrame) - ceilf(((cellFrame.size.height + 1.0) -
([[GlobalPrefs defaultPrefs] tableFontSize] * 1.3 + 1.5))/2.0));
[noteObject drawLabelBlocksInRect:blocksRect rightAlign:NO highlighted:([self isHighlighted] && [tv isActiveStyle])];

[[NSGraphicsContext currentContext] restoreGraphicsState];
}

}
Expand Down
4 changes: 4 additions & 0 deletions LabelsListController.h
Expand Up @@ -29,6 +29,7 @@

@interface LabelsListController : FastListDataSource {
NSCountedSet *allLabels, *filteredLabels;
NSMutableDictionary *labelImages;
unsigned *removeIndicies;
}

Expand All @@ -38,6 +39,9 @@

- (NSArray*)labelTitlesPrefixedByString:(NSString*)prefixString indexOfSelectedItem:(NSInteger *)anIndex minusWordSet:(NSSet*)antiSet;

- (void)invalidateCachedLabelImages;
- (NSImage*)cachedLabelImageForWord:(NSString*)aWord highlighted:(BOOL)isHighlighted;

- (NSSet*)notesAtFilteredIndex:(int)labelIndex;
- (NSSet*)notesAtFilteredIndexes:(NSIndexSet*)anIndexSet;

Expand Down
57 changes: 57 additions & 0 deletions LabelsListController.m
Expand Up @@ -24,6 +24,8 @@
#import "LabelsListController.h"
#import "LabelObject.h"
#import "NoteObject.h"
#import "GlobalPrefs.h"
#import "NSBezierPath_NV.h"
#import "NSCollection_utils.h"


Expand All @@ -44,6 +46,7 @@ - (id)init {

- (void)dealloc {

[labelImages release];
[allLabels release];
[filteredLabels release];
[super dealloc];
Expand Down Expand Up @@ -101,6 +104,56 @@ - (NSArray*)labelTitlesPrefixedByString:(NSString*)prefixString indexOfSelectedI
return titles;
}

#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
static CGRect NSRectToCGRect(NSRect nsrect) {
union _ {NSRect ns; CGRect cg;};
return ((union _ *)&nsrect)->cg;
}
#endif

- (void)invalidateCachedLabelImages {
//used when the list font size changes
[labelImages removeAllObjects];
}
- (NSImage*)cachedLabelImageForWord:(NSString*)aWord highlighted:(BOOL)isHighlighted {
if (!labelImages) labelImages = [[NSMutableDictionary alloc] init];

NSString *imgKey = [[aWord lowercaseString] stringByAppendingFormat:@", %d", isHighlighted];
NSImage *img = [labelImages objectForKey:imgKey];
if (!img) {
//generate the image and add it to labelImages under imgKey
float tableFontSize = [[GlobalPrefs defaultPrefs] tableFontSize] - 1.0;
NSDictionary *attrs = [NSDictionary dictionaryWithObject:[NSFont systemFontOfSize:tableFontSize] forKey:NSFontAttributeName];
NSSize wordSize = [aWord sizeWithAttributes:attrs];
NSRect wordRect = NSMakeRect(0, 0, roundf(wordSize.width + 4.0), roundf(tableFontSize * 1.3));

//peter hosey's suggestion, rather than doing setWindingRule: and appendBezierPath: as before:
//http://stackoverflow.com/questions/4742773/why-wont-helvetica-neue-bold-glyphs-draw-as-a-normal-subpath-in-nsbezierpath

img = [[NSImage alloc] initWithSize:wordRect.size];
[img lockFocus];

CGContextRef context = (CGContextRef)([[NSGraphicsContext currentContext] graphicsPort]);
CGContextBeginTransparencyLayer(context, NULL);

CGContextClipToRect(context, NSRectToCGRect(wordRect));

NSBezierPath *backgroundPath = [NSBezierPath bezierPathWithRoundRectInRect:wordRect radius:2.0f];
[(isHighlighted ? [NSColor whiteColor] : [NSColor colorWithCalibratedWhite:0.55 alpha:1.0]) setFill];
[backgroundPath fill];

[[NSGraphicsContext currentContext] setCompositingOperation:NSCompositeSourceOut];
[aWord drawWithRect:(NSRect){{2.0, 3.0}, wordRect.size} options:NSStringDrawingUsesFontLeading attributes:attrs];

CGContextEndTransparencyLayer(context);

[img unlockFocus];

[labelImages setObject:[img autorelease] forKey:imgKey];
}
return img;
}


//NotationController will probably want to filter these further if there is already a search in progress
- (NSSet*)notesAtFilteredIndex:(int)labelIndex {
Expand Down Expand Up @@ -136,6 +189,8 @@ - (void)removeLabelSet:(NSSet*)labelSet fromNote:(NoteObject*)note {
//HOWEVER, don't know this for sure, assuming this API is to remain non-permeable

[allLabels minusSet:labelSet];

//could use this as an opportunity to remove counterparts in labelImages

//we narrow down the set to make sure that we operate on the actual objects within it, and note the objects used as prototypes
//these will be any labels that were shared by notes other than this one
Expand All @@ -157,6 +212,8 @@ - (void)addLabelSet:(NSSet*)labelSet toNote:(NoteObject*)note {
//useful for moving groups of notes from one label to another
- (void)removeLabelSet:(NSSet*)labelSet fromNoteSet:(NSSet*)notes {
[allLabels minusSet:labelSet];

//could use this as an opportunity to remove counterparts in labelImages

NSMutableSet *existingLabels = [allLabels setIntersectedWithSet:labelSet];
[existingLabels makeObjectsPerformSelector:@selector(removeNoteSet:) withObject:notes];
Expand Down
1 change: 0 additions & 1 deletion NotationController.h
Expand Up @@ -188,7 +188,6 @@ typedef struct _NoteCatalogEntry {
- (float)titleColumnWidth;
- (void)regeneratePreviewsForColumn:(NSTableColumn*)col visibleFilteredRows:(NSRange)rows forceUpdate:(BOOL)force;
- (void)regenerateAllPreviews;
- (void)invalidateAllLabelPreviewImages;

//for setting up the nstableviews
- (id)labelsListDataSource;
Expand Down
4 changes: 0 additions & 4 deletions NotationController.m
Expand Up @@ -1518,10 +1518,6 @@ - (void)regeneratePreviewsForColumn:(NSTableColumn*)col visibleFilteredRows:(NSR
}
}

- (void)invalidateAllLabelPreviewImages {
[allNotes makeObjectsPerformSelector:@selector(invalidateLabelsPreviewImage)];
}

- (void)regenerateAllPreviews {
[allNotes makeObjectsPerformSelector:@selector(updateTablePreviewString)];
}
Expand Down
16 changes: 8 additions & 8 deletions NoteObject.h
Expand Up @@ -29,6 +29,7 @@
@class LabelObject;
@class WALStorageController;
@class NotesTableView;
@class ExternalEditor;

typedef struct _NoteFilterContext {
char* needle;
Expand All @@ -39,9 +40,7 @@ typedef struct _NoteFilterContext {
NSAttributedString *tableTitleString;
NSString *titleString, *labelString;
NSMutableAttributedString *contentString;

NSImage *labelsPreviewImage, *highlightedLabelsPreviewImage;


//caching/searching purposes only -- created at runtime
char *cTitle, *cContents, *cLabels, *cTitleFoundPtr, *cContentsFoundPtr, *cLabelsFoundPtr;
NSMutableSet *labelSet;
Expand Down Expand Up @@ -161,10 +160,9 @@ NSInteger compareFileSize(id *a, id *b);
- (void)setLabelString:(NSString*)newLabels;
- (NSMutableSet*)labelSetFromCurrentString;
- (NSArray*)orderedLabelTitles;
- (void)invalidateLabelsPreviewImage;
- (NSImage*)highlightedLabelsPreviewImage;
- (NSImage*)labelsPreviewImage;
- (NSImage*)_labelsPreviewImageOfColor:(NSColor*)aColor;
- (NSSize)sizeOfLabelBlocks;
- (void)_drawLabelBlocksInRect:(NSRect)aRect rightAlign:(BOOL)onRight highlighted:(BOOL)isHighlighted getSizeOnly:(NSSize*)reqSize;
- (void)drawLabelBlocksInRect:(NSRect)aRect rightAlign:(BOOL)onRight highlighted:(BOOL)isHighlighted;

- (void)setSyncObjectAndKeyMD:(NSDictionary*)aDict forService:(NSString*)serviceName;
- (void)removeAllSyncMDForService:(NSString*)serviceName;
Expand All @@ -179,7 +177,7 @@ NSInteger compareFileSize(id *a, id *b);
- (BOOL)upgradeEncodingToUTF8;
- (BOOL)updateFromFile;
- (BOOL)updateFromCatalogEntry:(NoteCatalogEntry*)catEntry;
- (BOOL)updateFromData:(NSMutableData*)data;
- (BOOL)updateFromData:(NSMutableData*)data inFormat:(int)fmt;

- (OSStatus)writeFileDatesAndUpdateTrackingInfo;

Expand All @@ -200,6 +198,8 @@ NSInteger compareFileSize(id *a, id *b);

- (OSStatus)exportToDirectoryRef:(FSRef*)directoryRef withFilename:(NSString*)userFilename usingFormat:(int)storageFormat overwrite:(BOOL)overwrite;
- (NSRange)nextRangeForWords:(NSArray*)words options:(unsigned)opts range:(NSRange)inRange;
- (void)editExternallyUsingEditor:(ExternalEditor*)ed;
- (void)abortEditingInExternalEditor;

- (void)setFilenameFromTitle;
- (void)setFilename:(NSString*)aString withExternalTrigger:(BOOL)externalTrigger;
Expand Down
140 changes: 52 additions & 88 deletions NoteObject.m
Expand Up @@ -193,6 +193,7 @@ NSInteger compareLabelString(id *a, id *b) {
(CFStringRef)(labelsOfNote(*(NoteObject **)b)), kCFCompareCaseInsensitive);
}
NSInteger compareTitleString(id *a, id *b) {
//add kCFCompareNumerically to options for natural order sort
CFComparisonResult stringResult = CFStringCompare((CFStringRef)(titleOfNote(*(NoteObject**)a)),
(CFStringRef)(titleOfNote(*(NoteObject**)b)),
kCFCompareCaseInsensitive);
Expand Down Expand Up @@ -715,12 +716,11 @@ - (void)updateTablePreviewString {

if ([prefs tableColumnsShowPreview]) {
if ([prefs horizontalLayout]) {
//labelsPreviewImage does work only when the image is explicitly invalidated, and because updateTablePreviewString
//is called for visible notes at launch and resize only, generation of images for invisible notes is delayed until after launch

NSImage *img = ColumnIsSet(NoteLabelsColumn, [prefs tableColumnsBitmap]) ? [self labelsPreviewImage] : nil;
NSSize labelBlockSize = ColumnIsSet(NoteLabelsColumn, [prefs tableColumnsBitmap]) ? [self sizeOfLabelBlocks] : NSZeroSize;
tableTitleString = [[titleString attributedMultiLinePreviewFromBodyText:contentString upToWidth:[delegate titleColumnWidth]
intrusionWidth:img ? [img size].width : 0.0] retain];
intrusionWidth:labelBlockSize.width] retain];
} else {
tableTitleString = [[titleString attributedSingleLinePreviewFromBodyText:contentString upToWidth:[delegate titleColumnWidth]] retain];
}
Expand Down Expand Up @@ -968,7 +968,6 @@ - (BOOL)_setLabelString:(NSString*)newLabelString {
cLabelsFoundPtr = cLabels = replaceString(cLabels, [labelString lowercaseUTF8String]);

[self updateLabelConnections];
[self invalidateLabelsPreviewImage];
return YES;
}
return NO;
Expand Down Expand Up @@ -1015,99 +1014,64 @@ - (NSArray*)orderedLabelTitles {
return [labelString labelCompatibleWords];
}

- (void)invalidateLabelsPreviewImage {
[highlightedLabelsPreviewImage release];
highlightedLabelsPreviewImage = nil;
[labelsPreviewImage release];
labelsPreviewImage = nil;
- (NSSize)sizeOfLabelBlocks {
NSSize size = NSZeroSize;
[self _drawLabelBlocksInRect:NSZeroRect rightAlign:NO highlighted:NO getSizeOnly:&size];
return size;
}

- (NSImage*)highlightedLabelsPreviewImage {
if (!highlightedLabelsPreviewImage && [labelString length]) {
highlightedLabelsPreviewImage = [[self _labelsPreviewImageOfColor:[NSColor whiteColor]] retain];
}
return highlightedLabelsPreviewImage;
- (void)drawLabelBlocksInRect:(NSRect)aRect rightAlign:(BOOL)onRight highlighted:(BOOL)isHighlighted {
return [self _drawLabelBlocksInRect:aRect rightAlign:onRight highlighted:isHighlighted getSizeOnly:NULL];
}

- (NSImage*)labelsPreviewImage {
if (!labelsPreviewImage && [labelString length]) {
labelsPreviewImage = [[self _labelsPreviewImageOfColor:[NSColor colorWithCalibratedWhite:0.55 alpha:1.0]] retain];
}
return labelsPreviewImage;
}

- (NSImage*)_labelsPreviewImageOfColor:(NSColor*)aColor {
if ([labelString length]) {
float tableFontSize = [[GlobalPrefs defaultPrefs] tableFontSize] - 1.0;
NSFont *font = [NSFont systemFontOfSize:tableFontSize];
NSDictionary *attrs = [NSDictionary dictionaryWithObject:font forKey:NSFontNameAttribute];

//compute dimensions of each word first using nslayoutmanager; -sizeWithAttributes: likes to ignore the font and size here for some reason

static NSTextStorage *textStorage = nil;
static NSTextContainer *textContainer = nil;
static NSLayoutManager *layoutManager = nil;

if (!layoutManager) {
textStorage = [[NSTextStorage alloc] initWithString:@"" attributes:attrs];
textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(1e7, 1e7)];
layoutManager = [[NSLayoutManager alloc] init];
- (void)_drawLabelBlocksInRect:(NSRect)aRect rightAlign:(BOOL)onRight highlighted:(BOOL)isHighlighted getSizeOnly:(NSSize*)reqSize {
//used primarily by UnifiedCell, but also by LabelColumnCell, as well as to determine the width of all label-block-images for this note
//iterate over words in orderedLabelTitles, retrieving images via -[LabelsListController cachedLabelImageForWord:highlighted:]
//if right-align is enabled, then the label-images are queued on the first pass and drawn in reverse on the second

float totalWidth = 0.0, height = 0.0;

if (![labelString length]) goto returnSizeIfNecessary;

NSArray *words = [self orderedLabelTitles];
if (![words count]) goto returnSizeIfNecessary;

NSPoint nextBoxPoint = onRight ? NSMakePoint(NSMaxX(aRect), aRect.origin.y) : aRect.origin;
NSMutableArray *images = reqSize || !onRight ? nil : [NSMutableArray arrayWithCapacity:[words count]];
NSInteger i;

for (i=0; i<(NSInteger)[words count]; i++) {
NSString *word = [words objectAtIndex:i];
if ([word length]) {
NSImage *img = [[delegate labelsListDataSource] cachedLabelImageForWord:word highlighted:isHighlighted];

[textContainer setLineFragmentPadding:0.0];
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
if (!reqSize) {
if (onRight) {
[images addObject:img];
} else {
[img compositeToPoint:nextBoxPoint operation:NSCompositeSourceOver];
nextBoxPoint.x += [img size].width + 4.0;
}
} else {
totalWidth += [img size].width + 4.0;
height = MAX(height, [img size].height);
}
}

NSArray *words = [self orderedLabelTitles];
if (![words count])
return nil;

NSBezierPath *blocksPath = [NSBezierPath bezierPath];
NSPoint nextBoxPoint = NSZeroPoint;
NSUInteger i;
float imageWidth = 0.0;

for (i=0; i<[words count]; i++) {
NSString *word = [words objectAtIndex:i];
if ([word length]) {

//Force the layout manager to layout its text
[[textStorage mutableString] setString:word];
[textStorage setFont:font]; //will infuriatingly revert to measuring Lucida Grande 11 otherwise, despite what it actually says

(void)[layoutManager glyphRangeForTextContainer:textContainer];
NSSize wordSize = [layoutManager usedRectForTextContainer:textContainer].size;

NSRect wordRect = NSMakeRect(nextBoxPoint.x, nextBoxPoint.y, roundf(wordSize.width + 4.0), roundf(tableFontSize * 1.3));
imageWidth += wordRect.size.width + 4.0;

NSBezierPath *stringPath = [NSBezierPath bezierPathWithLayoutManager:layoutManager characterRange:NSMakeRange(0,[word length])
atPoint:NSMakePoint(nextBoxPoint.x + 2.0, 3.0)];
wordRect.origin = nextBoxPoint;

NSBezierPath *backgroundPath = [NSBezierPath bezierPathWithRoundRectInRect:wordRect radius:2.0f];

[backgroundPath setWindingRule:NSEvenOddWindingRule];
[backgroundPath appendBezierPath:stringPath];

[blocksPath appendBezierPath:backgroundPath];

nextBoxPoint = NSMakePoint(roundf(nextBoxPoint.x + wordRect.size.width + 4.0), 0.0);
}

if (!reqSize) {
if (onRight) {
//draw images in reverse instead
for (i = [images count] - 1; i>=0; i--) {
NSImage *img = [images objectAtIndex:i];
nextBoxPoint.x -= [img size].width + 4.0;
[img compositeToPoint:nextBoxPoint operation:NSCompositeSourceOver];
}
}


NSImage *img = [[NSImage alloc] initWithSize:NSMakeSize(imageWidth - 4.0, tableFontSize * 1.3 + 1.5)];
[img lockFocus];

[aColor setFill];
[blocksPath fill];

[img unlockFocus];

return [img autorelease];
} else {
returnSizeIfNecessary:
if (reqSize) *reqSize = NSMakeSize(totalWidth, height);
}
return nil;
}


Expand Down

0 comments on commit b6d50ec

Please sign in to comment.