views:

1306

answers:

4

Hi,

I need to implement a simple graphical message box for a Linux (SDL) application similar to the Windows MessageBox in C++ (gcc/g++ 4.4.0). All it needs to do is to display a caption, a message and an ok or close button and to return to the calling function when that button is clicked.

SDL just uses X(11) to open a window for (OpenGL) rendering.

I have looked through a similar thread regarding a GTK implementation, but that implementation doesn't seem to work properly.

I have also tried wxWidgets' wxMessageBox function, but compiling the headers makes the compiler throw error messages about syntax errors in include/c++/4.4.0/bits/stl_algobase.h (gcc 4.4.0 32 bits on openSuSE 11.1 32 bits). Using wxWidgets also means having to link a ton of libraries, adding STL to my app (Which it doesn't need otherwise) and who knows what else, so I do not want to use wxWidgets.

X11/motif (openmotif) has what I need (XmCreate{Error|Warning|InfoDialog), but these need a parent widget (e.g. top level window) which I don't have and do not accept a NULL parameter for these.

So I am stumped right now. Is there a simple way to do what I want? Or at least a halfway simple/easy/straightforward one? If yes, which one (giving as many details as possible would be highly appreciated).

+2  A: 

I personally use Qt4's QMessageBox.

example:

QMessageBox mb(QMessageBox::Question, "Title", "Message",  QMessageBox::Ok | QMessageBox::Cancel);
if(mb.exec() == QMessageBox::Ok) { do_stuff(); }
OneOfOne
Thanks. What is the name of the Qt4 library please (-l<libname>)?
karx11erx
@karx11erx: Qt is more than just a small library, it is an entire cross platform GUI (and more) solution. Using it requires more than just linking to a particular library. Often it is best to use their build system overall. Qt is often an "all or nothing" choice.
Evan Teran
Using Qt4 makes gcc 4.4.0 throw a lot of errors for me, and I don't need a behemoth on top of my app.
karx11erx
well most any GUI toolkit is going to be a "behemoth" compare to not using one (think about how large Win32 is!). The errors are almost certainly due to not correctly using the Qt build system.
Evan Teran
+1  A: 

Looks like you will have to create a top-level X11/Motif window. Here's some code to get you started:

#include <Xm/Xm.h> 
#include <Xm/PushB.h>

/* Prototype Callback function */

void pushed_fn(Widget , XtPointer , 
               XmPushButtonCallbackStruct *);


main(int argc, char **argv) 

{   Widget top_wid, button;
    XtAppContext  app;

    top_wid = XtVaAppInitialize(&app, "Push", NULL, 0,
        &argc, argv, NULL, NULL);

    button = XmCreatePushButton(top_wid, "Push_me", NULL, 0);

    /* tell Xt to manage button */
       XtManageChild(button);

       /* attach fn to widget */
    XtAddCallback(button, XmNactivateCallback, pushed_fn, NULL);

    XtRealizeWidget(top_wid); /* display widget hierarchy */
    XtAppMainLoop(app); /* enter processing loop */ 

}

void pushed_fn(Widget w, XtPointer client_data, 
               XmPushButtonCallbackStruct *cbs) 
  {   
     printf("Don't Push Me!!\n");
  }

This was copied from here which might give you some more pointers on this.

Matthew Talbert
SDL is obviously creating a top level window for OpenGL rendering - I mentioned that in my initial question. There is no way to obtain its handle except modifying SDL though.
karx11erx
Actually all I need is to make X(11) display such a window for me, but from what I've found on the inet on this programming any stuff in X is non-trivial.
karx11erx
Would it be possible to create a Motif Widget from the X11 window handle SDL has?
karx11erx
It would seem so, but I really don't know. Did you try just passing your X11 window handle to the Message Box function? Seems like that would work.
Matthew Talbert
How can I kill XtAppMainLoop from inside the program, or make it terminate after the user has clicked on my message box's ok button?
karx11erx
+1  A: 

I would suggest that you look into one of the GUI libraries that support SDL as a backend. One such library would be GG, which has the class ThreeButtonDlg. When its Run() returns, you can look at its Result(). See the Initial method in their minimal example.

Martin v. Löwis
Thanks, but too complicated.
karx11erx
+1  A: 

Here is my solution. I chose to use Motif (OpenMotif) as it requires comparably few extra libraries (Xm, Xt, X11). Depending on the message size, my implementation opens a simple message box or a more sophisticated dialog with a non editable, scrollable text (the latter taken from the Motif programmer's manual and adapted for my purposes).

Include files and global data:

#include <Xm/Xm.h>
#include <Xm/MwmUtil.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/MessageB.h>
#include <Xm/RowColumn.h>
#include <Xm/Form.h>
#include <Xm/PushBG.h>
#include <Xm/LabelG.h>
#include <Xm/PanedW.h>
#include <Xm/Text.h>
#include <Xm/DialogS.h>
#include <Xm/Command.h>

static XtAppContext appShell;

Helper function to determine rows and max. cols of a text message:

static int MsgSize (char* pszMsg, int& nCols)
{
if (!(pszMsg && *pszMsg))
   return 0;
int nRows = 1;
nCols = 0;
for (char* p = pszMsg; *p && (pszMsg = strchr (p, '\n')); nRows++, p = ++pszMsg) {
   if (nCols < pszMsg - p)
      nCols = pszMsg - p;
   }
return nRows;
}

Callback function for the message dialog's close button:

void DestroyShell (Widget widget, XtPointer clientData, XtPointer callData)
{
Widget shell = (Widget) clientData;
XtDestroyWidget (shell);
// tell the application event loop to terminate w/o terminating the application
XtAppSetExitFlag (appShell);
}

Build a dialog containing a scrollable, non editable text widget and a close button. Taken from the Motif programmer's manual and slightly adapted (no icon, single button), minimal window decoration).

void XmMessageDialog (const char* pszMsg, int nRows, int nCols, bool bError)
{
    Widget       msgBox, pane, msgText, form, widget;
    void         DestroyShell(Widget, XtPointer, XtPointer);
    Arg          args [10];
    int          n = 0;
    int          i;
    Dimension    h;

// Set up a DialogShell as a popup window. Set the delete window protocol response to XmDESTROY to make sure that
// the window goes away appropriately. Otherwise, it's XmUNMAP which means it'd be lost forever, since we're not storing
// the widget globally or statically to this function.
Widget topWid = XtVaAppInitialize (&appShell, "D2X-XL", NULL, 0, &argc, argv, NULL, NULL);
XtSetArg (args [0], XmNdeleteResponse, XmDESTROY);
msgBox = XmCreateDialogShell (topWid, bError ? const_cast<char*>("Error") : const_cast<char*>("Warning"), args, 1);
XtVaGetValues (msgBox, XmNmwmDecorations, &i, NULL);
i &= ~(MWM_DECOR_ALL | MWM_DECOR_MINIMIZE | MWM_DECOR_MAXIMIZE | MWM_DECOR_MENU);
XtVaSetValues (msgBox, XmNmwmDecorations, i, NULL);
XtVaGetValues (msgBox, XmNmwmFunctions, &i, NULL);
i &= ~(MWM_FUNC_ALL | MWM_FUNC_MINIMIZE | MWM_FUNC_MAXIMIZE | MWM_FUNC_CLOSE);
XtVaSetValues (msgBox, XmNmwmFunctions, i, NULL);
// Create a PanedWindow to manage the stuff in this dialog. PanedWindow won't let us set these to 0!
XtSetArg (args [0], XmNsashWidth, 1);
// Make small so user doesn't try to resize
XtSetArg (args [1], XmNsashHeight, 1);
pane = XmCreatePanedWindow (msgBox, const_cast<char*>("pane"), args, 2);
// Create a RowColumn in the form for Label and Text widgets. This is the control area.
form = XmCreateForm (pane, const_cast<char*>("form1"), NULL, 0);
// prepare the text for display in the ScrolledText object we are about to create.
n = 0;
XtSetArg (args [n], XmNscrollVertical, True); n++;
XtSetArg (args [n], XmNscrollHorizontal, False); n++;
XtSetArg (args [n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
XtSetArg (args [n], XmNeditable, False); n++;
XtSetArg (args [n], XmNcursorPositionVisible, False); n++;
XtSetArg (args [n], XmNwordWrap, True); n++;
XtSetArg (args [n], XmNvalue, pszMsg); n++;
XtSetArg (args [n], XmNrows, min (nRows, 30)); n++;
XtSetArg (args [n], XmNcolumns, min (nCols, 120)); n++;
msgText = XmCreateScrolledText (form, const_cast<char*>("help_text"), args, n);
// Attachment values must be set on the Text widget's PARENT, the ScrolledWindow. This  is the object that is positioned.
XtVaSetValues (XtParent (msgText),
               XmNleftAttachment, XmATTACH_FORM,
               XmNtopAttachment, XmATTACH_FORM,
               XmNrightAttachment, XmATTACH_FORM,
               XmNbottomAttachment, XmATTACH_FORM,
               NULL);
XtManageChild (msgText);
XtManageChild (form);
// Create another form to act as the action area for the dialog
XtSetArg (args [0], XmNfractionBase, 5);
form = XmCreateForm (pane, const_cast<char*>("form2"), args, 1);
// The OK button is under the pane's separator and is attached to the left edge of the form. It spreads from
// position 0 to 1 along the bottom (the form is split into 5 separate grids via XmNfractionBase upon creation).
widget = XmCreatePushButtonGadget (form, const_cast<char*>("Close"), NULL, 0);
XtVaSetValues (widget,
               XmNtopAttachment, XmATTACH_FORM,
               XmNbottomAttachment, XmATTACH_FORM,
               XmNleftAttachment, XmATTACH_POSITION,
               XmNleftPosition, 2,
               XmNrightAttachment, XmATTACH_POSITION,
               XmNrightPosition, 3,
               XmNshowAsDefault, True,
               XmNdefaultButtonShadowThickness, 1,
               NULL);
XtManageChild (widget);
XtAddCallback (widget, XmNactivateCallback, DestroyShell, (XtPointer) msgBox);
// Fix the action area pane to its current height -- never let it resize
XtManageChild (form);
XtVaGetValues (widget, XmNheight, &h, NULL);
XtVaSetValues (form, XmNpaneMaximum, h, XmNpaneMinimum, h, NULL);
// This also pops up the dialog, as it is the child of a DialogShell
XtManageChild (pane);
}

Callback function for the message box' Ok button

void XmCloseMsgBox (Widget w, XtPointer clientData, XtPointer callData)
{
XtAppSetExitFlag (appShell);
}

Decide whether to use the simple or advanced message box, display either of them, and remove them when the user clicks their close/ok button.

void XmMessageBox (const char* pszMsg, bool bError)
{
   Widget   topWid;
   int      nRows, nCols;

nRows = MsgSize (const_cast<char*>(pszMsg), nCols);
if ((nRows > 3) || (nCols > 360))
   XmMessageDialog (pszMsg, nRows, nCols, bError);
else { // use the built-in message box
   topWid = XtVaAppInitialize (&appShell, "D2X-XL", NULL, 0, &argC, argv, NULL, NULL);
   // setup message box text
   Arg args [1];
   XmString xmString = XmStringCreateLocalized (const_cast<char*>(pszMsg));
   XtSetArg (args [0], XmNmessageString, xmString);
   // create and label message box
   Widget xMsgBox = bError
                    ? XmCreateErrorDialog (topWid, const_cast<char*>("Error"), args, 1)
                    : XmCreateWarningDialog (topWid, const_cast<char*>("Warning"), args, 1);
   // remove text resource
   XmStringFree (xmString);
   // remove help and cancel buttons
   XtUnmanageChild (XmMessageBoxGetChild (xMsgBox, XmDIALOG_CANCEL_BUTTON));
   XtUnmanageChild (XmMessageBoxGetChild (xMsgBox, XmDIALOG_HELP_BUTTON));
   // add callback to the "close" button that signals closing of the message box
   XtAddCallback (xMsgBox, XmNokCallback, XmCloseMsgBox, NULL);
   XtManageChild (xMsgBox);
   XtRealizeWidget (topWid);
   }
XtAppMainLoop (appShell);
XtUnrealizeWidget (topWid);
XtDestroyApplicationContext (appShell);
}
karx11erx