Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions MacDown 3000.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
BA2B9EC2E95175092A97B41E /* MPResourceWatcherSet.m in Sources */ = {isa = PBXBuildFile; fileRef = E6D070A6E080254A17B3B197 /* MPResourceWatcherSet.m */; };
CCD97578A2886FFE73BD96F7 /* MPMathJaxRenderingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6811F39B13C649FC86C4CE5B /* MPMathJaxRenderingTests.m */; };
CHKBOXTGL0001BUILDFILER /* MPCheckboxToggleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CHKBOXTGL0001FILEREFID /* MPCheckboxToggleTests.m */; };
PRVZMTST0001BUILDFILER /* MPPreviewZoomTests.m in Sources */ = {isa = PBXBuildFile; fileRef = PRVZMTST0001FILEREFID /* MPPreviewZoomTests.m */; };
D29776CC6E7EB5B4AA2E0537 /* MPFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = A81247E840EB5C07669FF165 /* MPFileWatcher.m */; };
D3877A6637DE48017448C8DB /* MPRendererTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E2FC4B9389A012264DC6214 /* MPRendererTestHelpers.m */; };
E087CFBB2125A4D2B37C132F /* MPHTMLResourceURLsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8552820CEE8D00E83AEE3897 /* MPHTMLResourceURLsTests.m */; };
Expand Down Expand Up @@ -547,6 +548,7 @@
B3BD139B59E787FBB9F38228 /* MPEditorViewSubstitutionTests.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = MPEditorViewSubstitutionTests.m; sourceTree = "<group>"; };
C2B84BF8A8BC4F4B871646F8 /* MPScrollSyncTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPScrollSyncTests.m; sourceTree = "<group>"; };
CHKBOXTGL0001FILEREFID /* MPCheckboxToggleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCheckboxToggleTests.m; sourceTree = "<group>"; };
PRVZMTST0001FILEREFID /* MPPreviewZoomTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPreviewZoomTests.m; sourceTree = "<group>"; };
DDDB87873110C02114439C2C /* MPFileWatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFileWatcher.h; sourceTree = "<group>"; };
E6D070A6E080254A17B3B197 /* MPResourceWatcherSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPResourceWatcherSet.m; sourceTree = "<group>"; };
E70ECDD4241C933C00537A46 /* ru-RU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ru-RU"; path = "Localization/ru-RU.lproj/Localizable.strings"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -961,6 +963,7 @@
197TESTS0F00000000197RSTF /* MPRendererStateTests.m */,
C2B84BF8A8BC4F4B871646F8 /* MPScrollSyncTests.m */,
CHKBOXTGL0001FILEREFID /* MPCheckboxToggleTests.m */,
PRVZMTST0001FILEREFID /* MPPreviewZoomTests.m */,
ISSUE285SMARTQTFILEREF /* MPSmartQuoteTests.m */,
1FFEB3261972DAB400B2254F /* MPHTMLTabularizeTests.m */,
1FFEB32F19ABCD1500B2254F /* MPMarkdownRenderingTests.m */,
Expand Down Expand Up @@ -1430,6 +1433,7 @@
03224E1B02E36783D5307F4A /* MPDocumentIOTests.m in Sources */,
B9A8DE030E3748EB899BD45E /* MPScrollSyncTests.m in Sources */,
CHKBOXTGL0001BUILDFILER /* MPCheckboxToggleTests.m in Sources */,
PRVZMTST0001BUILDFILER /* MPPreviewZoomTests.m in Sources */,
ISSUE285SMARTQTBUILDFILE /* MPSmartQuoteTests.m in Sources */,
1F51C9A5194565050015A96F /* MPPreferencesTests.m in Sources */,
1FF1420419A8A24800CF8A6A /* MPUtilityTests.m in Sources */,
Expand Down
136 changes: 127 additions & 9 deletions MacDown/Code/Application/MPToolbarController.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,88 @@
//

#import "MPToolbarController.h"
#import "MPPreferences.h"

// Because we're creating selectors for methods which aren't in this class
#pragma GCC diagnostic ignored "-Wundeclared-selector"
#pragma clang diagnostic ignored "-Wundeclared-selector"


static CGFloat itemWidth = 37;
// Preview-zoom presets must match MPPreviewZoomLevels() in MPDocument.m.
static NSArray<NSNumber *> *MPToolbarPreviewZoomLevels(void)
{
static NSArray *levels = nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
levels = @[@0.5, @0.75, @0.9, @1.0, @1.1, @1.25, @1.5, @2.0];
});
return levels;
}


@implementation MPToolbarController
{
NSArray *toolbarItems;
NSArray *toolbarItemIdentifiers;

/**
* Map toolbar item identifier to it's NSToolbarItem or NSToolbarItemGroup object
*/
NSMutableDictionary *toolbarItemIdentifierObjectDictionary;

/**
* Weak reference to the zoom popup so we can re-sync its selected item
* when the preference changes from elsewhere (menu, keyboard shortcut).
*/
__weak NSPopUpButton *_zoomPopUp;
}

- (id)init
{
self = [super init];

if (!self)
{
return nil;
}

self->toolbarItemIdentifierObjectDictionary = [NSMutableDictionary new];
[self setupToolbarItems];


// Observe NSUserDefaults so the popup's selection reflects external
// changes (View menu actions, ⌘+/⌘-/⌘0). Using KVO on the standard
// defaults avoids threading a sync callback through MPDocument.
[[NSUserDefaults standardUserDefaults]
addObserver:self
forKeyPath:@"previewZoomLevel"
options:NSKeyValueObservingOptionNew
context:NULL];

return self;
}

