cocoa_progress_callback.mm (6951B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "cocoa_progress_callback.h" 5 6 #include "common/cocoa_tools.h" 7 #include "common/log.h" 8 9 Log_SetChannel(CocoaProgressCallback); 10 11 CocoaProgressCallback::CocoaProgressCallback() : ProgressCallback() 12 { 13 Create(); 14 } 15 16 CocoaProgressCallback::~CocoaProgressCallback() 17 { 18 Destroy(); 19 } 20 21 void CocoaProgressCallback::PushState() 22 { 23 ProgressCallback::PushState(); 24 } 25 26 void CocoaProgressCallback::PopState() 27 { 28 ProgressCallback::PopState(); 29 UpdateProgress(); 30 } 31 32 void CocoaProgressCallback::SetCancellable(bool cancellable) 33 { 34 ProgressCallback::SetCancellable(cancellable); 35 } 36 37 void CocoaProgressCallback::SetTitle(const std::string_view title) 38 { 39 @autoreleasepool { 40 dispatch_async(dispatch_get_main_queue(), [this, title = [CocoaTools::StringViewToNSString(title) retain]]() { 41 [m_window setTitle:title]; 42 [title release]; 43 }); 44 } 45 } 46 47 void CocoaProgressCallback::SetStatusText(const std::string_view text) 48 { 49 ProgressCallback::SetStatusText(text); 50 @autoreleasepool { 51 dispatch_async(dispatch_get_main_queue(), [this, text = [CocoaTools::StringViewToNSString(text) retain]]() { 52 [m_status setStringValue:text]; 53 [text release]; 54 }); 55 } 56 } 57 58 void CocoaProgressCallback::SetProgressRange(u32 range) 59 { 60 ProgressCallback::SetProgressRange(range); 61 UpdateProgress(); 62 } 63 64 void CocoaProgressCallback::SetProgressValue(u32 value) 65 { 66 ProgressCallback::SetProgressValue(value); 67 UpdateProgress(); 68 } 69 70 bool CocoaProgressCallback::Create() 71 { 72 @autoreleasepool 73 { 74 const NSRect window_rect = 75 NSMakeRect(0.0f, 0.0f, static_cast<float>(WINDOW_WIDTH), static_cast<float>(WINDOW_HEIGHT)); 76 constexpr NSWindowStyleMask style = NSWindowStyleMaskTitled; 77 m_window = [[NSWindow alloc] initWithContentRect:window_rect 78 styleMask:style 79 backing:NSBackingStoreBuffered 80 defer:NO]; 81 82 NSView* m_view; 83 m_view = [[NSView alloc] init]; 84 [m_window setContentView:m_view]; 85 86 int x = WINDOW_MARGIN; 87 int y = WINDOW_HEIGHT - WINDOW_MARGIN; 88 89 y -= 16 + SUBWINDOW_PADDING; 90 m_status = [NSTextField labelWithString:@"Initializing..."]; 91 [m_status setFrame:NSMakeRect(x, y, SUBWINDOW_WIDTH, 16)]; 92 [m_view addSubview:m_status]; 93 94 y -= 16 + SUBWINDOW_PADDING; 95 m_progress = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(x, y, SUBWINDOW_WIDTH, 16)]; 96 [m_progress setMinValue:0]; 97 [m_progress setMaxValue:100]; 98 [m_progress setDoubleValue:0]; 99 [m_progress setIndeterminate:NO]; 100 [m_view addSubview:m_progress]; 101 102 y -= 170 + SUBWINDOW_PADDING; 103 m_text_scroll = [[NSScrollView alloc] initWithFrame:NSMakeRect(x, y, SUBWINDOW_WIDTH, 170)]; 104 [m_text_scroll setBorderType:NSBezelBorder]; 105 [m_text_scroll setHasVerticalScroller:YES]; 106 [m_text_scroll setHasHorizontalScroller:NO]; 107 108 const NSSize content_size = [m_text_scroll contentSize]; 109 m_text = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, content_size.width, content_size.height)]; 110 [m_text setMinSize:NSMakeSize(0, content_size.height)]; 111 [m_text setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; 112 [m_text setVerticallyResizable:YES]; 113 [m_text setHorizontallyResizable:NO]; 114 [m_text setAutoresizingMask:NSViewWidthSizable]; 115 [m_text setUsesAdaptiveColorMappingForDarkAppearance:YES]; 116 [[m_text textContainer] setContainerSize:NSMakeSize(content_size.width, FLT_MAX)]; 117 [[m_text textContainer] setWidthTracksTextView:YES]; 118 [m_text_scroll setDocumentView:m_text]; 119 [m_view addSubview:m_text_scroll]; 120 121 [m_window center]; 122 [m_window setIsVisible:TRUE]; 123 [m_window makeKeyAndOrderFront:nil]; 124 [m_window setReleasedWhenClosed:NO]; 125 } 126 127 return true; 128 } 129 130 void CocoaProgressCallback::Destroy() 131 { 132 if (m_window == nil) 133 return; 134 135 [m_window close]; 136 137 m_text = nil; 138 m_progress = nil; 139 m_status = nil; 140 141 [m_view release]; 142 m_view = nil; 143 144 [m_window release]; 145 m_window = nil; 146 } 147 148 void CocoaProgressCallback::UpdateProgress() 149 { 150 const float percent = (static_cast<float>(m_progress_value) / static_cast<float>(m_progress_range)) * 100.0f; 151 dispatch_async(dispatch_get_main_queue(), [this, percent]() { 152 [m_progress setDoubleValue:percent]; 153 }); 154 } 155 156 void CocoaProgressCallback::DisplayError(const std::string_view message) 157 { 158 ERROR_LOG(message); 159 AppendMessage(message); 160 } 161 162 void CocoaProgressCallback::DisplayWarning(const std::string_view message) 163 { 164 WARNING_LOG(message); 165 AppendMessage(message); 166 } 167 168 void CocoaProgressCallback::DisplayInformation(const std::string_view message) 169 { 170 INFO_LOG(message); 171 AppendMessage(message); 172 } 173 174 void CocoaProgressCallback::AppendMessage(const std::string_view message) 175 { 176 @autoreleasepool 177 { 178 NSString* nsmessage = [[CocoaTools::StringViewToNSString(message) stringByAppendingString:@"\n"] retain]; 179 dispatch_async(dispatch_get_main_queue(), [this, nsmessage]() { 180 @autoreleasepool 181 { 182 NSAttributedString* attr = [[[NSAttributedString alloc] initWithString:nsmessage] autorelease]; 183 [[m_text textStorage] appendAttributedString:attr]; 184 [m_text scrollRangeToVisible:NSMakeRange([[m_text string] length], 0)]; 185 [nsmessage release]; 186 } 187 }); 188 } 189 } 190 191 void CocoaProgressCallback::DisplayDebugMessage(const std::string_view message) 192 { 193 DEV_LOG(message); 194 } 195 196 void CocoaProgressCallback::ModalError(const std::string_view message) 197 { 198 if (![NSThread isMainThread]) 199 { 200 dispatch_sync(dispatch_get_main_queue(), [this, message]() { ModalError(message); }); 201 return; 202 } 203 204 @autoreleasepool 205 { 206 NSAlert* alert = [[[NSAlert alloc] init] autorelease]; 207 [alert setMessageText:CocoaTools::StringViewToNSString(message)]; 208 [alert setAlertStyle:NSAlertStyleCritical]; 209 [alert runModal]; 210 } 211 } 212 213 bool CocoaProgressCallback::ModalConfirmation(const std::string_view message) 214 { 215 if (![NSThread isMainThread]) 216 { 217 bool result; 218 dispatch_sync(dispatch_get_main_queue(), [this, message, &result]() { result = ModalConfirmation(message); }); 219 return result; 220 } 221 222 bool result; 223 @autoreleasepool 224 { 225 NSAlert* alert = [[[NSAlert alloc] init] autorelease]; 226 [alert setMessageText:CocoaTools::StringViewToNSString(message)]; 227 [alert addButtonWithTitle:@"Yes"]; 228 [alert addButtonWithTitle:@"No"]; 229 result = ([alert runModal] == NSAlertFirstButtonReturn); 230 } 231 232 return result; 233 } 234 235 void CocoaProgressCallback::ModalInformation(const std::string_view message) 236 { 237 if (![NSThread isMainThread]) 238 { 239 dispatch_sync(dispatch_get_main_queue(), [this, message]() { ModalInformation(message); }); 240 return; 241 } 242 243 @autoreleasepool 244 { 245 NSAlert* alert = [[[NSAlert alloc] init] autorelease]; 246 [alert setMessageText:CocoaTools::StringViewToNSString(message)]; 247 [alert runModal]; 248 } 249 }