How to create a blocking dialog with more than two buttons in dm-script

102 Views Asked by At

How do I create a dialog that has more than two custom buttons instead of the "Ok" and "Cancel" buttons that blocks the script?


One can create a dialog with two custom buttons in by using TwoButtonDialog(). But consider the following example dialog asking whether to overwrite files. This dialog needs more than just the "Ok" and "Cancel" buttons.

Overwrite dialog as an example for a FiveButtonDialog()

This dialog can simply be created by using the UIFrame and dialog functions. But using UIFrame::display() does not wait for the user to interact. The dialog will be shown and the script continues. When using UIFrame::pose(), this blocks the current script but also always adds the "Ok" and "Cancel" buttons. So using those functions don't seem to fit in this case here.

How do I create a dialog that prevents the script to go on without the additional "Ok" and "Cancel" buttons so I can add my own buttons? And optionally, how do I make this dialog modal?


What I have tried

Version 1

I tried to block the current working thread by waiting on a Signal as shown in the example code below. I am creating the dialog and using UIFrame::display() which does not add any buttons. Then I wait for the signal that is set in the button click callback.

number index = -1;
object wait_signal = NewSignal(0);

class ButtonDialog : UIFrame{
    void button_pressed(object self, number i){
        index = i;
        wait_signal.SetSignal();
        self.close();
    }

    object init(object self){
        // creating dialog contents, for full example look at the bottom
        return self;
    }
}    

// Version 1
alloc(ButtonDialog).init().display(title);
wait_signal.waitOnSignal(10, NULL);

result("The index is " + index + "\n");

Executable version at the bottom, uncomment Version 1 block, comment all other versions

But this doesn't work. The appearing dialog is not finished and looks like the following:

Empty dm-script dialog

As soon as I change the window, the dialog shows the text and the buttons but still is not clickable. Plus the layout is not perfect.

Version 2

So I thought to move the dialog in a separate thread completely. This way it is able to take as long as needed for initializiation and is for sure usable since the new separate thread is never blocked.

number index = -1;
object wait_signal = NewSignal(0);

class ButtonDialog : UIFrame{
    void button_pressed(object self, number i){
        index = i;
        wait_signal.SetSignal();
        self.close();
    }

    object init(object self){
        // creating dialog contents, for full example look at the bottom
        return self;
    }
}

class ButtonDialogThread : Thread{
    object init(object self){
        return self;
    }
    
    void RunThread(object self){
        alloc(ButtonDialog).init().display(title);
    }
}

// Version 2
alloc(ButtonDialogThread).init().startThread();
wait_signal.waitOnSignal(10, NULL);

result("The index is " + index + "\n");

Executable version at the bottom, uncomment Version 2 block, comment all other versions

But this results in the same output as the above mentioned and shown dialog.

Version 3

The even more basic approach was to use a while loop with sleeping inbetween. As far as I know Signals work exactly like this. But still I gave it a try.

number index = -1;
object wait_signal = NewSignal(0);

class ButtonDialog : UIFrame{
    void button_pressed(object self, number i){
        index = i;
        wait_signal.SetSignal();
        self.close();
    }

    object init(object self){
        // creating dialog contents, for full example look at the bottom
        return self;
    }
}

// Version 3
alloc(ButtonDialog).init().display(title);
number security_counter = 0;
while(index == -1 && security_counter < 100){
    sleep(0.1);
    security_counter += 1;
}

result("The index is " + index + "\n");

Executable version at the bottom, uncomment Version 3 block, comment all other versions

But again, the same result as in Version 1.

What am I doing wrong? How do I block the main thread until the shown dialog has some user interaction?


string text = "Several files already exist. Do you want to overwrite the file 'test_file.dm4' and 12 others?"
string button0 = "Yes";
string button1 = "Yes, all";
string button2 = "No";
string button3 = "No, all";
string button4 = "Cancel";

string title = "Overwrite?";
object wait_signal = NewSignal(0);
number index = -1;

class ButtonDialog : UIFrame{
    void button_pressed(object self, number i){
        index = i;
        wait_signal.SetSignal();
        self.close();
    }
    
    void button0_pressed(object self){self.button_pressed(0);}
    void button1_pressed(object self){self.button_pressed(1);}
    void button2_pressed(object self){self.button_pressed(2);}
    void button3_pressed(object self){self.button_pressed(3);}
    void button4_pressed(object self){self.button_pressed(4);}
    
    object init(object self){
        TagGroup dlg, dlg_items, wrapper, label, b;
        
        index = -1;
        
        dlg = DLGCreateDialog("Press a button", dlg_items);
        
        dlg_items.DLGAddElement(DLGCreateLabel(text));
        
        wrapper = DLGCreateGroup();
        wrapper.DLGTableLayout(5, 1, 1);
        dlg_items.DLGAddElement(wrapper);
        
        b = DLGCreatePushButton(button0, "button0_pressed");
        b.DLGWidth(80);
        wrapper.DLGAddElement(b);
        
        b = DLGCreatePushButton(button1, "button1_pressed");
        b.DLGWidth(80);
        wrapper.DLGAddElement(b);
        
        b = DLGCreatePushButton(button2, "button2_pressed");
        b.DLGWidth(80);
        wrapper.DLGAddElement(b);
        
        b = DLGCreatePushButton(button3, "button3_pressed");
        b.DLGWidth(80);
        wrapper.DLGAddElement(b);
        
        b = DLGCreatePushButton(button4, "button4_pressed");
        b.DLGWidth(80);
        wrapper.DLGAddElement(b);
        
        self.super.init(dlg);
        return self;
    }
}

class ButtonDialogThread : Thread{
    object init(object self){
        return self;
    }
    
    void RunThread(object self){
        alloc(ButtonDialog).init().display(title);
    }
}       

// Version 1
// alloc(ButtonDialog).init().display(title);
// wait_signal.waitOnSignal(10, NULL);

// Version 2
alloc(ButtonDialogThread).init().startThread();
wait_signal.waitOnSignal(10, NULL);

// Version 3
// alloc(ButtonDialog).init().display(title);
// number security_counter = 0;
// while(index == -1 && security_counter < 100){
//  sleep(0.1);
//  security_counter += 1;
// }

result("The index is " + index + "\n");
1

There are 1 best solutions below

1
On BEST ANSWER

Actually all your attempts are good ones and in principle working. (The first one being the one to go with.) However, they are not working because you are missing one point:

All UI actions in DigitalMicrograph are run on the main thread of the application.

And also:

Any script is, by default, run on the main thread of the application.

Thus, your script is self-blocking. The script, after initializing the dialog and sending the message to display it, blocks the main-thread. therefore the messages to properly draw the dialog can not be executed and the dialog is not properly displayed. However, as the script waits for a message from the dialog, you're in a blocked situation.

You can not put a dialog on a separate thread - you can only put the script to launch the dialog on a separate thread. The UI will always need some main-thread cycles to actually work.

Thus, what you really need to do is to ensure the script isn't on the main thread. The simplest thing is to use the old method of putting script execution on a separate thread by having the very first line of the script written exactly as follows:

// $BACKGROUND$

(All capitalized and a white space after the // ) If you add this little line to your script, all is working. A more elegant way of starting a script on a separate thread is to use the threading class as you did in version 2. Just makes sure you put the script waiting for the signal on the background thread, not the dialog.