modal controls

Hi all.

I have the following problem: for some menu commands which require user input, I would like to create a TextCtrl in a frame at the bottom of the GUI (like a StatusBar) which grabs the event loop and release it only if the user presses ESC or ENTER. Basically it would be a replacement for a modal dialog, which I would prefer to avoid because I do not want to clutter the desktop. In fact it would be a kind of EMACS's minibuffer.

I've seen that a wxWindow has a MakeModal method, but it is virtual, and looking in the implementation of Dialog::ShowModal() for GTK I've seen they directly use gtk_main(). Can anyone give me a hint or point to some demo code addressing the same problem?

thanks,
curzio

Curzio Basso wrote:

Hi all.

I have the following problem: for some menu commands which require user input, I would like to create a TextCtrl in a frame at the bottom of the GUI (like a StatusBar) which grabs the event loop and release it only if the user presses ESC or ENTER. Basically it would be a replacement for a modal dialog, which I would prefer to avoid because I do not want to clutter the desktop. In fact it would be a kind of EMACS's minibuffer.

I've seen that a wxWindow has a MakeModal method, but it is virtual, and looking in the implementation of Dialog::ShowModal() for GTK I've seen they directly use gtk_main(). Can anyone give me a hint or point to some demo code addressing the same problem?

MakeModal really only works well for Frames anyway.

There are a couple approaches I can think of, but they may look a little clunky at runtime. (BTW, the minibuffer in emacs is not modal...)

* You can make all the other windows in the app be disabled (perhaps with wx.WindowDisabler) and enable only your TextCtrl.

* You can catch the TextCtrl's EVT_KILL_FOCUS and everytime it loses the focs you get it back with wx.CallAfter(self.SetFocus)

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Robin Dunn wrote:

MakeModal really only works well for Frames anyway.

Ah. So you mean wx.Frame.MakeModal() is implemented? But that should do, because the TextCtrl could be in a frame anyway. I'll try this.

There are a couple approaches I can think of, but they may look a little clunky at runtime. (BTW, the minibuffer in emacs is not modal...)

Ok, the minibuffer is not modal, that's right...

maybe I should make a practical example: I have an editor and want to implement the command "goto line". I do not want to open a dialog asking for the line, I just want this TextCtrl at the bottom of the window, as in EMACS. Now, the function called by the menu item "Goto line" has to wait until the user has entered a line number in the control, and then check the return code to see if the user interrupted the action, as for a modal dialog. E.g.:

if minibuffer->ShowCtrl() == wx.OK:
      editor->Goto(minibuffer->GetValue())

In fact, the problem is not disabling interaction with the rest of the application, but more how to implement the ShowCtrl() for the minibuffer, which should return only upon certain events.

Sounds to me like you want dialog *behavior*
without dialog *appearance*.

You could (I think)...
1. Define a dialog MiniBuffer with one TextCtrl, no padding,
    and no buttons. Catch the Enter and Escape key and process
    them appropriately (call self.EndModal). Provide Set/GetValue
    methods that simply call the TextCtrl's equivalent methods.
2. Create a "minibuffer" dialog with no border or padding.
        minibuffer = MiniBuffer()
3. Position and size it to exactly cover your status bar.
4. Invoke ShowModal and switch on result.
       if minibuffer.ShowModal() == wx.OK:
          editor.Goto( minibuffer.GetValue() )
Some additional steps may be required to eliminate
default behavior such as centering the dialog on the
screen or the parent.

My $0.015 worth. :slight_smile:

- Sam

···

At 2004-11-02 10:50 AM +0100, you wrote:

maybe I should make a practical example: I have an editor and want to implement the command "goto line". I do not want to open a dialog asking for the line, I just want this TextCtrl at the bottom of the window, as in EMACS. Now, the function called by the menu item "Goto line" has to wait until the user has entered a line number in the control, and then check the return code to see if the user interrupted the action, as for a modal dialog. E.g.:

if minibuffer->ShowCtrl() == wx.OK:
     editor->Goto(minibuffer->GetValue())

