1/*
2Copyright (c) 2003-2017 Sony Pictures Imageworks Inc., et al.
3All Rights Reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are
7met:
8* Redistributions of source code must retain the above copyright
9  notice, this list of conditions and the following disclaimer.
10* Redistributions in binary form must reproduce the above copyright
11  notice, this list of conditions and the following disclaimer in the
12  documentation and/or other materials provided with the distribution.
13* Neither the name of Sony Pictures Imageworks nor the names of its
14  contributors may be used to endorse or promote products derived from
15  this software without specific prior written permission.
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27*/
28
29#import "OpenColorIO_PS_Dialog_Controller.h"
30
31#import "OpenColorIO_AE_MonitorProfileChooser_Controller.h"
32
33#include <fstream>
34#include <map>
35#include <cstdlib>
36
37#include "OpenColorIO_PS_Context.h"
38
39#include "OpenColorIO_AE_Dialogs.h"
40
41#include "ocioicc.h"
42
43
44
45static NSString *standardPath = @"/Library/Application Support/OpenColorIO";
46
47
48@implementation OpenColorIO_PS_Dialog_Controller
49
50- (NSString *)pathForStandardConfig:(NSString *)config
51{
52    return [[standardPath stringByAppendingPathComponent:config] stringByAppendingPathComponent:@"config.ocio"];
53}
54
55- (id)initWithSource:(ControllerSource)initSource
56        configuration:(NSString *)initConfiguration
57        action:(ControllerAction)initAction
58        invert:(BOOL)initInvert
59        interpolation:(ControllerInterp)initInterpolation
60        inputSpace:(NSString *)initInputSpace
61        outputSpace:(NSString *)initOutputSpace
62        device:(NSString *)initDevice
63        transform:(NSString *)initTransform
64{
65    self = [super init];
66
67    if(self)
68    {
69        if(!([NSBundle loadNibNamed:@"OpenColorIO_PS_Dialog" owner:self]))
70            return nil;
71
72        source = initSource;
73
74        if(source == CSOURCE_CUSTOM)
75        {
76            customPath = [initConfiguration retain];
77        }
78        else if(source == CSOURCE_STANDARD)
79        {
80            configuration = [initConfiguration retain];
81        }
82
83        action = initAction;
84        inputSpace = [initInputSpace retain];
85        outputSpace = [initOutputSpace retain];
86        device = [initDevice retain];
87        transform = [initTransform retain];
88        interpolation = initInterpolation;
89        invert = initInvert;
90
91
92        // configuration menu
93        [configurationMenu removeAllItems];
94
95        [configurationMenu setAutoenablesItems:NO];
96
97        [configurationMenu addItemWithTitle:@"$OCIO"];
98
99        [[configurationMenu lastItem] setTag:CSOURCE_ENVIRONMENT];
100
101        char *envFile = std::getenv("OCIO");
102
103        if(envFile == NULL || strlen(envFile) == 0)
104            [[configurationMenu lastItem] setEnabled:FALSE];
105
106
107        [[configurationMenu menu] addItem:[NSMenuItem separatorItem]];
108
109
110        NSArray *standardConfigs = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:standardPath error:NULL];
111
112        if([standardConfigs count] > 0)
113        {
114            for(NSString *configName in standardConfigs)
115            {
116                NSAssert([configName isKindOfClass:[NSString class]], @"expected NSString");
117
118                NSString *configFilePath = [self pathForStandardConfig:configName];
119
120                if([[NSFileManager defaultManager] isReadableFileAtPath:configFilePath])
121                {
122                    [configurationMenu addItemWithTitle:configName];
123
124                    [[configurationMenu lastItem] setTag:CSOURCE_STANDARD];
125                }
126            }
127        }
128        else
129        {
130            NSString *noConfigsMessage = [NSString stringWithFormat:@"No configs in %@", standardPath];
131
132            [configurationMenu addItemWithTitle:noConfigsMessage];
133
134            [[configurationMenu lastItem] setTag:CSOURCE_STANDARD];
135            [[configurationMenu lastItem] setEnabled:FALSE];
136        }
137
138
139        [[configurationMenu menu] addItem:[NSMenuItem separatorItem]];
140
141
142        [configurationMenu addItemWithTitle:@"Custom…"];
143
144        [[configurationMenu lastItem] setTag:CSOURCE_CUSTOM];
145
146
147        [invertCheck setHidden:YES];
148
149        [menu1 removeAllItems];
150        [menu2 removeAllItems];
151        [menu3 removeAllItems];
152
153        [label3 setHidden:YES];
154        [menu3 setHidden:YES];
155
156
157        [self trackConfigMenu:nil];
158    }
159
160    return self;
161}
162
163- (void)dealloc
164{
165    OpenColorIO_PS_Context *context = (OpenColorIO_PS_Context *)contextPtr;
166
167    delete context;
168
169    [configuration release];
170    [customPath release];
171    [inputSpace release];
172    [outputSpace release];
173    [device release];
174    [transform release];
175
176    [super dealloc];
177}
178
179- (IBAction)clickedOK:(id)sender
180{
181    [NSApp stopModal];
182}
183
184- (IBAction)clickedCancel:(id)sender
185{
186    [NSApp abortModal];
187}
188
189/*
190- (void)profileChooserDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
191{
192    NSAssert(profileController != nil, @"profileController is nil");
193
194
195    [sheet orderOut:self];
196
197
198    if(returnCode == NSRunStoppedResponse)
199    {
200        NSString *path = (NSString *)contextInfo;
201
202        NSAssert([path isKindOfClass:[NSString class]], @"expected NSString");
203
204        try
205        {
206            OpenColorIO_PS_Context *context = (OpenColorIO_PS_Context *)contextPtr;
207
208            NSAssert(context != NULL, @"context was NULL");
209
210
211            char display_icc_path[256];
212
213            const BOOL gotICC = [profileController getMonitorProfile:display_icc_path bufferSize:255];
214
215            if(!gotICC)
216                throw OCIO::Exception("Failed to get ICC profile");
217
218
219            OCIO::ConstProcessorRcPtr processor;
220
221            if(action == CACTION_CONVERT)
222            {
223                processor = context->getConvertProcessor([inputSpace UTF8String], [outputSpace UTF8String]);
224            }
225            else if(action == CACTION_DISPLAY)
226            {
227                processor = context->getDisplayProcessor([inputSpace UTF8String], [device UTF8String], [transform UTF8String]);
228            }
229            else
230            {
231                NSAssert(action == CACTION_LUT, @"expected CACTION_LUT");
232
233                const OCIO::Interpolation interp = (interpolation == CINTERP_NEAREST ? OCIO::INTERP_NEAREST :
234                                                    interpolation == CINTERP_LINEAR ? OCIO::INTERP_LINEAR :
235                                                    interpolation == CINTERP_TETRAHEDRAL ? OCIO::INTERP_TETRAHEDRAL :
236                                                    OCIO::INTERP_BEST);
237
238                const OCIO::TransformDirection direction = (invert ? OCIO::TRANSFORM_DIR_INVERSE : OCIO::TRANSFORM_DIR_FORWARD);
239
240                processor = context->getLUTProcessor(interp, direction);
241            }
242
243
244            int cubesize = 32;
245            int whitepointtemp = 6505;
246            std::string copyright = "OpenColorIO, Sony Imageworks";
247
248            // create a description tag from the filename
249            std::string description = [[[path lastPathComponent] stringByDeletingPathExtension] UTF8String];
250
251            SaveICCProfileToFile([path UTF8String], processor, cubesize, whitepointtemp,
252                                    display_icc_path, description, copyright, false);
253        }
254        catch(const std::exception &e)
255        {
256            NSBeep();
257
258            NSString *ocioString = [NSString stringWithUTF8String:e.what()];
259
260            NSAlert *alert = [NSAlert alertWithMessageText:@"OpenColorIO error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", ocioString];
261
262            [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
263        }
264        catch(...)
265        {
266            NSBeep();
267
268            NSString *ocioString = @"Some unknown error";
269
270            NSAlert *alert = [NSAlert alertWithMessageText:@"OpenColorIO error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", ocioString];
271
272            [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
273        }
274
275        [path release];
276    }
277
278
279    [profileController release];
280
281    profileController = nil;
282}
283*/
284- (void)exportPanelDidEnd:(NSSavePanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo
285{
286    if(returnCode == NSOKButton)
287    {
288        OpenColorIO_PS_Context *context = (OpenColorIO_PS_Context *)contextPtr;
289
290        NSAssert(context != NULL, @"context was NULL");
291
292
293        NSString *path = [[panel URL] path];
294
295        NSString *extension = [path pathExtension];
296
297        if([extension isEqualToString:@"icc"])
298        {
299            OpenColorIO_AE_MonitorProfileChooser_Controller *profileController = [[OpenColorIO_AE_MonitorProfileChooser_Controller alloc] init];
300
301            // the sheet is still active, so can't run another one...will settle for modal
302            const NSInteger modal_result = [NSApp runModalForWindow:[profileController getWindow]];
303
304
305            if(modal_result == NSRunStoppedResponse)
306            {
307                try
308                {
309                    char display_icc_path[256];
310
311                    const BOOL gotICC = [profileController getMonitorProfile:display_icc_path bufferSize:255];
312
313                    if(!gotICC)
314                        throw OCIO::Exception("Failed to get ICC profile");
315
316
317                    OCIO::ConstProcessorRcPtr processor;
318
319                    if(action == CACTION_CONVERT)
320                    {
321                        processor = context->getConvertProcessor([inputSpace UTF8String], [outputSpace UTF8String]);
322                    }
323                    else if(action == CACTION_DISPLAY)
324                    {
325                        processor = context->getDisplayProcessor([inputSpace UTF8String], [device UTF8String], [transform UTF8String]);
326                    }
327                    else
328                    {
329                        NSAssert(action == CACTION_LUT, @"expected CACTION_LUT");
330
331                        const OCIO::Interpolation interp = (interpolation == CINTERP_NEAREST ? OCIO::INTERP_NEAREST :
332                                                            interpolation == CINTERP_LINEAR ? OCIO::INTERP_LINEAR :
333                                                            interpolation == CINTERP_TETRAHEDRAL ? OCIO::INTERP_TETRAHEDRAL :
334                                                            OCIO::INTERP_BEST);
335
336                        const OCIO::TransformDirection direction = (invert ? OCIO::TRANSFORM_DIR_INVERSE : OCIO::TRANSFORM_DIR_FORWARD);
337
338                        processor = context->getLUTProcessor(interp, direction);
339                    }
340
341
342                    int cubesize = 32;
343                    int whitepointtemp = 6505;
344                    std::string copyright = "OpenColorIO, Sony Imageworks";
345
346                    // create a description tag from the filename
347                    std::string description = [[[path lastPathComponent] stringByDeletingPathExtension] UTF8String];
348
349                    SaveICCProfileToFile([path UTF8String], processor, cubesize, whitepointtemp,
350                                            display_icc_path, description, copyright, false);
351                }
352                catch(const std::exception &e)
353                {
354                    NSBeep();
355
356                    NSString *ocioString = [NSString stringWithUTF8String:e.what()];
357
358                    NSAlert *alert = [NSAlert alertWithMessageText:@"OpenColorIO error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", ocioString];
359
360                    [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
361                }
362                catch(...)
363                {
364                    NSBeep();
365
366                    NSString *ocioString = @"Some unknown error";
367
368                    NSAlert *alert = [NSAlert alertWithMessageText:@"OpenColorIO error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", ocioString];
369
370                    [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
371                }
372            }
373
374            [[profileController getWindow] close];
375
376            [profileController release];
377        }
378        else
379        {
380            try
381            {
382                // need an extension->format map
383                std::map<std::string, std::string> extensions;
384
385                for(int i=0; i < OCIO::Baker::getNumFormats(); ++i)
386                {
387                    const char *extension = OCIO::Baker::getFormatExtensionByIndex(i);
388                    const char *format = OCIO::Baker::getFormatNameByIndex(i);
389
390                    extensions[ extension ] = format;
391                }
392
393                const std::string the_extension = [extension UTF8String];
394
395                std::string format = extensions[ the_extension ];
396
397
398                OCIO::BakerRcPtr baker;
399
400                if(action == CACTION_CONVERT)
401                {
402                    baker = context->getConvertBaker([inputSpace UTF8String], [outputSpace UTF8String]);
403                }
404                else if(action == CACTION_DISPLAY)
405                {
406                    baker = context->getDisplayBaker([inputSpace UTF8String], [device UTF8String], [transform UTF8String]);
407                }
408                else
409                {
410                    NSAssert(action == CACTION_LUT, @"expected CACTION_LUT");
411
412                    const OCIO::Interpolation interp = (interpolation == CINTERP_NEAREST ? OCIO::INTERP_NEAREST :
413                                                        interpolation == CINTERP_LINEAR ? OCIO::INTERP_LINEAR :
414                                                        interpolation == CINTERP_TETRAHEDRAL ? OCIO::INTERP_TETRAHEDRAL :
415                                                        OCIO::INTERP_BEST);
416
417                    const OCIO::TransformDirection direction = (invert ? OCIO::TRANSFORM_DIR_INVERSE : OCIO::TRANSFORM_DIR_FORWARD);
418
419                    baker = context->getLUTBaker(interp, direction);
420                }
421
422                baker->setFormat( format.c_str() );
423
424                std::ofstream f([path UTF8String]);
425                baker->bake(f);
426            }
427            catch(const std::exception &e)
428            {
429                NSBeep();
430
431                NSString *ocioString = [NSString stringWithUTF8String:e.what()];
432
433                NSAlert *alert = [NSAlert alertWithMessageText:@"OpenColorIO error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", ocioString];
434
435                [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
436            }
437            catch(...)
438            {
439                NSBeep();
440
441                NSString *ocioString = @"Some unknown error";
442
443                NSAlert *alert = [NSAlert alertWithMessageText:@"OpenColorIO error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", ocioString];
444
445                [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
446            }
447        }
448    }
449}
450
451- (IBAction)clickedExport:(id)sender
452{
453    NSSavePanel *panel = [NSSavePanel savePanel];
454
455    NSMutableString *message = [NSMutableString stringWithString:@"Formats: ICC Profile (.icc)"];
456
457    NSMutableArray *extensionArray = [NSMutableArray arrayWithObject:@"icc"];
458
459    for(int i=0; i < OCIO::Baker::getNumFormats(); ++i)
460    {
461        const char *extension = OCIO::Baker::getFormatExtensionByIndex(i);
462        const char *format = OCIO::Baker::getFormatNameByIndex(i);
463
464        [extensionArray addObject:[NSString stringWithUTF8String:extension]];
465
466        [message appendFormat:@", %s (.%s)", format, extension];
467    }
468
469    [panel setAllowedFileTypes:extensionArray];
470    [panel setMessage:message];
471
472    [panel beginSheetForDirectory:nil
473        file:nil
474        modalForWindow:window
475        modalDelegate:self
476        didEndSelector:@selector(exportPanelDidEnd:returnCode:contextInfo:)
477        contextInfo:NULL];
478}
479
480- (void)openPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo
481{
482    if(returnCode == NSOKButton)
483    {
484        source = CSOURCE_CUSTOM;
485
486        NSURL *url = [panel URL];
487
488        [customPath release];
489
490        customPath = [[url path] retain];
491    }
492
493    [self trackConfigMenu:nil];
494}
495
496- (IBAction)trackConfigMenu:(id)sender
497{
498    const ControllerSource previousSource = source;
499
500    if(sender == nil)
501    {
502        // set menu from values
503        if(source == CSOURCE_STANDARD)
504        {
505            [configurationMenu selectItemWithTitle:configuration];
506        }
507        else
508        {
509            [configurationMenu selectItemWithTag:source];
510        }
511    }
512    else
513    {
514        // set value from menu
515        source = (ControllerSource)[[configurationMenu selectedItem] tag];
516    }
517
518
519    NSString *configPath = nil;
520
521    if(source == CSOURCE_ENVIRONMENT)
522    {
523        char *envFile = std::getenv("OCIO");
524
525        if(envFile != NULL && strlen(envFile) > 0)
526        {
527            configPath = [NSString stringWithUTF8String:envFile];
528        }
529    }
530    else if(source == CSOURCE_CUSTOM)
531    {
532        if(sender != nil)
533        {
534            source = previousSource; // we'll re-set to the previous source in case dialog is canceled
535
536            // do file open dialog
537            NSOpenPanel *panel = [NSOpenPanel openPanel];
538
539            [panel setCanChooseDirectories:NO];
540            [panel setAllowsMultipleSelection:NO];
541
542            NSMutableArray *extensions = [NSMutableArray arrayWithObject:@"ocio"];
543
544            for(int i=0; i < OCIO::FileTransform::getNumFormats(); ++i)
545            {
546                const char *extension = OCIO::FileTransform::getFormatExtensionByIndex(i);
547                //const char *format = OCIO::FileTransform::getFormatNameByIndex(i);
548
549                NSString *extensionString = [NSString stringWithUTF8String:extension];
550
551                if(![extensionString isEqualToString:@"ccc"]) // .ccc files require an ID parameter
552                    [extensions addObject:[NSString stringWithUTF8String:extension]];
553            }
554
555
556            NSMutableString *message = [NSMutableString stringWithString:@"Formats: "];
557
558            for(int i=0; i < [extensions count]; i++)
559            {
560                NSString *extension = [extensions objectAtIndex:i];
561
562                if(i != 0)
563                    [message appendString:@", "];
564
565                [message appendString:@"."];
566                [message appendString:extension];
567            }
568
569            [panel setMessage:message];
570            [panel setAllowedFileTypes:extensions];
571
572            [panel beginSheetForDirectory:nil
573                    file:nil
574                    modalForWindow:window
575                    modalDelegate:self
576                    didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
577                    contextInfo:NULL];
578
579            return;
580        }
581        else
582        {
583            configPath = customPath;
584        }
585    }
586    else
587    {
588        NSAssert(source == CSOURCE_STANDARD, @"expected TAG_STANDARD");
589
590        [configuration release];
591
592        configuration = [[configurationMenu titleOfSelectedItem] retain];
593
594        configPath = [self pathForStandardConfig:configuration];
595    }
596
597
598    if(configPath != nil && [[NSFileManager defaultManager] isReadableFileAtPath:configPath])
599    {
600        try
601        {
602            OpenColorIO_PS_Context *oldContext = (OpenColorIO_PS_Context *)contextPtr;
603
604            delete oldContext;
605
606
607            OpenColorIO_PS_Context *context = new OpenColorIO_PS_Context([configPath UTF8String]);
608
609            contextPtr = context;
610
611
612            if( context->isLUT() )
613            {
614                action = CACTION_LUT;
615
616                [invertCheck setHidden:NO];
617
618                if( context->canInvertLUT() )
619                {
620                    [invertCheck setEnabled:YES];
621                }
622                else
623                {
624                    [invertCheck setEnabled:NO];
625
626                    invert = NO;
627                }
628
629                [invertCheck setState:(invert ? NSOnState : NSOffState)];
630
631                [actionRadios setHidden:YES];
632
633
634                NSTextField *interpolationLabel = label1;
635                NSPopUpButton *interpolationMenu = menu1;
636
637                [interpolationLabel setStringValue:@"Interpolation"];
638
639                // interpolation menu
640                [interpolationMenu removeAllItems];
641
642                [interpolationMenu addItemWithTitle:@"Nearest Neighbor"];
643                [[interpolationMenu lastItem] setTag:CINTERP_NEAREST];
644
645                [interpolationMenu addItemWithTitle:@"Linear"];
646                [[interpolationMenu lastItem] setTag:CINTERP_LINEAR];
647
648                [interpolationMenu addItemWithTitle:@"Tetrahedral"];
649                [[interpolationMenu lastItem] setTag:CINTERP_TETRAHEDRAL];
650
651                const bool canTetrahedral = !context->canInvertLUT();
652
653                if(!canTetrahedral)
654                {
655                    [interpolationMenu setAutoenablesItems:NO];
656
657                    [[interpolationMenu lastItem] setEnabled:NO];
658
659                    if(interpolation == CINTERP_TETRAHEDRAL)
660                        interpolation = CINTERP_LINEAR;
661                }
662
663                [[interpolationMenu menu] addItem:[NSMenuItem separatorItem]];
664
665                [interpolationMenu addItemWithTitle:@"Best"];
666                [[interpolationMenu lastItem] setTag:CINTERP_BEST];
667
668                [interpolationMenu selectItemWithTag:interpolation];
669
670                [inputSpaceButton setHidden:YES];
671
672                [label2 setHidden:YES];
673                [menu2 setHidden:YES];
674                [outputSpaceButton setHidden:YES];
675
676                [label3 setHidden:YES];
677                [menu3 setHidden:YES];
678            }
679            else
680            {
681                [invertCheck setHidden:YES];
682
683                if(action == CACTION_LUT)
684                {
685                    action = CACTION_CONVERT;
686                }
687
688                [actionRadios setHidden:NO];
689
690
691                const SpaceVec &colorSpaces = context->getColorSpaces();
692
693                if(inputSpace == nil || -1 == FindSpace(colorSpaces, [inputSpace UTF8String]))
694                {
695                    [inputSpace release];
696
697                    inputSpace = [[NSString alloc] initWithUTF8String:context->getDefaultColorSpace().c_str()];
698                }
699
700                if(outputSpace == nil || -1 == FindSpace(colorSpaces, [outputSpace UTF8String]))
701                {
702                    [outputSpace release];
703
704                    outputSpace = [[NSString alloc] initWithUTF8String:context->getDefaultColorSpace().c_str()];
705                }
706
707
708                const SpaceVec &devices = context->getDevices();
709
710                if(device == nil || -1 == FindSpace(devices, [device UTF8String]))
711                {
712                    [device release];
713
714                    device = [[NSString alloc] initWithUTF8String:context->getDefaultDevice().c_str()];
715                }
716
717
718                const SpaceVec transforms = context->getTransforms([device UTF8String]);
719
720                if(transform == nil || -1 == FindSpace(transforms, [transform UTF8String]))
721                {
722                    [transform release];
723
724                    transform = [[NSString alloc] initWithUTF8String:context->getDefaultTransform([device UTF8String]).c_str()];
725                }
726
727
728                [self trackActionRadios:nil];
729            }
730        }
731        catch(const std::exception &e)
732        {
733            NSBeep();
734
735            NSString *ocioString = [NSString stringWithUTF8String:e.what()];
736
737            NSAlert *alert = [NSAlert alertWithMessageText:@"OpenColorIO error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", ocioString];
738
739            [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
740
741            if(source != CSOURCE_ENVIRONMENT)
742            {
743                source = CSOURCE_ENVIRONMENT;
744
745                [self trackConfigMenu:nil];
746            }
747        }
748        catch(...)
749        {
750            NSBeep();
751
752            NSString *ocioString = @"Some unknown error";
753
754            NSAlert *alert = [NSAlert alertWithMessageText:@"OpenColorIO error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", ocioString];
755
756            [alert beginSheetModalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
757
758            if(source != CSOURCE_ENVIRONMENT)
759            {
760                source = CSOURCE_ENVIRONMENT;
761
762                [self trackConfigMenu:nil];
763            }
764        }
765    }
766
767    [configurationMenu setToolTip:configPath];
768}
769
770- (IBAction)trackActionRadios:(id)sender;
771{
772    if(sender == nil)
773    {
774        // set radios from values
775        NSAssert(action != CACTION_LUT, @"should not be a LUT");
776
777        const NSUInteger selectedColumn = (action == CACTION_DISPLAY ? 1 : 0);
778
779        [actionRadios selectCellAtRow:0 column:selectedColumn];
780    }
781    else
782    {
783        // set values from radios
784        action = ([actionRadios selectedColumn] == 1 ? CACTION_DISPLAY : CACTION_CONVERT);
785    }
786
787
788    OpenColorIO_PS_Context *context = (OpenColorIO_PS_Context *)contextPtr;
789
790    NSAssert(context != NULL, @"context was NULL");
791
792
793    NSTextField *inputLabel = label1;
794    NSPopUpButton *inputMenu = menu1;
795
796    [inputLabel setStringValue:@"Input Space:"];
797
798    [inputMenu removeAllItems];
799
800    const SpaceVec &colorSpaces = context->getColorSpaces();
801
802    for(SpaceVec::const_iterator i = colorSpaces.begin(); i != colorSpaces.end(); ++i)
803    {
804        NSString *colorSpace = [NSString stringWithUTF8String:i->c_str()];
805
806        [inputMenu addItemWithTitle:colorSpace];
807    }
808
809    NSAssert([inputMenu itemWithTitle:inputSpace] != nil, @"don't have the input space");
810
811    [inputMenu selectItemWithTitle:inputSpace];
812
813    [inputSpaceButton setHidden:NO];
814
815
816    if(action == CACTION_DISPLAY)
817    {
818        NSTextField *deviceLabel = label2;
819        NSPopUpButton *deviceMenu = menu2;
820
821        NSTextField *transformLabel = label3;
822        NSPopUpButton *transformMenu = menu3;
823
824
825        [deviceLabel setStringValue:@"Device:"];
826        [deviceLabel setHidden:NO];
827
828        [deviceMenu setHidden:NO];
829
830        [deviceMenu removeAllItems];
831
832        const SpaceVec &devices = context->getDevices();
833
834        for(SpaceVec::const_iterator i = devices.begin(); i != devices.end(); ++i)
835        {
836            NSString *deviceName = [NSString stringWithUTF8String:i->c_str()];
837
838            [deviceMenu addItemWithTitle:deviceName];
839        }
840
841        [outputSpaceButton setHidden:YES];
842
843
844        [transformLabel setStringValue:@"Transform:"];
845        [transformLabel setHidden:NO];
846
847        [transformMenu setHidden:NO];
848
849
850        [self trackMenu2:nil];
851    }
852    else
853    {
854        NSAssert(action == CACTION_CONVERT, @"expected Convert");
855
856        NSTextField *outputLabel = label2;
857        NSPopUpButton *outputMenu = menu2;
858
859        [outputLabel setHidden:NO];
860        [outputLabel setStringValue:@"Output Space:"];
861
862        [outputMenu setHidden:NO];
863
864        [outputMenu removeAllItems];
865
866        for(SpaceVec::const_iterator i = colorSpaces.begin(); i != colorSpaces.end(); ++i)
867        {
868            NSString *colorSpace = [NSString stringWithUTF8String:i->c_str()];
869
870            [outputMenu addItemWithTitle:colorSpace];
871        }
872
873        NSAssert([outputMenu itemWithTitle:outputSpace] != nil, @"don't have the input space");
874
875        [outputMenu selectItemWithTitle:outputSpace];
876
877        [outputSpaceButton setHidden:NO];
878
879
880        NSTextField *transformLabel = label3;
881        NSPopUpButton *transformMenu = menu3;
882
883        [transformLabel setHidden:YES];
884        [transformMenu setHidden:YES];
885
886        NSAssert([[transformLabel stringValue] isEqualToString:@"Transform:"], @"expected Transform:");
887    }
888}
889
890- (IBAction)trackMenu1:(id)sender
891{
892    NSAssert(sender == menu1, @"always from the UI");
893
894    if(action == CACTION_LUT)
895    {
896        interpolation = (ControllerInterp)[[menu1 selectedItem] tag];
897    }
898    else
899    {
900        [inputSpace release];
901
902        inputSpace = [[menu1 titleOfSelectedItem] retain];
903    }
904}
905
906- (IBAction)trackMenu2:(id)sender
907{
908    if(action == CACTION_DISPLAY)
909    {
910        NSPopUpButton *deviceMenu = menu2;
911
912        if(sender == nil)
913        {
914            // set menu from values
915            NSAssert([deviceMenu itemWithTitle:device] != nil, @"don't have the device");
916
917            [deviceMenu selectItemWithTitle:device];
918        }
919        else
920        {
921            // set values from menu
922            [device release];
923
924            device = [[deviceMenu titleOfSelectedItem] retain];
925        }
926    }
927    else
928    {
929        NSAssert(action == CACTION_CONVERT, @"expected Convert");
930
931        NSPopUpButton *outputMenu = menu2;
932
933        if(sender == nil)
934        {
935            // set menu from values
936            NSAssert([outputMenu itemWithTitle:outputSpace] != nil, @"don't have the device");
937
938            [outputMenu selectItemWithTitle:outputSpace];
939        }
940        else
941        {
942            // set values from menu
943            [outputSpace release];
944
945            outputSpace = [[outputMenu titleOfSelectedItem] retain];
946        }
947    }
948
949
950    if(action == CACTION_DISPLAY)
951    {
952        OpenColorIO_PS_Context *context = (OpenColorIO_PS_Context *)contextPtr;
953
954        NSAssert(context != NULL, @"context was NULL");
955
956        const SpaceVec transforms = context->getTransforms([device UTF8String]);
957
958        NSPopUpButton *transformMenu = menu3;
959
960        [transformMenu removeAllItems];
961
962        for(SpaceVec::const_iterator i = transforms.begin(); i != transforms.end(); ++i)
963        {
964            NSString *transformName = [NSString stringWithUTF8String:i->c_str()];
965
966            [transformMenu addItemWithTitle:transformName];
967        }
968
969        [self trackMenu3:nil];
970    }
971}
972
973- (IBAction)trackMenu3:(id)sender
974{
975    NSAssert(action == CACTION_DISPLAY, @"expected Display");
976    NSAssert([[label3 stringValue] isEqualToString:@"Transform:"], @"expected Transform:");
977
978    NSPopUpButton *transformMenu = menu3;
979
980    if(sender == nil)
981    {
982        // set menu from value
983        NSMenuItem *valueItem = [transformMenu itemWithTitle:transform];
984
985        if(valueItem != nil)
986        {
987            [transformMenu selectItem:valueItem];
988        }
989        else
990        {
991            OpenColorIO_PS_Context *context = (OpenColorIO_PS_Context *)contextPtr;
992
993            NSAssert(context != NULL, @"context was NULL");
994
995            const std::string defaultTransform = context->getDefaultTransform([device UTF8String]);
996
997            NSMenuItem *defaultItem = [transformMenu itemWithTitle:[NSString stringWithUTF8String:defaultTransform.c_str()]];
998
999            NSAssert(defaultItem != nil, @"where's that default item?");
1000
1001            [transformMenu selectItem:defaultItem];
1002
1003
1004            [transform release];
1005
1006            transform = [[transformMenu titleOfSelectedItem] retain];
1007        }
1008    }
1009    else
1010    {
1011        // set value from menu
1012        [transform release];
1013
1014        transform = [[transformMenu titleOfSelectedItem] retain];
1015    }
1016}
1017
1018- (IBAction)trackInvert:(id)sender
1019{
1020    NSAssert(sender == invertCheck, @"expected invertCheck");
1021
1022    invert = ([invertCheck state] == NSOnState);
1023}
1024
1025- (IBAction)popInputSpaceMenu:(id)sender
1026{
1027    OpenColorIO_PS_Context *context = (OpenColorIO_PS_Context *)contextPtr;
1028
1029    if(context != NULL)
1030    {
1031        std::string colorSpace = [inputSpace UTF8String];
1032
1033        const bool chosen = ColorSpacePopUpMenu(context->getConfig(), colorSpace, false, NULL);
1034
1035        if(chosen)
1036        {
1037            [inputSpace release];
1038
1039            inputSpace = [[NSString alloc] initWithUTF8String:colorSpace.c_str()];
1040
1041            [menu1 selectItemWithTitle:inputSpace];
1042        }
1043    }
1044}
1045
1046- (IBAction)popOutputSpaceMenu:(id)sender
1047{
1048    OpenColorIO_PS_Context *context = (OpenColorIO_PS_Context *)contextPtr;
1049
1050    if(context != NULL)
1051    {
1052        std::string colorSpace = [outputSpace UTF8String];
1053
1054        const bool chosen = ColorSpacePopUpMenu(context->getConfig(), colorSpace, false, NULL);
1055
1056        if(chosen)
1057        {
1058            [outputSpace release];
1059
1060            outputSpace = [[NSString alloc] initWithUTF8String:colorSpace.c_str()];
1061
1062            [menu2 selectItemWithTitle:outputSpace];
1063        }
1064    }
1065}
1066
1067- (NSWindow *)window
1068{
1069    return window;
1070}
1071
1072- (ControllerSource)source
1073{
1074    return source;
1075}
1076
1077- (NSString *)configuration
1078{
1079    if(source == CSOURCE_CUSTOM)
1080    {
1081        return customPath;
1082    }
1083    else
1084    {
1085        return configuration;
1086    }
1087}
1088
1089- (ControllerAction)action
1090{
1091    return action;
1092}
1093
1094- (BOOL)invert
1095{
1096    return invert;
1097}
1098
1099- (ControllerInterp)interpolation
1100{
1101    return interpolation;
1102}
1103
1104- (NSString *)inputSpace
1105{
1106    return inputSpace;
1107}
1108
1109- (NSString *)outputSpace
1110{
1111    return outputSpace;
1112}
1113
1114- (NSString *)device
1115{
1116    return device;
1117}
1118
1119- (NSString *)transform
1120{
1121    return transform;
1122}
1123
1124@end
1125