- (void)dealloc
{
@try {
[[NSUserDefaults standardUserDefaults]
removeObserver:self forKeyPath:@"previewZoomLevel"];
} @catch (NSException *exception) {
// removeObserver may throw if not registered; ignore on teardown.
}
}

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context
{
if ([keyPath isEqualToString:@"previewZoomLevel"])
{
[self syncPreviewZoomDisplay];
}
}


#pragma mark - Private

Expand Down Expand Up @@ -87,10 +134,14 @@ - (void)setupToolbarItems
@[
toggleEditorMenuItem, togglePreviewMenuItem
]
]
],
[self toolbarItemZoomPopUpWithIdentifier:@"preview-zoom" label:NSLocalizedString(@"Preview Zoom", @"Preview pane zoom toolbar item")]
];

self->toolbarItemIdentifiers = [self toolbarItemIdentifiersFromItemsArray:self->toolbarItems];

// Reflect the persisted preference once everything is wired up.
[self syncPreviewZoomDisplay];
}

/**
Expand Down Expand Up @@ -323,13 +374,80 @@ - (NSToolbarItem *)toolbarItemDropDownWithIdentifier:(NSString *)itemIdentifier
[[popupButton lastItem] setTarget:self.document];
[[popupButton lastItem] setAction:menuItem.action];
}

toolbarItem.view = popupButton;

[self->toolbarItemIdentifierObjectDictionary setObject:toolbarItem forKey:itemIdentifier];

return toolbarItem;
}

/**
* Factory method for the preview-zoom popup. Unlike the layout dropdown
* this is a regular (non-pull-down) NSPopUpButton: the currently selected
* item is shown as the button label so the user sees the active zoom
* percentage at a glance. Each menu item is wired to
* -selectPreviewZoom: on the document, with the target zoom level
* (NSNumber) attached as the item's representedObject.
*/
- (NSToolbarItem *)toolbarItemZoomPopUpWithIdentifier:(NSString *)itemIdentifier label:(NSString *)label
{
NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
toolbarItem.label = label;
toolbarItem.paletteLabel = label;
toolbarItem.toolTip = label;

NSPopUpButton *popupButton = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 70, 27) pullsDown:NO];
popupButton.bezelStyle = NSBezelStyleTexturedRounded;
popupButton.focusRingType = NSFocusRingTypeDefault;

NSArray<NSNumber *> *levels = MPToolbarPreviewZoomLevels();
for (NSNumber *level in levels)
{
NSString *title = [NSString stringWithFormat:@"%.0f%%", level.doubleValue * 100.0];
[popupButton addItemWithTitle:title];
NSMenuItem *added = [popupButton lastItem];
added.representedObject = level;
added.target = self.document;
added.action = @selector(selectPreviewZoom:);
}

toolbarItem.view = popupButton;

[self->toolbarItemIdentifierObjectDictionary setObject:toolbarItem forKey:itemIdentifier];
_zoomPopUp = popupButton;

return toolbarItem;
}

/**
* Update the popup's selection to match the current preview-zoom
* preference. If the current preference matches a preset (within
* epsilon), that item is selected. Otherwise the popup falls back to
* the closest preset so the button always shows a sensible label.
*/
- (void)syncPreviewZoomDisplay
{
NSPopUpButton *popup = _zoomPopUp;
if (!popup)
return;

CGFloat current = [MPPreferences sharedInstance].previewZoomLevel;
NSArray<NSNumber *> *levels = MPToolbarPreviewZoomLevels();

NSUInteger nearestIdx = 0;
CGFloat bestDiff = CGFLOAT_MAX;
for (NSUInteger i = 0; i < levels.count; i++)
{
CGFloat diff = fabs(levels[i].doubleValue - current);
if (diff < bestDiff)
{
bestDiff = diff;
nearestIdx = i;
}
}
[popup selectItemAtIndex:(NSInteger)nearestIdx];
}


@end
27 changes: 27 additions & 0 deletions MacDown/Code/Document/MPDocument.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,31 @@
*/
+ (NSString *)toggleCheckboxAtIndex:(NSUInteger)index inMarkdown:(NSString *)markdown;

/**
* Step the preview pane zoom level up to the next preset.
* Snaps to the next preset > current; beeps if already at the maximum.
* Bound to View > Zoom In (Command-+).
*/
- (IBAction)zoomPreviewIn:(id)sender;

/**
* Step the preview pane zoom level down to the previous preset.
* Snaps to the next preset < current; beeps if already at the minimum.
* Bound to View > Zoom Out (Command--).
*/
- (IBAction)zoomPreviewOut:(id)sender;

/**
* Reset the preview pane zoom level to 100%.
* Bound to View > Actual Size (Command-0).
*/
- (IBAction)actualPreviewSize:(id)sender;

/**
* Set the preview pane zoom to the level represented by the sender's
* representedObject (NSNumber). Sender may be an NSMenuItem or
* NSPopUpButton; the toolbar dropdown uses this entry point.
*/
- (IBAction)selectPreviewZoom:(id)sender;

@end
Loading
Loading