ionah

Q: Display an alert as sheet on other alert

I have a routine to show an alert when clicking on help button in a NSAlert ( see this post ).

Is it possible to display this help alert as sheet on the first alert?

 

I found the beginSheetModalForWindow method but can't figure out how it works in AppleScriptObjC…

Posted on Aug 16, 2016 6:38 AM

Close

Q: Display an alert as sheet on other alert

  • All replies
  • Helpful answers

  • by red_menace,

    red_menace red_menace Aug 17, 2016 7:07 PM in response to ionah
    Level 6 (15,526 points)
    Desktops
    Aug 17, 2016 7:07 PM in response to ionah
    
    

    Cocoa does not support cascading or nested sheets - per Apple's Human Interface Guidelines, you need to close the first sheet before opening the second.

     

    As for the beginSheetModalForWindow method, the current API uses a block (a closure/lambda/anonymous function used as a callback), but blocks are not supported in AppleScriptObjC.  All is not lost though, you can just use an Objective-C helper category, since Objective-C can use blocks.  Using a category is also handy in that it just extends an existing class, so you don't have to do anything but add it to your project.  A couple I've been using for a while are the NSWindow+MyriadHelpers and NSAlert+MyriadHelpers categories from Myriad Helpers.

     

    The NSWindow category uses the current beginSheetModalForWindow:completionHandler: method, while the NSAlert category uses the beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo: method which is deprecated as of 10.10 Yosemite.  For an example using the NSAlert+MyriadHelpers category, add the category .h and .m files to a new Cocoa-AppleScript project, add the following handlers, and call doSheet when the application finishes launching:

     

      on doSheetStuff()
          set theAlert to current application's NSAlert's makeAlert:"Sheet Test" buttons:{"OK", "Whatever"} |text|:"This is a test...  This is only a test..."
          theAlert's showOver:theWindow calling:"finishAlert:"
      end doSheetStuff
    
    
      on finishAlert:theButton
          log theButton -- the name of the button pressed
      end finishAlert:
    
  • by red_menace,

    red_menace red_menace Aug 18, 2016 1:08 PM in response to red_menace
    Level 6 (15,526 points)
    Desktops
    Aug 18, 2016 1:08 PM in response to red_menace

    Myriad Helpers has been updated and is now compatible with OS X 10.11 El Capitan.

  • by ionah,

    ionah ionah Aug 20, 2016 1:35 AM in response to red_menace
    Level 1 (4 points)
    Aug 20, 2016 1:35 AM in response to red_menace

    @red_menace

     

    [I could not find a minute to read your posts last 2 days]

    I know Shane's site, Myriad Helpers and other stuff I'm already using.

    But in the actual case, it's about a script in bundled format, not an XCcode project.

     

     

  • by red_menace,Helpful

    red_menace red_menace Aug 30, 2016 6:36 AM in response to ionah
    Level 6 (15,526 points)
    Desktops
    Aug 30, 2016 6:36 AM in response to ionah

    You aren't going to have much fun without some Objective-C helpers.  You can chain together alert sheets, but the beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo: method doesn't appear to return a button result code (so you are limited to the default "OK" button), and in my tests just adding buttons will crash the application (or the Script Editor).

     

    From your previous posts I am going to guess that this won't do what you are wanting, but the following is an example using simple alert sheets:

     

    use framework "Cocoa"
    
    property myWindow : missing value
    
    on run
      set myWindow to current application's NSWindow's alloc's initWithContentRect:{{200, 400}, {400, 200}} styleMask:7 backing:(current application's NSBackingStoreBuffered) defer:true
      tell myWindow to makeKeyAndOrderFront:me
      tell current application's NSAlert's alloc's init()
        its setMessageText:"Alert"
        its setInformativeText:"This is a test.  It is only a test."
        its beginSheetModalForWindow:myWindow modalDelegate:me didEndSelector:"alertDidEnd:returnCode:contextInfo:" contextInfo:(missing value)
      end tell
    end run
    
    on alertDidEnd:alert returnCode:returnCode contextInfo:contextInfo
      alert's |window|'s orderOut:me -- hide the sheet first for better animation
      myWindow's endSheet:alert
      showSecondSheet()
    end alertDidEnd:returnCode:contextInfo:
    
    on showSecondSheet()
      tell current application's NSAlert's alloc's init()
        its setMessageText:"Another Alert"
        its setInformativeText:"This is a another test.  It is still only a test."
        its beginSheetModalForWindow:myWindow modalDelegate:me didEndSelector:(missing value) contextInfo:(missing value)
      end tell
    end showSecondSheet
    
    
  • by ionah,

    ionah ionah Aug 22, 2016 3:52 AM in response to red_menace
    Level 1 (4 points)
    Aug 22, 2016 3:52 AM in response to red_menace

    I don't want to display 2 alerts on a main window.

    Just to display a help alert as a sheet on a first alert.

    Something like this:

     

    -- show the main alert
    set theAlert to current application's NSAlert's alloc's init()
    tell theAlert
      its setDelegate:me
      its setShowsHelp:true
      its setMessageText:"Main alert message."
      its setInformativeText:"Main alert informative text."
      its setAlertStyle()
      its addButtonWithTitle:"OK"
      its runModal()
    end tell
    
    
    -- show the help alert
    on alertShowHelp:theAlert
      tell current application's NSAlert's alloc's init()
      its setMessageText:"Help alert message."
      its setInformativeText:"Help alert informative text."
      its runModal()
      --its beginSheetModalForWindow:theAlert modalDelegate:me didEndSelector:(missing value) contextInfo:(missing value)
      end tell
      return true
    end alertShowHelp:
    

     

    In the alertShowHelp handler, the its runModal() command runs fine.

    If I replace it with the line below, the help alert will not show.

     

    …But I'm beginning to guess that the trick is not possible…

  • by Hiroto,

    Hiroto Hiroto Aug 22, 2016 7:30 AM in response to ionah
    Level 5 (7,281 points)
    Aug 22, 2016 7:30 AM in response to ionah

    Hello

     

    You'd need to specify alert's window obtained by NSAlert -window method as the beginSheetModalForWindow argument.

     

    It would be something like the following in AppleScriptObjC. Not tested because I don't use AppleScriptObjC.

     

     

    on alertShowHelp:theAlert  
        tell current application's NSAlert's alloc's init()  
            its setMessageText:"Help alert message."  
            its setInformativeText:"Help alert informative text."  
            its beginSheetModalForWindow:theAlert's window() modalDelegate:me didEndSelector:(missing value) contextInfo:(missing value)  
        end tell  
        return true  
    end alertShowHelp:  
    

     

     

     

    ---

    Here's PyObjC version tested with pyobjc 2.2b3 and python 2.6.1 under OS X 10.6.8.

     

     

    #!/usr/bin/python
    # coding: utf-8
    #
    #   NSAlert alert help shown as sheet on parent alert window
    #
    from AppKit import *
    
    class AppDelegate(NSObject):
        def init(self):
            self = super(AppDelegate, self).init()
            return self
        
        def applicationDidFinishLaunching_(self, notif):
            try:
                NSApp.activateIgnoringOtherApps_(True)
                alert = NSAlert.alloc().init()
                alert.setDelegate_(self)
                alert.setShowsHelp_(True)
                alert.setMessageText_('Main alert message')
                alert.setInformativeText_('Main alert informative text')
                alert.addButtonWithTitle_('OK')
                alert.runModal()
            except:
                NSApp.terminate_(self)
                raise
    
        def applicationShouldTerminateAfterLastWindowClosed_(self, app):
            return True
        
        def applicationShouldTerminate_(self, sender):
            return NSTerminateNow
    
        def alertShowHelp_(self, alert):
            help = NSAlert.alloc().init()
            help.setMessageText_('Help alert message')
            help.setInformativeText_('Help alert informative text')
            help.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
                alert.window(),
                self,
                None,
                None
            )
            return True
    
    def main():
        app = NSApplication.sharedApplication()
        delegate = AppDelegate.alloc().init()
        app.setDelegate_(delegate)
        app.run()
    
    main()
    

     

     

     

    Good luck,

    H

  • by red_menace,Helpful

    red_menace red_menace Aug 30, 2016 6:37 AM in response to ionah
    Level 6 (15,526 points)
    Desktops
    Aug 30, 2016 6:37 AM in response to ionah

    As Hiroto mentioned, you need to use the alert's window - don't forget to put pipes around the term 'window', since it is a reserved word.  For some reason my Script Editor crashes quite a bit when going back and forth from Xcode with this, but the following seems to be stable from the Script Editor when run in the foreground (it still goes against Apple's HIG):

     

    use framework "Cocoa"
    
    property theWindow : missing value
    property myAlert : missing value
    
    on run
      set theWindow to current application's NSWindow's alloc's initWithContentRect:{{200, 400}, {400, 200}} styleMask:7 backing:(current application's NSBackingStoreBuffered) defer:true
      tell theWindow to makeKeyAndOrderFront:me
      -- show the main alert
      set myAlert to current application's NSAlert's alloc's init()
      tell myAlert
        its setDelegate:me
        its setShowsHelp:true
        its setMessageText:"Main alert message."
        its setInformativeText:"Main alert informative text."
        its beginSheetModalForWindow:theWindow modalDelegate:me didEndSelector:(missing value) contextInfo:(missing value)
      end tell
    end run
    
    on alertShowHelp:theAlert
      -- show the help alert
      tell current application's NSAlert's alloc's init()
        its setMessageText:"Help alert message."
        its setInformativeText:"Help alert informative text."
        its beginSheetModalForWindow:(theAlert's |window|()) modalDelegate:me didEndSelector:(missing value) contextInfo:(missing value)
      end tell
      return true
    end alertShowHelp:
    
  • by ionah,

    ionah ionah Aug 29, 2016 4:57 PM in response to red_menace
    Level 1 (4 points)
    Aug 29, 2016 4:57 PM in response to red_menace

    Sorry, folks.

     

    I can't do nothing with your examples.

    I'm totally confused and not able to understand how all that is working.

    I'm giving up.

    After all, it's not so important.

     

    Thanks for your time!

     

  • by red_menace,

    red_menace red_menace Aug 29, 2016 5:07 PM in response to ionah
    Level 6 (15,526 points)
    Desktops
    Aug 29, 2016 5:07 PM in response to ionah

    You couldn't get the script to work?  It was just a slight variation on the one you posted - the first alert was changed to a sheet on the application window, and the help was changed to a sheet over the first alert's window.

     

    NSAlert's runModal will do just that (run a modal alert not attached to any window), while the beginSheetModalForWindow: method will run the alert as a modal sheet over the specified window, calling the specified didEndSelector: when a button is pressed, so you need to pick one or the other.

  • by ionah,

    ionah ionah Aug 30, 2016 2:58 AM in response to red_menace
    Level 1 (4 points)
    Aug 30, 2016 2:58 AM in response to red_menace

    @ red_menace

     

    It works!

    Thank you for your encouragements.

     

    As I said before, I don't want 3 windows.

    This is the definitive code:

     

    use framework "AppKit"
    
    on run
      -- show the main alert  
      set mainAlert to current application's NSAlert's alloc's init()
      tell mainAlert
      its setDelegate:me
      its setShowsHelp:true
      its setMessageText:"Main alert message."
      its setInformativeText:"Main alert informative text."
      its runModal()
      end tell
    end run
    
    on alertShowHelp:parentAlert
      -- show the help alert  
      tell current application's NSAlert's alloc's init()
      its setMessageText:"Help alert message."
      its setInformativeText:"Help alert informative text."
      its beginSheetModalForWindow:(parentAlert's |window|()) modalDelegate:me didEndSelector:(missing value) contextInfo:(missing value)
      end tell
      return true
    end alertShowHelp:
    

     

    I hope this will help some one else…

     

  • by Hiroto,

    Hiroto Hiroto Aug 30, 2016 3:02 AM in response to ionah
    Level 5 (7,281 points)
    Aug 30, 2016 3:02 AM in response to ionah

    Hello

     

    My AppleScriptObjC snippet should have been as follows as red_menace explained. Terminalogy conflict on term "window" must be evaded by using enclosing pipes around symbol; i.e., |window|() in lieu of window().

     

     

    on alertShowHelp:theAlert
        tell current application's NSAlert's alloc()'s init()
            its setMessageText:"Help alert message."
            its setInformativeText:"Help alert informative text."
            its beginSheetModalForWindow:theAlert's |window|() modalDelegate:me didEndSelector:(missing value) contextInfo:(missing value)
        end tell
        return true
    end alertShowHelp:
    

     

     

     

    By the way, as far as I have tested under OS X 10.6.8, passing (missing value) to didEndSelector reports error in console. The following version does not throw error.

     

    (I used positional parameters syntax here because there's no such thing as interleaved parameters syntax in AppleScript 2.1.2 under OS X 10.6.8)

     

     

    on alertShowHelp_(alert)
        set alert_help to current application's NSAlert's alloc()'s init()
        tell alert_help
            its setMessageText_("Help alert message.")
            its setInformativeText_("Help alert informative text.")
            its beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(¬
                alert's |window|(), ¬
                me, ¬
                "alertDidEnd:returnCode:contextInfo:", ¬
                missing value)
        end tell
        return true
    end alertShowHelp_
    
    on alertDidEnd_returnCode_contextInfo_(alert, ret, cinfo)
        return
    end alertDidEnd_returnCode_contextInfo_
    

     

     

     

    Good luck,

    H

     

    PS. As for pyobjc script, you may simply run it in terminal. E.g,, save the script as ~/Desktop/a.py and in Terminal:

     

    python ~/Desktop/a.py
    

     

    or

     

    ~/Desktop/a.py
    

     

    /H

  • by ionah,

    ionah ionah Aug 30, 2016 5:07 AM in response to Hiroto
    Level 1 (4 points)
    Aug 30, 2016 5:07 AM in response to Hiroto

    @Hiroto,

     

    There's no error in console under Yosemite.

    Only this warning: "30/08/2016 13:59:56,464 Script Debugger[28820]: CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces."

     

    Is it a problem?

     

    Thanks for the tip to run Python code.

     

  • by Hiroto,

    Hiroto Hiroto Aug 30, 2016 5:21 AM in response to ionah
    Level 5 (7,281 points)
    Aug 30, 2016 5:21 AM in response to ionah

    Hello

     

    Good to hear ASObjC bridge has been somehow refined under 10.10. For the record, the following is the error I get under OS X 10.6.8 when passing missing value to selector argument in ASObjC.

     

     

    [NSAlert beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:]: unable to set argument 4 because the AppleScript value <NSAppleEventDescriptor: 'msng'> could not be coerced to type :.

     

     

    As for the warning about CoreAnimation, I'm not sure but I'd disregard it as the irrelevant.

     

    All the best,

    Hiroto

  • by ionah,

    ionah ionah Aug 30, 2016 6:35 AM in response to Hiroto
    Level 1 (4 points)
    Aug 30, 2016 6:35 AM in response to Hiroto

    Hiroto wrote:

     

    As for the warning about CoreAnimation, I'm not sure but I'd disregard it as the irrelevant.

     

    That's what I thought, because every runModal() method triggers this warning.

     

    I really appreciate the interest you're showing for my query.