In fact, the problem is not disabling interaction with the rest of the application, but more how to implement the ShowCtrl() for the minibuffer, which should return only upon certain events.

__________________________________________________________
Spinward Stars, LLC Samuel Reynolds
Software Consulting and Development 303-805-1446
http://SpinwardStars.com/ sam@SpinwardStars.com

Samuel Reynolds wrote:

Sounds to me like you want dialog *behavior*
without dialog *appearance*.

You could (I think)...
1. Define a dialog MiniBuffer with one TextCtrl, no padding,
   and no buttons. Catch the Enter and Escape key and process
   them appropriately (call self.EndModal). Provide Set/GetValue
   methods that simply call the TextCtrl's equivalent methods.
2. Create a "minibuffer" dialog with no border or padding.
       minibuffer = MiniBuffer()
3. Position and size it to exactly cover your status bar.

Doesn't a dialog always have a title bar?

Look at the docs.
wx.DEFAULT_DIALOG_STYLE=wx.CAPTION|wx.CLOSE_BOX|wx.SYSTEM_MENU
CAPTION is the title.
CLOSE_BOX and SYSTEM_MENU require a title bar.

If you use style=wx.NO_BORDER, you get a borderless
frame without a title bar.

(FWIW, I wasn't sure, so I did a quick test, which I've
attached.)

- Sam

DialogTest.py (934 Bytes)

···

At 2004-11-02 05:08 PM +0100, you wrote:

Samuel Reynolds wrote:

Sounds to me like you want dialog *behavior*
without dialog *appearance*.
You could (I think)...
1. Define a dialog MiniBuffer with one TextCtrl, no padding,
   and no buttons. Catch the Enter and Escape key and process
   them appropriately (call self.EndModal). Provide Set/GetValue
   methods that simply call the TextCtrl's equivalent methods.
2. Create a "minibuffer" dialog with no border or padding.
       minibuffer = MiniBuffer()
3. Position and size it to exactly cover your status bar.

Doesn't a dialog always have a title bar?

__________________________________________________________
Spinward Stars, LLC Samuel Reynolds
Software Consulting and Development 303-805-1446
http://SpinwardStars.com/ sam@SpinwardStars.com

Samuel Reynolds wrote:

Look at the docs.
wx.DEFAULT_DIALOG_STYLE=wx.CAPTION|wx.CLOSE_BOX|wx.SYSTEM_MENU
CAPTION is the title.
CLOSE_BOX and SYSTEM_MENU require a title bar.

If you use style=wx.NO_BORDER, you get a borderless
frame without a title bar.

ah! I looked at the docs and I tried to use a style without CAPTION, but did not think about NO_BORDER.

(FWIW, I wasn't sure, so I did a quick test, which I've
attached.)

Great. I'll try this! thanks

Curzio Basso wrote:

Robin Dunn wrote:

MakeModal really only works well for Frames anyway.

Ah. So you mean wx.Frame.MakeModal() is implemented? But that should do, because the TextCtrl could be in a frame anyway. I'll try this.

There are a couple approaches I can think of, but they may look a little clunky at runtime. (BTW, the minibuffer in emacs is not modal...)

Ok, the minibuffer is not modal, that's right...

maybe I should make a practical example: I have an editor and want to implement the command "goto line". I do not want to open a dialog asking for the line, I just want this TextCtrl at the bottom of the window, as in EMACS. Now, the function called by the menu item "Goto line" has to wait until the user has entered a line number in the control, and then check the return code to see if the user interrupted the action, as for a modal dialog. E.g.:

if minibuffer->ShowCtrl() == wx.OK:
     editor->Goto(minibuffer->GetValue())

In fact, the problem is not disabling interaction with the rest of the application, but more how to implement the ShowCtrl() for the minibuffer, which should return only upon certain events.

