1#import "Options.h" 2#import "BoolOptionUI.h" 3#import "GroupOptionUI.h" 4#import "EnumOptionUI.h" 5#import "StringOptionUI.h" 6#import "PathOptionUI.h" 7#import "LongOptionUI.h" 8#import "FloatOptionUI.h" 9#import "OCLocalStrings.h" 10#import "OptionsPanel.h" 11#import "LDViewCategories.h" 12 13@implementation Options 14 15- (id) init 16{ 17 self = [super init]; 18 if (self != nil) 19 { 20 margin = 6.0f; 21 spacing = 6.0f; 22 [self ldvLoadNibNamed:@"Options" topLevelObjects:&topLevelObjects]; 23 [topLevelObjects retain]; 24 } 25 return self; 26} 27 28- (void)dealloc 29{ 30 [optionUIs release]; 31 [self releaseTopLevelObjects:topLevelObjects orTopLevelObject:panel]; 32 [super dealloc]; 33} 34 35- (void)calcOptionHeightWithEnum:(NSEnumerator *)enumerator optionUI:(OptionUI *)optionUI y:(CGFloat &)y width:(CGFloat)width leftMargin:(CGFloat)leftMargin rightMargin:(CGFloat)rightMargin numberWidth:(CGFloat)numberWidth optimalWidth:(CGFloat &)optimalWidth update:(bool)update enabled:(BOOL)enabled 36{ 37 LDExporterSetting *setting = [optionUI setting]; 38 LDExporterSetting::Type type = setting->getType(); 39 CGFloat otherWidth = 0.0f; 40 41 if (type == LDExporterSetting::TLong || type == LDExporterSetting::TFloat) 42 { 43 y += [optionUI updateLayoutX:margin + leftMargin y:y width:numberWidth - leftMargin - rightMargin update:update optimalWidth:optimalWidth] + spacing; 44 } 45 else 46 { 47 y += [optionUI updateLayoutX:margin + leftMargin y:y width:width - leftMargin - rightMargin update:update optimalWidth:otherWidth] + spacing; 48 } 49 [optionUI setEnabled:enabled]; 50 if (setting->getGroupSize() > 0) 51 { 52 CGFloat groupLeftMargin = [optionUI leftGroupMargin]; 53 CGFloat groupRightMargin = [optionUI rightGroupMargin]; 54 int i; 55 56 enabled = [optionUI groupEnabled]; 57 leftMargin += groupLeftMargin; 58 rightMargin += groupRightMargin; 59 optimalWidth -= groupLeftMargin + groupRightMargin; 60 for (i = 0; i < setting->getGroupSize(); i++) 61 { 62 OptionUI *newOptionUI = [enumerator nextObject]; 63 64 if (!newOptionUI) 65 { 66 // This is an error condition, but don't crash. 67 break; 68 } 69 [self calcOptionHeightWithEnum:enumerator optionUI:newOptionUI y:y width:width leftMargin:leftMargin rightMargin:rightMargin numberWidth:numberWidth optimalWidth:optimalWidth update:update enabled:enabled]; 70 } 71 if ([optionUI bottomGroupMargin] > 0.0f) 72 { 73 if (update) 74 { 75 [(GroupOptionUI *)optionUI closeGroupAtY:y - spacing]; 76 } 77 y += [optionUI bottomGroupMargin]; 78 } 79 optimalWidth += groupLeftMargin + groupRightMargin; 80 } 81} 82 83- (CGFloat)calcHeightForWidth:(CGFloat)width optimalWidth:(CGFloat &)optimalWidth update:(bool)update 84{ 85 CGFloat y = margin; 86 CGFloat numberWidth; 87 NSEnumerator *enumerator = [optionUIs objectEnumerator]; 88 OptionUI *optionUI; 89 90 width -= margin * 2.0f; 91 if (update) 92 { 93 numberWidth = optimalWidth; 94 } 95 else 96 { 97 numberWidth = width; 98 } 99 while ((optionUI = [enumerator nextObject]) != nil) 100 { 101 [self calcOptionHeightWithEnum:enumerator optionUI:optionUI y:y width:width leftMargin:0 rightMargin:0 numberWidth:numberWidth optimalWidth:optimalWidth update:update enabled:YES]; 102 } 103 return y; 104} 105 106- (void)calcSize 107{ 108 NSSize size; 109 CGFloat width; 110 CGFloat optimalWidth = 0.0f; 111 CGFloat height; 112 bool scrollNeeded; 113 114 [scrollView setHasVerticalScroller:NO]; 115 size = [scrollView contentSize]; 116 width = size.width; 117 height = [self calcHeightForWidth:width optimalWidth:optimalWidth update:false]; 118 scrollNeeded = height > size.height; 119 size.height = height; 120 [docView setFrameSize:size]; 121 if (scrollNeeded) 122 { 123 [scrollView setHasVerticalScroller:YES]; 124 size = [scrollView contentSize]; 125 width = size.width; 126 optimalWidth = 0; 127 height = [self calcHeightForWidth:width optimalWidth:optimalWidth update:false]; 128 size.height = height; 129 [docView setFrameSize:size]; 130 } 131 [self calcHeightForWidth:width optimalWidth:optimalWidth update:true]; 132 [scrollView setNeedsDisplay:YES]; 133} 134 135- (void)awakeFromNib 136{ 137} 138 139- (IBAction)ok:(id)sender 140{ 141 NSString *error = nil; 142 NSEnumerator *enumerator = [optionUIs objectEnumerator]; 143 OptionUI *optionUI; 144 145 // First, walk through all settings and validate them. If any of the 146 // validations fails, stop and return false. That means that if there are 147 // any validation failures, the settings before the failure won't have their 148 // values updated. 149 while ((optionUI = [enumerator nextObject]) != nil) 150 { 151 if (![optionUI validate:error]) 152 { 153 if (error != nil) 154 { 155 NSRunAlertPanel([OCLocalStrings get:@"Error"], error, [OCLocalStrings get:@"OK"], nil, nil); 156 } 157 return; 158 } 159 } 160 enumerator = [optionUIs objectEnumerator]; 161 // If we get here, validation succeeded, so save all the option values. 162 while ((optionUI = [enumerator nextObject]) != nil) 163 { 164 [optionUI commit]; 165 } 166 [NSApp stopModalWithCode:NSModalResponseOK]; 167} 168 169- (IBAction)cancel:(id)sender 170{ 171 [NSApp stopModalWithCode:NSModalResponseCancel]; 172} 173 174- (void)addGroup:(LDExporterSetting &)setting 175{ 176 [optionUIs addObject:[[[GroupOptionUI alloc] initWithOptions:self setting:setting spacing:spacing] autorelease]]; 177} 178 179- (void)addBoolSetting:(LDExporterSetting &)setting 180{ 181 [optionUIs addObject:[[[BoolOptionUI alloc] initWithOptions:self setting:setting] autorelease]]; 182} 183 184- (void)addFloatSetting:(LDExporterSetting &)setting 185{ 186 [optionUIs addObject:[[[FloatOptionUI alloc] initWithOptions:self setting:setting] autorelease]]; 187} 188 189- (void)addLongSetting:(LDExporterSetting &)setting 190{ 191 [optionUIs addObject:[[[LongOptionUI alloc] initWithOptions:self setting:setting] autorelease]]; 192} 193 194- (void)addStringSetting:(LDExporterSetting &)setting 195{ 196 if (setting.isPath()) 197 { 198 // Paths go to TCUserDefault via different functions, and they also have 199 // a browse button, which strings lack. 200 [optionUIs addObject:[[[PathOptionUI alloc] initWithOptions:self setting:setting] autorelease]]; 201 } 202 else 203 { 204 [optionUIs addObject:[[[StringOptionUI alloc] initWithOptions:self setting:setting] autorelease]]; 205 } 206} 207 208- (void)addEnumSetting:(LDExporterSetting &)setting 209{ 210 [optionUIs addObject:[[[EnumOptionUI alloc] initWithOptions:self setting:setting] autorelease]]; 211} 212 213- (void)populate 214{ 215 LDExporterSettingList::iterator it; 216 std::stack<int> groupSizes; 217 int groupSize = 0; 218 NSEnumerator *enumerator; 219 OptionUI *optionUI; 220 NSView *lastKeyView = okButton; 221 222 optionUIs = [[NSMutableArray alloc] initWithCapacity:settings->size()]; 223 while ([[docView subviews] count] > 0) 224 { 225 [[[docView subviews] lastObject] removeFromSuperview]; 226 } 227 for (it = settings->begin(); it != settings->end(); ++it) 228 { 229 bool inGroup = groupSize > 0; 230 231 if (inGroup) 232 { 233 groupSize--; 234 if (groupSize == 0) 235 { 236 groupSize = groupSizes.top(); 237 groupSizes.pop(); 238 } 239 } 240 if (it->getGroupSize() > 0) 241 { 242 if (inGroup) 243 { 244 [self addBoolSetting:*it]; 245 } 246 else 247 { 248 [self addGroup:*it]; 249 } 250 groupSizes.push(groupSize); 251 groupSize = it->getGroupSize(); 252 } 253 else 254 { 255 switch (it->getType()) 256 { 257 case LDExporterSetting::TBool: 258 [self addBoolSetting:*it]; 259 break; 260 case LDExporterSetting::TFloat: 261 [self addFloatSetting:*it]; 262 break; 263 case LDExporterSetting::TLong: 264 [self addLongSetting:*it]; 265 break; 266 case LDExporterSetting::TString: 267 [self addStringSetting:*it]; 268 break; 269 case LDExporterSetting::TEnum: 270 [self addEnumSetting:*it]; 271 break; 272 default: 273 // Do nothing but get rid of warning. 274 break; 275 } 276 } 277 } 278 enumerator = [optionUIs objectEnumerator]; 279 while ((optionUI = [enumerator nextObject]) != nil) 280 { 281 [lastKeyView setNextKeyView:[optionUI firstKeyView]]; 282 lastKeyView = [optionUI lastKeyView]; 283 } 284 [lastKeyView setNextKeyView:cancelButton]; 285 [panel setInitialFirstResponder:[okButton nextKeyView]]; 286} 287 288- (void)makeOptionUIVisible:(OptionUI *)optionUI 289{ 290 NSRect optionRect = [optionUI frame]; 291 NSClipView *clipView = [scrollView contentView]; 292 NSRect docVisibleRect = [scrollView documentVisibleRect]; 293 CGFloat optionBottom = optionRect.origin.y + optionRect.size.height; 294 CGFloat docVisibleBottom = docVisibleRect.origin.y + docVisibleRect.size.height; 295 CGFloat delta = optionBottom - docVisibleBottom; 296 297 if (delta > 0.0f) 298 { 299 // Why does the clip view have a different coordinate system? 300 NSPoint scrollPoint = [docView convertPoint:NSMakePoint(0.0f, docVisibleRect.origin.y + delta) toView:clipView]; 301 302 [clipView scrollToPoint:scrollPoint]; 303 [scrollView reflectScrolledClipView:clipView]; 304 } 305 delta = optionRect.origin.y - docVisibleRect.origin.y; 306 if (delta < 0.0f) 307 { 308 // Why does the clip view have a different coordinate system? 309 NSPoint scrollPoint = [docView convertPoint:NSMakePoint(0.0f, docVisibleRect.origin.y + delta) toView:clipView]; 310 311 [clipView scrollToPoint:scrollPoint]; 312 [scrollView reflectScrolledClipView:clipView]; 313 } 314} 315 316- (void)newFirstResponder:(NSNotification *)notification 317{ 318 id responder = [[notification userInfo] objectForKey:@"Responder"]; 319 320 if ([responder respondsToSelector:@selector(cell)]) 321 { 322 OptionUI *optionUI = [[responder cell] representedObject]; 323 324 if (optionUI == nil && [responder isKindOfClass:[NSPopUpButton class]]) 325 { 326 // For some reason NSPopUpButtonCell always returns nil from 327 // -representedObject. 328 optionUI = [[responder itemAtIndex:0] representedObject]; 329 } 330 if (optionUI != nil) 331 { 332 [self makeOptionUIVisible:optionUI]; 333 } 334 } 335} 336 337- (NSInteger)runModalWithSettings:(LDExporterSettingList &)theSettings titlePrefix:(NSString *)titlePrefix 338{ 339 NSInteger retValue; 340 NSString *titleFormat = [panel title]; 341 342 [panel setTitle:[NSString stringWithFormat:titleFormat, titlePrefix]]; 343 settings = &theSettings; 344 [self populate]; 345 [self calcSize]; 346 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(newFirstResponder:) name:OPDidChangeFirstResponderNotification object:panel]; 347 [panel setFrameUsingName:titlePrefix]; 348 [panel setFrameAutosaveName:titlePrefix]; 349 retValue = [NSApp runModalForWindow:panel]; 350 [panel orderOut:self]; 351 [[NSNotificationCenter defaultCenter] removeObserver:self name:OPDidChangeFirstResponderNotification object:panel]; 352 settings = NULL; 353 return retValue; 354} 355 356- (void)windowWillClose:(NSNotification *)aNotification 357{ 358 if ([aNotification object] == panel) 359 { 360 [NSApp stopModalWithCode:NSModalResponseCancel]; 361 } 362} 363 364- (id)docView 365{ 366 return docView; 367} 368 369- (void)windowDidResize:(NSNotification *)aNotification 370{ 371 [self calcSize]; 372} 373 374- (void)updateEnabled 375{ 376 [self calcSize]; 377} 378 379- (IBAction)resetAll:(id)sender 380{ 381 for (int i = 0; i < [optionUIs count]; i++) 382 { 383 [[optionUIs objectAtIndex:i] reset]; 384 } 385 [self updateEnabled]; 386} 387 388@end 389