Permalink
Browse files

use word-indexed caching of label-block images, rather than per-note …

…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...
1 parent c544699 commit b6d50eca4673521dcb4e2c5ddd1907902aab2ddc @scrod committed Mar 31, 2011
Showing with 154 additions and 145 deletions.
  1. +1 −1 AppController.m
  2. +10 −9 LabelColumnCell.m
  3. +4 −0 LabelsListController.h
  4. +57 −0 LabelsListController.m
  5. +0 −1 NotationController.h
  6. +0 −4 NotationController.m
  7. +8 −8 NoteObject.h
  8. +52 −88 NoteObject.m
  9. +2 −2 NotesTableView.m
  10. +1 −1 UnifiedCell.h
  11. +19 −31 UnifiedCell.m
View
@@ -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];
View
@@ -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];
}
}
View
@@ -29,6 +29,7 @@
@interface LabelsListController : FastListDataSource {
NSCountedSet *allLabels, *filteredLabels;
+ NSMutableDictionary *labelImages;
unsigned *removeIndicies;
}
@@ -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;
View
@@ -24,6 +24,8 @@
#import "LabelsListController.h"
#import "LabelObject.h"
#import "NoteObject.h"
+#import "GlobalPrefs.h"
+#import "NSBezierPath_NV.h"
#import "NSCollection_utils.h"
@@ -44,6 +46,7 @@ - (id)init {
- (void)dealloc {
+ [labelImages release];
[allLabels release];
[filteredLabels release];
[super dealloc];
@@ -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 {
@@ -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
@@ -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];
View
@@ -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;
View
@@ -1518,10 +1518,6 @@ - (void)regeneratePreviewsForColumn:(NSTableColumn*)col visibleFilteredRows:(NSR
}
}
-- (void)invalidateAllLabelPreviewImages {
- [allNotes makeObjectsPerformSelector:@selector(invalidateLabelsPreviewImage)];
-}
-
- (void)regenerateAllPreviews {
[allNotes makeObjectsPerformSelector:@selector(updateTablePreviewString)];
}
View
@@ -29,6 +29,7 @@
@class LabelObject;
@class WALStorageController;
@class NotesTableView;
+@class ExternalEditor;
typedef struct _NoteFilterContext {
char* needle;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
View
@@ -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);
@@ -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];
}
@@ -968,7 +968,6 @@ - (BOOL)_setLabelString:(NSString*)newLabelString {
cLabelsFoundPtr = cLabels = replaceString(cLabels, [labelString lowercaseUTF8String]);
[self updateLabelConnections];
- [self invalidateLabelsPreviewImage];
return YES;
}
return NO;
@@ -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;
}
Oops, something went wrong.

0 comments on commit b6d50ec

Please sign in to comment.