I think I would approach it a little differently. Rather than wait modally for the user to enter something, just pass the command to execute to the class that manages the textctrl. Then later when that class gets the event for the enter key it can call whatever fucntion was passed to it, and no modalness is needed. For example, you would use it something like this:

     minibuffer.ShowCtrl(prompt="Enter line number:",
                         command=editor.Goto)

Then a panel with a statictext and textctrl would be shown and inserted into the layout, the statictext label would be set to the prompt, and focus would be set to the textctrl. At that point ShowCtrl returns and control is allowed to return to the MainLoop. The minibuffer has event handlers bound to the textctrl for EVT_KEY_DOWN and EVT_KILL_FOCUS. If the Esc key is pressed, or focus is lost, then the minibuffer hides itself and nothing is done. If the Enter key is pressed then it calls the command function that was given in the ShowCtrl call.

BTW, the current Firefox has a popup bar like this for the search function that is not modal. When in Firefox press the '/' key and then type what you are searching for.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Curzio Basso wrote:

Great. I'll try this! thanks

In fact, I realized that you would have anyway a separate window, even if without titlebar and borders. And this won't do, it has to be in the main frame, place into a sizer. Thanks anyway!

Robin Dunn wrote:

I think I would approach it a little differently. Rather than wait modally for the user to enter something, just pass the command to execute to the class that manages the textctrl. Then later when that

;o)

well, this is exactly the implementation I currently have and which I don't like. And the reason I do not like is that if the command needs more than one input (replace, for instance), than this method gets way too cumbersome: you can do it but it's ugly. Moreover, take exactly this example with the Goto, the editor.Goto function should take an int, but the minibuffer gets a string from the TextCtrl. How does it know the string must be converted to an int? Of course I can put this in the Goto method, but it's jus a hack.

So, this is my problem, I have a solution which somehow works, but it is in fact just a hack. On the other hand, if there is no other way I'll probably stick to that.

Ah, and yes, I've seen the bar in Firefox, and there is a big difference: it is only for the search function! To implement this would be easy, I think, since it's just another control. The minibuffer in EMACS, on the other hand, is for everything.

curzio

well, this is exactly the implementation I currently have and which I
don't like. And the reason I do not like is that if the command needs
more than one input (replace, for instance), than this method gets way
too cumbersome: you can do it but it's ugly.

Now you're going to make me crack open the Emacs code to see how *they* do it.
:slight_smile:

Moreover, take exactly this
example with the Goto, the editor.Goto function should take an int, but
the minibuffer gets a string from the TextCtrl. How does it know the
string must be converted to an int? Of course I can put this in the Goto
method, but it's jus a hack.

I'm not sure why that's a hack. I suppose that each use of your minibuffer
(search, search-replace, goto line, etc.) could provide some GUI info for
setting up the control, such as the number of arguments and the validators to
use for each one.

By the way, even when there's a separate dialog box, most of the apps I use on
a day-to-day basis don't make something like the search-replace dialog modal.

The minibuffer in
EMACS, on the other hand, is for everything.

You keep comparing this feature to the minibuffer in emacs, but I like the
emacs minibuffer *because* it's non-modal. Thus, I can start the search and
replace, realize that I have a long string to search, jump into the main
buffer, copy the text I want to search, jump back to the mini-buffer, paste
that text, etc. If I need to, I can start the search and replace, enter the
search string, jump out of the minibuffer, start a *new* mini-buffer so that
I can open a second file, find the string to replace in the second file, copy
it, jump back to the original minibuffer, and paste the string.

In fact, if your minibuffer is modal, then I'm not sure why you're going to
the trouble to avoid a modal dialog instead. I hit search-replace. The
cursor jumps into the minibuffer where I'm trapped until I type in the
strings or decide to cancel. Or, a new dialog box opens, my cursor jumps
into the text control of the dialog, and I'm trapped in the dialog until I
type in the strings or decide to cancel. It seems like pretty much identical
functionality for the end user, isn't it?

Just curious why you're going to all this trouble...

Now if your minibuffer is going to be as cool as the Emacs one, where I get
*all* of the editing features that are available in the main window,
non-modal behavior, multiple currently active minibuffers, etc. Well, *that*
would be worth the effort! :slight_smile:

