From 03793ad2dfdc422cb261b12588300e9d993c7353 Mon Sep 17 00:00:00 2001 From: SteffeyDev Date: Sat, 27 Mar 2021 13:53:24 -0400 Subject: [PATCH] Updating launch sequence, log view refactor, main menu improvements, misc other fixes --- atemOSC/AppDelegate.h | 3 + atemOSC/AppDelegate.mm | 51 +++--- atemOSC/ConnectionView.mm | 25 ++- atemOSC/FeedbackMonitors.mm | 9 +- atemOSC/LogView.h | 16 +- atemOSC/LogView.mm | 121 ++++++++----- atemOSC/MainWindow.xib | 198 +++++++++++++--------- atemOSC/Switcher.mm | 6 +- atemOSC/Utilities.h | 4 +- atemOSC/Utilities.mm | 13 +- atemOSC/atemOSC.xcodeproj/project.pbxproj | 6 +- 11 files changed, 270 insertions(+), 182 deletions(-) diff --git a/atemOSC/AppDelegate.h b/atemOSC/AppDelegate.h index 0a82aeb..486b15e 100644 --- a/atemOSC/AppDelegate.h +++ b/atemOSC/AppDelegate.h @@ -52,7 +52,10 @@ @property (retain) NSMutableArray* switchers; - (void)incomingPortChanged:(int)inPortValue; + +- (IBAction)bugFeatureButtonPressed:(id)sender; - (IBAction)githubPageButtonPressed:(id)sender; +- (IBAction)websiteButtonPressed:(id)sender; - (void)logMessage:(NSString *)message; diff --git a/atemOSC/AppDelegate.mm b/atemOSC/AppDelegate.mm index e024477..29ba71d 100644 --- a/atemOSC/AppDelegate.mm +++ b/atemOSC/AppDelegate.mm @@ -38,8 +38,6 @@ @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - [self setupMenu]; - endpoints = [[NSMutableArray alloc] init]; mOscReceiver = [[OSCReceiver alloc] initWithDelegate:self]; @@ -91,43 +89,23 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification } [self checkForUpdate]; -} - -- (void)applicationWillBecomeActive:(NSNotification *)notification -{ + dispatch_async(dispatch_get_main_queue(), ^{ - if (!self->window) - { - Window *window = (Window *) [[NSApplication sharedApplication] mainWindow]; - self->window = window; - } + Window *window = (Window *) [[NSApplication sharedApplication] mainWindow]; + self->window = window; + [self->window loadSettingsFromPreferences]; - - if ([[self->window connectionView] switcher] != nil) - { - [[self->window outlineView] refreshList]; - [[self->window connectionView] loadFromSwitcher:[[self->window connectionView] switcher]]; - } - else - { - [[self->window outlineView] reloadData]; - NSIndexSet* indexes = [[NSIndexSet alloc] initWithIndex:1]; - [[self->window outlineView] selectRowIndexes:indexes byExtendingSelection:NO]; - } - [[self->window logView] flushMessages]; + [[self->window outlineView] reloadData]; + NSIndexSet* indexes = [[NSIndexSet alloc] initWithIndex:1]; + [[self->window outlineView] selectRowIndexes:indexes byExtendingSelection:NO]; }); } -- (void)setupMenu +- (void)applicationWillBecomeActive:(NSNotification *)notification { - NSMenu* edit = [[[[NSApplication sharedApplication] mainMenu] itemWithTitle: @"Edit"] submenu]; - if ([[edit itemAtIndex: [edit numberOfItems] - 1] action] == NSSelectorFromString(@"orderFrontCharacterPalette:")) - [edit removeItemAtIndex: [edit numberOfItems] - 1]; - if ([[edit itemAtIndex: [edit numberOfItems] - 1] action] == NSSelectorFromString(@"startDictation:")) - [edit removeItemAtIndex: [edit numberOfItems] - 1]; - if ([[edit itemAtIndex: [edit numberOfItems] - 1] isSeparatorItem]) - [edit removeItemAtIndex: [edit numberOfItems] - 1]; + [[self->window outlineView] refreshList]; + [[self->window connectionView] loadFromSwitcher:[[self->window connectionView] switcher]]; } - (void)checkForUpdate @@ -191,6 +169,15 @@ - (IBAction)githubPageButtonPressed:(id)sender [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://github.com/SteffeyDev/atemOSC/"]]; } +- (IBAction)bugFeatureButtonPressed:(id)sender; +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://github.com/SteffeyDev/atemOSC/issues/new"]]; +} + +- (IBAction)websiteButtonPressed:(id)sender; +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.atemosc.com"]]; +} - (void) addSwitcher { diff --git a/atemOSC/ConnectionView.mm b/atemOSC/ConnectionView.mm index cce106d..d0f5715 100644 --- a/atemOSC/ConnectionView.mm +++ b/atemOSC/ConnectionView.mm @@ -140,12 +140,16 @@ - (void)controlTextDidEndEditing:(NSNotification *)notification NSTextField* textField = (NSTextField *)[notification object]; Window *window = (Window *) [[NSApplication sharedApplication] mainWindow]; - if ((textField == feedbackIpAddressTextField || textField == ipAddressTextField) && ![self isValidIPAddress:[textField stringValue]]) + if ([textField stringValue].length > 0 && (textField == feedbackIpAddressTextField || textField == ipAddressTextField) && ![self isValidIPAddress:[textField stringValue]]) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Invalid IP Adress"]; [alert setInformativeText:@"Please enter a valid IPv4 Address"]; [alert beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow] completionHandler:nil]; + if (textField == feedbackIpAddressTextField) + [feedbackIpAddressTextField setStringValue:switcher.feedbackIpAddress]; + else + [ipAddressTextField setStringValue:switcher.ipAddress]; return; } @@ -186,16 +190,19 @@ - (void)controlTextDidEndEditing:(NSNotification *)notification else if (textField == nicknameTextField) { AppDelegate* appDel = (AppDelegate *) [[NSApplication sharedApplication] delegate]; - for (Switcher *s : [appDel switchers]) + if ([textField stringValue].length > 0) { - if (s.nickname != nil && [textField stringValue].length > 0 && [s.nickname isEqualToString: [textField stringValue]]) + for (Switcher *s : [appDel switchers]) { - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:@"Duplicate Nickname"]; - [alert setInformativeText:@"Please assign a unique nickname to each switcher"]; - [alert beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow] completionHandler:nil]; - [textField setStringValue:switcher.nickname]; - return; + if (s.nickname != nil && ![s.uid isEqualToString: switcher.uid] && [s.nickname isEqualToString: [textField stringValue]]) + { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Duplicate Nickname"]; + [alert setInformativeText:@"Please assign a unique nickname to each switcher"]; + [alert beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow] completionHandler:nil]; + [textField setStringValue:switcher.nickname]; + return; + } } } [switcher setNickname: [textField stringValue]]; diff --git a/atemOSC/FeedbackMonitors.mm b/atemOSC/FeedbackMonitors.mm index fc5bfb3..c7c30fe 100644 --- a/atemOSC/FeedbackMonitors.mm +++ b/atemOSC/FeedbackMonitors.mm @@ -1090,7 +1090,9 @@ uint16_t hours; uint8_t minutes, seconds, frames; switcher.mHyperdecks[hyperdeckId_]->GetCurrentClipTime(&hours, &minutes, &seconds, &frames); - sendFeedbackMessage(switcher, [NSString stringWithFormat:@"/hyperdeck/%lld/clip-time", hyperdeckId_], [OSCValue createWithString:[NSString stringWithFormat:@"%d:%d:%d:%d", hours, minutes, seconds, frames]]); + + // The clip-time message gets sent every 100ms, which is too frequent to show in log + sendFeedbackMessage(switcher, [NSString stringWithFormat:@"/hyperdeck/%lld/clip-time", hyperdeckId_], [OSCValue createWithString:[NSString stringWithFormat:@"%d:%d:%d:%d", hours, minutes, seconds, frames]], false); } } @@ -1101,8 +1103,9 @@ uint16_t hours; uint8_t minutes, seconds, frames; switcher.mHyperdecks[hyperdeckId_]->GetCurrentTimelineTime(&hours, &minutes, &seconds, &frames); - - sendFeedbackMessage(switcher, [NSString stringWithFormat:@"/hyperdeck/%lld/timeline-time", hyperdeckId_], [OSCValue createWithString:[NSString stringWithFormat:@"%d:%d:%d:%d", hours, minutes, seconds, frames]]); + + // The timeline-time message gets sent every 100ms, which is too frequent to show in log + sendFeedbackMessage(switcher, [NSString stringWithFormat:@"/hyperdeck/%lld/timeline-time", hyperdeckId_], [OSCValue createWithString:[NSString stringWithFormat:@"%d:%d:%d:%d", hours, minutes, seconds, frames]], false); } } diff --git a/atemOSC/LogView.h b/atemOSC/LogView.h index 5613f67..2f3cb33 100644 --- a/atemOSC/LogView.h +++ b/atemOSC/LogView.h @@ -9,19 +9,21 @@ NS_ASSUME_NONNULL_BEGIN -@interface LogView : NSVisualEffectView { - NSMutableString* basicLog; // no debug info - NSMutableString* fullLog; // normal and debug log - BOOL debugMode; +@interface LogView : NSView { + NSMutableArray* basicLog; // no debug info + NSMutableArray* fullLog; // normal and debug log + BOOL debugMode; + BOOL live; + NSDateFormatter* formatter; } -@property (assign) IBOutlet NSTextView *logTextView; @property (assign) IBOutlet NSButton *debugCheckbox; +@property (assign) IBOutlet NSTableView *tableView; +@property (assign) IBOutlet NSTextField *pausedLabel; -- (IBAction)debugChanged:(id)sender; +- (IBAction)debugChanged:(id)sender; -- (void)flushMessages; - (void)logMessage:(NSString *)message; @end diff --git a/atemOSC/LogView.mm b/atemOSC/LogView.mm index 4a98825..a7802fe 100644 --- a/atemOSC/LogView.mm +++ b/atemOSC/LogView.mm @@ -20,66 +20,111 @@ - (instancetype)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { - [self setBlendingMode:NSVisualEffectBlendingModeBehindWindow]; - [self setMaterial:NSVisualEffectMaterialDark]; - [self setState:NSVisualEffectStateActive]; - self->fullLog = [[NSMutableString alloc] init]; - self->basicLog = [[NSMutableString alloc] init]; + //[self setBlendingMode:NSVisualEffectBlendingModeBehindWindow]; + //[self setMaterial:NSVisualEffectMaterialDark]; + //[self setState:NSVisualEffectStateActive]; + self->fullLog = [[NSMutableArray alloc] init]; + self->basicLog = [[NSMutableArray alloc] init]; self->debugMode = false; + self->live = true; + self->formatter = [[NSDateFormatter alloc] init]; + [self->formatter setDateFormat:@"HH:mm:ss"]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userScolled) name:NSScrollViewDidLiveScrollNotification object:nil]; + + NSTimer* timer = [NSTimer timerWithTimeInterval:0.5 + target:self + selector:@selector(reloadData) + userInfo:nil + repeats:YES]; + [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; + return self; } return nil; } -- (void)logMessage:(NSString *)message +// This will be run twice a second +- (void)reloadData { - if (message) { - NSLog(@"%@", message); - - NSDate *now = [NSDate date]; - NSDateFormatter *formatter = nil; - formatter = [[NSDateFormatter alloc] init]; - [formatter setDateFormat:@"HH:mm:ss"]; - - NSString *messageWithNewLine = [NSString stringWithFormat:@"[%@] %@\n", [formatter stringFromDate:now], message]; - NSMutableAttributedString *attributedMessage = [[NSMutableAttributedString alloc]initWithString:messageWithNewLine]; + if (self->live) + { + if (self->fullLog.count > 2000) + { + [self->fullLog removeObjectsInRange:NSMakeRange(0, fmax(self->fullLog.count - 2100, 100))]; + } + if (self->basicLog.count > 2000) + { + [self->basicLog removeObjectsInRange:NSMakeRange(0, fmax(self->basicLog.count - 2100, 100))]; + } + [[self tableView] reloadData]; + [[self tableView] scrollToEndOfDocument:nil]; + } +} - [fullLog appendString:messageWithNewLine]; - if (![message containsString:@"[Debug]"]) - [basicLog appendString:messageWithNewLine]; +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return debugMode ? fullLog.count : basicLog.count; +} - if (![message containsString:@"[Debug]"] || debugMode) +- (NSTableCellView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + NSTableCellView *cell = [tableView makeViewWithIdentifier:@"cell" owner:self]; + [cell.textField setStringValue: debugMode ? [fullLog objectAtIndex:row] : [basicLog objectAtIndex:row]]; + return cell; +} + +- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex +{ + return NO; +} + +// If the user scrolls up in the log, pause live updates so that they can inspect the log +// When they scroll back to the bottom, resume live updates +- (void)userScolled +{ + BOOL scrollAtBottom = [[[[self tableView] enclosingScrollView] verticalScroller] floatValue] == 1.0; + BOOL scrollNeeded = [[[self tableView] enclosingScrollView] documentView].bounds.size.height > [[[self tableView] enclosingScrollView] contentView].bounds.size.height; + if (scrollAtBottom || !scrollNeeded) + { + if (self->live == NO) { + [[self pausedLabel] setHidden:YES]; + self->live = YES; dispatch_async(dispatch_get_main_queue(), ^{ - NSColor *color = [[self logTextView] textColor]; - [[self logTextView].textStorage appendAttributedString:attributedMessage]; - [[self logTextView] scrollRangeToVisible: NSMakeRange([self logTextView].string.length, 0)]; - - // Need to set color to original color, because adding attributed string resets the text color to black for some reason - [[self logTextView] setTextColor:color]; + [[self tableView] reloadData]; + [[self tableView] layout]; + [[self tableView] scrollToEndOfDocument:nil]; }); } } + else if (self->live == YES) + { + [[self pausedLabel] setHidden:NO]; + self->live = NO; + } } -- (void)flushMessages +- (void)logMessage:(NSString *)message { - NSColor *color = [[self logTextView] textColor]; - if ([[self debugCheckbox] state]) - { - [[[self logTextView] textStorage] setAttributedString:[[NSAttributedString alloc] initWithString:fullLog]]; - } - else - { - [[[self logTextView] textStorage] setAttributedString:[[NSAttributedString alloc] initWithString:basicLog]]; + if (message) { + NSLog(@"%@", message); + + NSDate *now = [NSDate date]; + NSString *messageWithTime = [NSString stringWithFormat:@"[%@] %@", [self->formatter stringFromDate:now], message]; + BOOL isDebugMessage = [message containsString:@"[Debug]"]; // calc here for performance + + // Keep this update small and fast, do more heavy lifting on the task run once a second + dispatch_async(dispatch_get_main_queue(), ^{ + [self->fullLog addObject:messageWithTime]; + if (!isDebugMessage) + [self->basicLog addObject:messageWithTime]; + }); } - [[self logTextView] scrollRangeToVisible: NSMakeRange([self logTextView].string.length, 0)]; - [[self logTextView] setTextColor:color]; } - (IBAction)debugChanged:(id)sender { self->debugMode = [[self debugCheckbox] state]; - [self flushMessages]; + [[self tableView] reloadData]; } @end diff --git a/atemOSC/MainWindow.xib b/atemOSC/MainWindow.xib index 5c6dbe1..3abd09e 100644 --- a/atemOSC/MainWindow.xib +++ b/atemOSC/MainWindow.xib @@ -18,34 +18,34 @@ - - + + - + - + - + - + - + - + - + @@ -233,21 +233,21 @@ - + - + - + - + @@ -258,7 +258,7 @@ - + @@ -272,7 +272,7 @@ - + @@ -280,7 +280,7 @@ - + @@ -291,7 +291,7 @@ - + @@ -299,7 +299,7 @@ - + @@ -313,7 +313,7 @@ - + @@ -321,7 +321,7 @@ - + @@ -329,7 +329,7 @@ - + @@ -337,7 +337,7 @@ - + @@ -351,7 +351,7 @@ - + @@ -359,7 +359,7 @@ - + @@ -377,7 +377,7 @@ - + Feedback messages are OSC messages that reflect the current state of the switcher. They are sent when any value on the switcher is changed by atemOSC or any other application, such as ATEM Software Control. @@ -386,7 +386,7 @@ + - + + @@ -613,7 +664,6 @@ - @@ -626,14 +676,13 @@ + + + - - - - @@ -659,45 +708,34 @@ - + + + + + - + - + - - - - - - - - - - - + + - + - + + - + - - - - - - - - + - + diff --git a/atemOSC/Switcher.mm b/atemOSC/Switcher.mm index 42a69f3..ae99379 100644 --- a/atemOSC/Switcher.mm +++ b/atemOSC/Switcher.mm @@ -498,13 +498,13 @@ - (void)switcherConnected [self logMessage:[NSString stringWithFormat:@"[Debug] Could not create IBMDSwitcherHyperDeckIterator iterator. code: %d", HRESULT_CODE(result)]]; } - if (FAILED(mSwitcher->QueryInterface(IID_IBMDSwitcherRecordAV, (void**)&mRecordAV))) + if (SUCCEEDED(mSwitcher->QueryInterface(IID_IBMDSwitcherRecordAV, (void**)&mRecordAV))) { - [self logMessage:@"[Debug] Could not get IID_IBMDSwitcherRecordAV interface"]; + mRecordAV->AddCallback(mRecordAVMonitor); } else { - mRecordAV->AddCallback(mRecordAVMonitor); + [self logMessage:@"[Debug] Could not get IBMDSwitcherRecordAV interface"]; } [self setIsConnected: YES]; diff --git a/atemOSC/Utilities.h b/atemOSC/Utilities.h index 4f5afe9..dc9f3ce 100644 --- a/atemOSC/Utilities.h +++ b/atemOSC/Utilities.h @@ -21,7 +21,7 @@ extern NSString* getDescriptionOfMacro(Switcher *s, uint32_t index); extern void activateChannel(Switcher *s, int me, int channel, bool program); extern bool stringIsNumber(NSString * str); extern NSArray *mapObjectsUsingBlock(NSArray *array, id (^block)(id obj, NSUInteger idx)); -void sendFeedbackMessage(Switcher *s, NSString *address, OSCValue* val); -void sendFeedbackMessage(Switcher *s, NSString *address, OSCValue* val, int me); +void sendFeedbackMessage(Switcher *s, NSString *address, OSCValue* val, bool printToLog = true); +void sendFeedbackMessage(Switcher *s, NSString *address, OSCValue* val, int me, bool printToLog = true); #endif /* Utilities_hpp */ diff --git a/atemOSC/Utilities.mm b/atemOSC/Utilities.mm index 55a510e..eb2836b 100644 --- a/atemOSC/Utilities.mm +++ b/atemOSC/Utilities.mm @@ -188,7 +188,7 @@ bool stringIsNumber(NSString * str) } // address will start with a forward slash -void sendFeedbackMessage(Switcher *s, NSString *address, OSCValue* val) { +void sendFeedbackMessage(Switcher *s, NSString *address, OSCValue* val, bool printToLog) { // If a switcher nickname is set, they probably have multiple switchers connected // and are thus using nicknames, so include nickname in the feedback address if (s.nickname && s.nickname.length > 0) @@ -200,20 +200,21 @@ void sendFeedbackMessage(Switcher *s, NSString *address, OSCValue* val) { [msg addValue:val]; [s.outPort sendThisMessage:msg]; - [s logMessage:[NSString stringWithFormat:@"Sending feedback message: %@ %@", address, val]]; + if (printToLog) + [s logMessage:[NSString stringWithFormat:@"Sending feedback message: %@ %@", address, val]]; } // address will start with a forward slash -void sendFeedbackMessage(Switcher *s, NSString *address, OSCValue* val, int me) { +void sendFeedbackMessage(Switcher *s, NSString *address, OSCValue* val, int me, bool printToLog) { // If there are multiple mix effect blocks on this switcher, include the block number in the feedback string if ([s mMixEffectBlocks].size() > 1) { - sendFeedbackMessage(s, [NSString stringWithFormat:@"/me/%d%@", me, address], val); + sendFeedbackMessage(s, [NSString stringWithFormat:@"/me/%d%@", me, address], val, printToLog); // If the first me, send message with no /me for backward compatability if (me == 1) - sendFeedbackMessage(s, address, val); + sendFeedbackMessage(s, address, val, printToLog); } else - sendFeedbackMessage(s, address, val); + sendFeedbackMessage(s, address, val, printToLog); } diff --git a/atemOSC/atemOSC.xcodeproj/project.pbxproj b/atemOSC/atemOSC.xcodeproj/project.pbxproj index 002ce65..c9b347d 100644 --- a/atemOSC/atemOSC.xcodeproj/project.pbxproj +++ b/atemOSC/atemOSC.xcodeproj/project.pbxproj @@ -338,6 +338,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = x86_64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -365,7 +366,7 @@ LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @executable_path/../Frameworks"; MACH_O_TYPE = mh_execute; MACOSX_DEPLOYMENT_TARGET = 10.9; - MARKETING_VERSION = 4.1.0; + MARKETING_VERSION = 4.1.4; PRODUCT_BUNDLE_IDENTIFIER = com.atemosc.atemOSC; PRODUCT_NAME = atemOSC; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -380,6 +381,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = x86_64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -405,7 +407,7 @@ LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @executable_path/../Frameworks"; MACH_O_TYPE = mh_execute; MACOSX_DEPLOYMENT_TARGET = 10.9; - MARKETING_VERSION = 4.1.0; + MARKETING_VERSION = 4.1.4; PRODUCT_BUNDLE_IDENTIFIER = com.atemosc.atemOSC; PRODUCT_NAME = atemOSC; PROVISIONING_PROFILE_SPECIFIER = "";