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