---Tom

···

On Wednesday 03 November 2004 04:55 am, Curzio Basso wrote:

Tom Bryan wrote:

Now you're going to make me crack open the Emacs code to see how *they* do it. :slight_smile:

I was tempted, only problem I would have to learn lisp ;o)

By the way, even when there's a separate dialog box, most of the apps I use on a day-to-day basis don't make something like the search-replace dialog modal.

You keep comparing this feature to the minibuffer in emacs, but I like the emacs minibuffer *because* it's non-modal. Thus, I can start the search and

Yes, yes, I'm sorry, the subject is wrong. Robin Dunn made the same observation, and you both are right, I agree, and in fact the feature I am missing is not the... er... modal-mode, but rather this sort of delayed call to the menu command only after, and if, all the required arguments have been passed through the minibuffer.

Just curious why you're going to all this trouble...

as I said, in fact I started the thread in the wrong direction. Maybe it should have been : "implementing an EMACS-like minibuffer" ;o)

Now if your minibuffer is going to be as cool as the Emacs one, where I get *all* of the editing features that are available in the main window, non-modal behavior, multiple currently active minibuffers, etc. Well, *that* would be worth the effort! :slight_smile:

that's exactly what I would like to have (apart from the multiple minibuffers), even if I don't think I'll able to get to that.

--- wx 2.4 + Py. 2.3 + GTK + Linux ---

When I do something like'self.flexGridSizer1.Remove(self.textCtrl1)', this text control is removed from sizer layout.
The problem is when this text control have an scrollbar, this scrollbar doesn't dissapear from layout!!!
Any idea?

Thanks.

···

--
Francisco Iván Antón Prieto

Francisco Ivan Anton Prieto wrote:

--- wx 2.4 + Py. 2.3 + GTK + Linux ---

When I do something like'self.flexGridSizer1.Remove(self.textCtrl1)', this text control is removed from sizer layout.
The problem is when this text control have an scrollbar, this scrollbar doesn't dissapear from layout!!!
Any idea?

The textctrl still exists since Remove doesn't destroy it. Try calling self.textCtrl1.Destroy() after you remove it.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Robin Dunn wrote:

Francisco Ivan Anton Prieto wrote:

--- wx 2.4 + Py. 2.3 + GTK + Linux ---

When I do something like'self.flexGridSizer1.Remove(self.textCtrl1)', this text control is removed from sizer layout.
The problem is when this text control have an scrollbar, this scrollbar doesn't dissapear from layout!!!
Any idea?

The textctrl still exists since Remove doesn't destroy it. Try calling self.textCtrl1.Destroy() after you remove it.

Yes, this works ok (Destroy), but the problem is that I only want to hide/show text control from layout (it is a console).

You give-me the key:

To hide:
self.flexGridSizer1.Remove(self.textCtrl1)
self.textCtrl1.Hide()

To show:
self.flexGridSizer1.AddWindow(self.textCtrl1, 0, border=0, flag=wxEXPAND)
self.textCtrl1.Show()

···

--
Francisco Iván Antón Prieto
Director Técnico

OZONO MULTIMEDIA S.L.L.
C.I.F. B15816275
galeras 13 - planta 2 - oficina 2
santiago de compostela - a coruña
tel./fax 981 580 679
www.ozonomultimedia.com
ozono@ozonomultimedia.com

Did you call self.flexGridSizer1.Layout()
after removing the text control?

- Sam

···

At 2004-11-04 08:41 PM +0100, you wrote:

When I do something like'self.flexGridSizer1.Remove(self.textCtrl1)', this text control is removed from sizer layout.
The problem is when this text control have an scrollbar, this scrollbar doesn't dissapear from layout!!!
Any idea?

__________________________________________________________
Spinward Stars, LLC Samuel Reynolds
Software Consulting and Development 303-805-1446
http://SpinwardStars.com/ sam@SpinwardStars.com