tge/engine/platformX86UNIX/x86UNIXMessageBox.cc
2017-04-17 06:17:10 -06:00

481 lines
14 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include "platformX86UNIX/x86UNIXMessageBox.h"
#define MessageBox_MaxWinWidth 800
#define MessageBox_MaxWinHeight 600
#define MessageBox_MinWinWidth 450
#define MessageBox_ButtonBoxWidth 60
#define MessageBox_ButtonBoxHeight 22
#define MessageBox_ButtonSpacer 20
#define MessageBox_ButtonVMargin 10
#define MessageBox_ButtonHMargin 10
#define MessageBox_LineSpacer 2
#define MessageBox_LineVMargin 10
#define MessageBox_LineHMargin 10
XMessageBoxButton::XMessageBoxButton()
{
strcpy(mLabel, "");
mClickVal = -1;
mLabelWidth = mX = mY = mWidth = mHeight = mMouseX = mMouseX = -1;
mMouseDown = false;
}
XMessageBoxButton::XMessageBoxButton(const char* label, int clickVal)
{
strncpy(mLabel, label, LabelSize);
mClickVal = clickVal;
mLabelWidth = mX = mY = mWidth = mHeight = mMouseX = mMouseX = -1;
mMouseDown = false;
}
XMessageBox::XMessageBox(Display* display)
{
mMessage = "";
mFS = NULL;
mDisplay = display;
}
XMessageBox::~XMessageBox()
{
clearMessageLines();
if (mDisplay != NULL)
{
mDisplay = NULL;
}
}
int XMessageBox::alertOK(const char *windowTitle, const char *message)
{
mMessage = message;
mTitle = windowTitle;
mButtons.clear();
mButtons.push_back(XMessageBoxButton("OK", OK));
return show();
}
int XMessageBox::alertOKCancel(const char *windowTitle, const char *message)
{
mMessage = message;
mTitle = windowTitle;
mButtons.clear();
mButtons.push_back(XMessageBoxButton("OK", OK));
mButtons.push_back(XMessageBoxButton("Cancel", Cancel));
return show();
}
int XMessageBox::alertRetryCancel(const char *windowTitle, const char *message)
{
mMessage = message;
mTitle = windowTitle;
mButtons.clear();
mButtons.push_back(XMessageBoxButton("Retry", Retry));
mButtons.push_back(XMessageBoxButton("Cancel", Cancel));
return show();
}
void XMessageBox::repaint()
{
int white = WhitePixel(mDisplay, DefaultScreen(mDisplay));
int black = BlackPixel(mDisplay, DefaultScreen(mDisplay));
int x = 0;
int y = 0;
// line V margin
y = y + MessageBox_LineVMargin * 2;
// line H margin
x = MessageBox_LineHMargin;
XSetForeground(mDisplay, mGC, black);
for (unsigned int i = 0; i < mMessageLines.size(); ++i)
{
XDrawString(mDisplay, mWin, mGC, x, y, mMessageLines[i],
strlen(mMessageLines[i]));
if (i < (mMessageLines.size() - 1))
y = y + MessageBox_LineSpacer + mFontHeight;
}
XFlush(mDisplay);
// line V margin
y = y + MessageBox_LineVMargin;
int maxButWidth = MessageBox_ButtonBoxWidth;
int maxButHeight = MessageBox_ButtonBoxHeight;
// compute size of text labels on buttons
int fgColor, bgColor;
int fontDirection, fontAscent, fontDescent;
Vector<XMessageBoxButton>::iterator iter;
for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
{
XCharStruct strInfo;
XTextExtents(mFS, iter->getLabel(), strlen(iter->getLabel()),
&fontDirection, &fontAscent, &fontDescent,
&strInfo);
// if (maxButWidth < strInfo.width)
// maxButWidth = strInfo.width;
// if (maxButHeight < (strInfo.ascent + strInfo.descent))
// maxButHeight = (strInfo.ascent + strInfo.descent);
iter->setLabelWidth(strInfo.width);
}
int buttonBoxWidth = maxButWidth;
int buttonBoxHeight = maxButHeight;
// draw buttons
// button V margin
y = y + MessageBox_ButtonVMargin;
// center the buttons
x = MessageBox_ButtonHMargin + (mMBWidth - getButtonLineWidth()) / 2;
for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
{
if (iter->drawReverse())
{
fgColor = white;
bgColor = black;
}
else
{
fgColor = black;
bgColor = white;
}
XSetForeground(mDisplay, mGC, bgColor);
XFillRectangle(mDisplay, mWin, mGC, x, y,
buttonBoxWidth, buttonBoxHeight);
XSetForeground(mDisplay, mGC, fgColor);
XDrawRectangle(mDisplay, mWin, mGC, x, y,
buttonBoxWidth, buttonBoxHeight);
XDrawString(mDisplay, mWin, mGC,
x + ((buttonBoxWidth - iter->getLabelWidth()) / 2),
y + mFontAscent + ((buttonBoxHeight - mFontAscent) / 2),
iter->getLabel(),
strlen(iter->getLabel()));
iter->setButtonRect(x, y, buttonBoxWidth, buttonBoxHeight);
x = x + buttonBoxWidth + MessageBox_ButtonSpacer;
}
}
template <class Type>
static inline Type max(Type v1, Type v2)
{
if (v1 <= v2)
return v2;
else
return v1;
}
template <class Type>
static inline Type min(Type v1, Type v2)
{
if (v1 > v2)
return v2;
else
return v1;
}
void XMessageBox::clearMessageLines()
{
Vector<char*>::iterator iter;
for (iter = mMessageLines.begin(); iter != mMessageLines.end(); ++iter)
delete [] *iter;
mMessageLines.clear();
}
void XMessageBox::splitMessage()
{
clearMessageLines();
if (mMessage == NULL || strlen(mMessage)==0)
// JMQTODO: what to do with empty strings?
return;
// need to break message up in to lines, and store lines in
// mMessageLines
int numChars = strlen(mMessage);
const int ScratchBufSize = 2048;
char scratchBuf[ScratchBufSize];
memset(scratchBuf, 0, ScratchBufSize);
int fontDirection, fontAscent, fontDescent;
XCharStruct strInfo;
char *curChar = const_cast<char*>(mMessage);
char *endChar;
char *curWrapped = scratchBuf;
int curWidth = 0;
int maxWidth = mMaxWindowWidth - (MessageBox_LineHMargin);
while ( // while pointers are in range...
(curChar - mMessage) < numChars &&
(curWrapped - scratchBuf) < ScratchBufSize)
{
// look for next space in remaining string
endChar = index(curChar, ' ');
if (endChar == NULL)
endChar = index(curChar, '\0');
if (endChar != NULL)
// increment one char past the space to include it
endChar++;
else
// otherwise, set the endchar to one char ahead
endChar = curChar + 1;
// compute length of substr
int len = endChar - curChar;
XTextExtents(mFS, curChar, len,
&fontDirection, &fontAscent, &fontDescent,
&strInfo);
// if its too big, time to add a new line...
if ((curWidth + strInfo.width) > maxWidth)
{
// create a new block for the line and add it
*curWrapped = '\0';
int len = strlen(scratchBuf);
char* line = new char[len+1];
strncpy(line, scratchBuf, len+1); // +1 gets the null char
mMessageLines.push_back(line);
// reset curWrapped to the beginning of the scratch buffer
curWrapped = scratchBuf;
curWidth = 0;
}
// copy the current string into curWrapped if we have enough room
int bytesRemaining =
ScratchBufSize - (curWrapped - scratchBuf);
if (bytesRemaining >= len)
strncpy(curWrapped, curChar, len);
curWrapped += len;
curWidth += strInfo.width;
curChar = endChar;
}
// make a final line out of any leftover stuff in the scratch buffer
if (curWrapped != scratchBuf)
{
*curWrapped = '\0';
int len = strlen(scratchBuf);
char* line = new char[len+1];
strncpy(line, scratchBuf, len+1); // +1 gets the null char
mMessageLines.push_back(line);
}
}
int XMessageBox::loadFont()
{
// load the font
mFS = XLoadQueryFont(mDisplay,
"-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*");
if (mFS == NULL)
mFS = XLoadQueryFont(mDisplay, "fixed");
if (mFS == NULL)
return -1;
// dummy call to XTextExtents to get the font specs
XCharStruct strInfo;
XTextExtents(mFS, "foo", 1,
&mFontDirection, &mFontAscent, &mFontDescent,
&strInfo);
mFontHeight = mFontAscent + mFontDescent;
return 0;
}
int XMessageBox::getButtonLineWidth()
{
return mButtons.size() * MessageBox_ButtonBoxWidth +
(mButtons.size() - 1) * MessageBox_ButtonSpacer +
MessageBox_ButtonHMargin * 2;
}
void XMessageBox::setDimensions()
{
mMBWidth = MessageBox_MaxWinWidth;
mMBHeight = MessageBox_MaxWinHeight;
// determine width of button line
int buttonWidth = getButtonLineWidth();
// if there is only one line, the desired width is the greater of the
// line width and the buttonWidth, otherwise the lineWidth is the
// max possible width which we already set.
if (mMessageLines.size() == 1)
{
XCharStruct strInfo;
int fontDirection, fontAscent, fontDescent;
XTextExtents(mFS, mMessageLines[0], strlen(mMessageLines[0]),
&fontDirection, &fontAscent, &fontDescent,
&strInfo);
mMBWidth = max(MessageBox_LineHMargin * 2 + strInfo.width,
buttonWidth);
mMBWidth = max(mMBWidth, MessageBox_MinWinWidth);
}
// determine the height of the button line
int buttonHeight = MessageBox_ButtonBoxHeight +
MessageBox_ButtonVMargin * 2;
int lineHeight = mFontHeight * mMessageLines.size() +
(mMessageLines.size() - 1) * MessageBox_LineSpacer +
MessageBox_LineVMargin * 2;
mMBHeight = buttonHeight + lineHeight;
}
int XMessageBox::show()
{
if (mDisplay == NULL)
return -1;
int retVal = 0;
retVal = loadFont();
if (retVal < 0)
return retVal;
// set the maximum window dimensions
mScreenWidth = DisplayWidth(mDisplay, DefaultScreen(mDisplay));
mScreenHeight = DisplayHeight(mDisplay, DefaultScreen(mDisplay));
mMaxWindowWidth = min(mScreenWidth, MessageBox_MaxWinWidth);
mMaxWindowHeight = min(mScreenHeight, MessageBox_MaxWinHeight);
// split the message into a vector of lines
splitMessage();
// set the dialog dimensions
setDimensions();
mWin = XCreateSimpleWindow(
mDisplay,
DefaultRootWindow(mDisplay),
(mScreenWidth - mMBWidth) / 2, (mScreenHeight - mMBHeight) / 2,
mMBWidth, mMBHeight,
1,
BlackPixel(mDisplay, DefaultScreen(mDisplay)),
WhitePixel(mDisplay, DefaultScreen(mDisplay)));
mGC = XCreateGC(mDisplay, mWin, 0, 0);
XSetFont(mDisplay, mGC, mFS->fid);
// set input mask
XSelectInput(mDisplay, mWin,
ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
// set wm protocols in case they hit X
Atom wm_delete_window =
XInternAtom(mDisplay, "WM_DELETE_WINDOW", False);
Atom wm_protocols =
XInternAtom(mDisplay, "WM_PROTOCOLS", False);
XSetWMProtocols (mDisplay, mWin, &wm_delete_window, 1);
// set pop up dialog hint
XSetTransientForHint(mDisplay, mWin, mWin);
// set title
XTextProperty wtitle;
wtitle.value = (unsigned char *)mTitle;
wtitle.encoding = XA_STRING;
wtitle.format = 8;
wtitle.nitems = strlen(mTitle);
XSetWMName(mDisplay, mWin, &wtitle);
// show window
XMapWindow(mDisplay, mWin);
// move it in case some bozo window manager repositioned it
XMoveWindow(mDisplay, mWin,
(mScreenWidth - mMBWidth) / 2, (mScreenHeight - mMBHeight) / 2);
// raise it to top
XRaiseWindow(mDisplay, mWin);
XMessageBoxButton* clickedButton = NULL;
XEvent event;
Vector<XMessageBoxButton>::iterator iter;
bool done = false;
while (!done)
{
XNextEvent(mDisplay, &event);
switch (event.type)
{
case Expose:
repaint();
break;
case MotionNotify:
for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
iter->setMouseCoordinates(event.xmotion.x, event.xmotion.y);
break;
case ButtonPress:
for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
{
if (iter->pointInRect(event.xbutton.x, event.xbutton.y))
{
iter->setMouseDown(true);
iter->setMouseCoordinates(event.xbutton.x, event.xbutton.y);
break;
}
}
break;
case ButtonRelease:
for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
{
if (iter->pointInRect(event.xbutton.x, event.xbutton.y) &&
iter->isMouseDown())
{
// we got a winner!
clickedButton = iter;
done = true;
break;
}
}
if (clickedButton == NULL)
{
// user released outside a button. clear the button states
for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
iter->setMouseDown(false);
}
break;
case ClientMessage:
if (event.xclient.message_type == wm_protocols &&
event.xclient.data.l[0] == static_cast<long>(wm_delete_window))
done = true;
break;
}
repaint();
}
XUnmapWindow(mDisplay, mWin);
XDestroyWindow(mDisplay, mWin);
XFreeGC(mDisplay, mGC);
XFreeFont(mDisplay, mFS);
if (clickedButton != NULL)
return clickedButton->getClickVal();
else
return -1;
}