x86UNIXMessageBox.client.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include <stdio.h>
  23. #include <stdlib.h>
  24. #include <string.h>
  25. #include <X11/Xlib.h>
  26. #include <X11/Xutil.h>
  27. #include <X11/Xatom.h>
  28. #include "platformX86UNIX/x86UNIXMessageBox.h"
  29. #define MessageBox_MaxWinWidth 800
  30. #define MessageBox_MaxWinHeight 600
  31. #define MessageBox_MinWinWidth 450
  32. #define MessageBox_ButtonBoxWidth 60
  33. #define MessageBox_ButtonBoxHeight 22
  34. #define MessageBox_ButtonSpacer 20
  35. #define MessageBox_ButtonVMargin 10
  36. #define MessageBox_ButtonHMargin 10
  37. #define MessageBox_LineSpacer 2
  38. #define MessageBox_LineVMargin 10
  39. #define MessageBox_LineHMargin 10
  40. XMessageBoxButton::XMessageBoxButton()
  41. {
  42. strcpy(mLabel, "");
  43. mClickVal = -1;
  44. mLabelWidth = mX = mY = mWidth = mHeight = mMouseX = mMouseX = -1;
  45. mMouseDown = false;
  46. }
  47. XMessageBoxButton::XMessageBoxButton(const char* label, int clickVal)
  48. {
  49. strncpy(mLabel, label, LabelSize);
  50. mClickVal = clickVal;
  51. mLabelWidth = mX = mY = mWidth = mHeight = mMouseX = mMouseX = -1;
  52. mMouseDown = false;
  53. }
  54. XMessageBox::XMessageBox(Display* display)
  55. {
  56. mMessage = "";
  57. mFS = NULL;
  58. mDisplay = display;
  59. }
  60. XMessageBox::~XMessageBox()
  61. {
  62. clearMessageLines();
  63. if (mDisplay != NULL)
  64. {
  65. mDisplay = NULL;
  66. }
  67. }
  68. int XMessageBox::alertOK(const char *windowTitle, const char *message)
  69. {
  70. mMessage = message;
  71. mTitle = windowTitle;
  72. mButtons.clear();
  73. mButtons.push_back(XMessageBoxButton("OK", OK));
  74. return show();
  75. }
  76. int XMessageBox::alertOKCancel(const char *windowTitle, const char *message)
  77. {
  78. mMessage = message;
  79. mTitle = windowTitle;
  80. mButtons.clear();
  81. mButtons.push_back(XMessageBoxButton("OK", OK));
  82. mButtons.push_back(XMessageBoxButton("Cancel", Cancel));
  83. return show();
  84. }
  85. int XMessageBox::alertRetryCancel(const char *windowTitle, const char *message)
  86. {
  87. mMessage = message;
  88. mTitle = windowTitle;
  89. mButtons.clear();
  90. mButtons.push_back(XMessageBoxButton("Retry", Retry));
  91. mButtons.push_back(XMessageBoxButton("Cancel", Cancel));
  92. return show();
  93. }
  94. int XMessageBox::alertAssert(const char *windowTitle, const char *message)
  95. {
  96. mMessage = message;
  97. mTitle = windowTitle;
  98. mButtons.clear();
  99. mButtons.push_back(XMessageBoxButton("Exit", OK));
  100. mButtons.push_back(XMessageBoxButton("Ignore", Cancel));
  101. mButtons.push_back(XMessageBoxButton("Ignore All", IgnoreAll));
  102. mButtons.push_back(XMessageBoxButton("Debug", Retry));
  103. return show();
  104. }
  105. void XMessageBox::repaint()
  106. {
  107. int white = WhitePixel(mDisplay, DefaultScreen(mDisplay));
  108. int black = BlackPixel(mDisplay, DefaultScreen(mDisplay));
  109. int x = 0;
  110. int y = 0;
  111. // line V margin
  112. y = y + MessageBox_LineVMargin * 2;
  113. // line H margin
  114. x = MessageBox_LineHMargin;
  115. XSetForeground(mDisplay, mGC, black);
  116. for (unsigned int i = 0; i < mMessageLines.size(); ++i)
  117. {
  118. XDrawString(mDisplay, mWin, mGC, x, y, mMessageLines[i],
  119. strlen(mMessageLines[i]));
  120. if (i < (mMessageLines.size() - 1))
  121. y = y + MessageBox_LineSpacer + mFontHeight;
  122. }
  123. XFlush(mDisplay);
  124. // line V margin
  125. y = y + MessageBox_LineVMargin;
  126. int maxButWidth = MessageBox_ButtonBoxWidth;
  127. int maxButHeight = MessageBox_ButtonBoxHeight;
  128. // compute size of text labels on buttons
  129. int fgColor, bgColor;
  130. int fontDirection, fontAscent, fontDescent;
  131. Vector<XMessageBoxButton>::iterator iter;
  132. for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
  133. {
  134. XCharStruct strInfo;
  135. XTextExtents(mFS, iter->getLabel(), strlen(iter->getLabel()),
  136. &fontDirection, &fontAscent, &fontDescent,
  137. &strInfo);
  138. // if (maxButWidth < strInfo.width)
  139. // maxButWidth = strInfo.width;
  140. // if (maxButHeight < (strInfo.ascent + strInfo.descent))
  141. // maxButHeight = (strInfo.ascent + strInfo.descent);
  142. iter->setLabelWidth(strInfo.width);
  143. }
  144. int buttonBoxWidth = maxButWidth;
  145. int buttonBoxHeight = maxButHeight;
  146. // draw buttons
  147. // button V margin
  148. y = y + MessageBox_ButtonVMargin;
  149. // center the buttons
  150. x = MessageBox_ButtonHMargin + (mMBWidth - getButtonLineWidth()) / 2;
  151. for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
  152. {
  153. if (iter->drawReverse())
  154. {
  155. fgColor = white;
  156. bgColor = black;
  157. }
  158. else
  159. {
  160. fgColor = black;
  161. bgColor = white;
  162. }
  163. XSetForeground(mDisplay, mGC, bgColor);
  164. XFillRectangle(mDisplay, mWin, mGC, x, y,
  165. buttonBoxWidth, buttonBoxHeight);
  166. XSetForeground(mDisplay, mGC, fgColor);
  167. XDrawRectangle(mDisplay, mWin, mGC, x, y,
  168. buttonBoxWidth, buttonBoxHeight);
  169. XDrawString(mDisplay, mWin, mGC,
  170. x + ((buttonBoxWidth - iter->getLabelWidth()) / 2),
  171. y + mFontAscent + ((buttonBoxHeight - mFontAscent) / 2),
  172. iter->getLabel(),
  173. strlen(iter->getLabel()));
  174. iter->setButtonRect(x, y, buttonBoxWidth, buttonBoxHeight);
  175. x = x + buttonBoxWidth + MessageBox_ButtonSpacer;
  176. }
  177. }
  178. template <class Type>
  179. static inline Type max(Type v1, Type v2)
  180. {
  181. if (v1 <= v2)
  182. return v2;
  183. else
  184. return v1;
  185. }
  186. template <class Type>
  187. static inline Type min(Type v1, Type v2)
  188. {
  189. if (v1 > v2)
  190. return v2;
  191. else
  192. return v1;
  193. }
  194. void XMessageBox::clearMessageLines()
  195. {
  196. Vector<char*>::iterator iter;
  197. for (iter = mMessageLines.begin(); iter != mMessageLines.end(); ++iter)
  198. delete [] *iter;
  199. mMessageLines.clear();
  200. }
  201. void XMessageBox::splitMessage()
  202. {
  203. clearMessageLines();
  204. if (mMessage == NULL || strlen(mMessage)==0)
  205. // JMQTODO: what to do with empty strings?
  206. return;
  207. // need to break message up in to lines, and store lines in
  208. // mMessageLines
  209. int numChars = strlen(mMessage);
  210. const int ScratchBufSize = 2048;
  211. char scratchBuf[ScratchBufSize];
  212. memset(scratchBuf, 0, ScratchBufSize);
  213. int fontDirection, fontAscent, fontDescent;
  214. XCharStruct strInfo;
  215. char *curChar = const_cast<char*>(mMessage);
  216. char *endChar;
  217. char *curWrapped = scratchBuf;
  218. int curWidth = 0;
  219. int maxWidth = mMaxWindowWidth - (MessageBox_LineHMargin);
  220. while ( // while pointers are in range...
  221. (curChar - mMessage) < numChars &&
  222. (curWrapped - scratchBuf) < ScratchBufSize)
  223. {
  224. // look for next space in remaining string
  225. endChar = index(curChar, ' ');
  226. if (endChar == NULL)
  227. endChar = index(curChar, '\0');
  228. if (endChar != NULL)
  229. // increment one char past the space to include it
  230. endChar++;
  231. else
  232. // otherwise, set the endchar to one char ahead
  233. endChar = curChar + 1;
  234. // compute length of substr
  235. int len = endChar - curChar;
  236. XTextExtents(mFS, curChar, len,
  237. &fontDirection, &fontAscent, &fontDescent,
  238. &strInfo);
  239. // if its too big, time to add a new line...
  240. if ((curWidth + strInfo.width) > maxWidth)
  241. {
  242. // create a new block for the line and add it
  243. *curWrapped = '\0';
  244. int len = strlen(scratchBuf);
  245. char* line = new char[len+1];
  246. strncpy(line, scratchBuf, len+1); // +1 gets the null char
  247. mMessageLines.push_back(line);
  248. // reset curWrapped to the beginning of the scratch buffer
  249. curWrapped = scratchBuf;
  250. curWidth = 0;
  251. }
  252. // copy the current string into curWrapped if we have enough room
  253. int bytesRemaining =
  254. ScratchBufSize - (curWrapped - scratchBuf);
  255. if (bytesRemaining >= len)
  256. strncpy(curWrapped, curChar, len);
  257. curWrapped += len;
  258. curWidth += strInfo.width;
  259. curChar = endChar;
  260. }
  261. // make a final line out of any leftover stuff in the scratch buffer
  262. if (curWrapped != scratchBuf)
  263. {
  264. *curWrapped = '\0';
  265. int len = strlen(scratchBuf);
  266. char* line = new char[len+1];
  267. strncpy(line, scratchBuf, len+1); // +1 gets the null char
  268. mMessageLines.push_back(line);
  269. }
  270. }
  271. int XMessageBox::loadFont()
  272. {
  273. // load the font
  274. mFS = XLoadQueryFont(mDisplay,
  275. "-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*");
  276. if (mFS == NULL)
  277. mFS = XLoadQueryFont(mDisplay, "fixed");
  278. if (mFS == NULL)
  279. return -1;
  280. // dummy call to XTextExtents to get the font specs
  281. XCharStruct strInfo;
  282. XTextExtents(mFS, "foo", 1,
  283. &mFontDirection, &mFontAscent, &mFontDescent,
  284. &strInfo);
  285. mFontHeight = mFontAscent + mFontDescent;
  286. return 0;
  287. }
  288. int XMessageBox::getButtonLineWidth()
  289. {
  290. return mButtons.size() * MessageBox_ButtonBoxWidth +
  291. (mButtons.size() - 1) * MessageBox_ButtonSpacer +
  292. MessageBox_ButtonHMargin * 2;
  293. }
  294. void XMessageBox::setDimensions()
  295. {
  296. mMBWidth = MessageBox_MaxWinWidth;
  297. mMBHeight = MessageBox_MaxWinHeight;
  298. // determine width of button line
  299. int buttonWidth = getButtonLineWidth();
  300. // if there is only one line, the desired width is the greater of the
  301. // line width and the buttonWidth, otherwise the lineWidth is the
  302. // max possible width which we already set.
  303. if (mMessageLines.size() == 1)
  304. {
  305. XCharStruct strInfo;
  306. int fontDirection, fontAscent, fontDescent;
  307. XTextExtents(mFS, mMessageLines[0], strlen(mMessageLines[0]),
  308. &fontDirection, &fontAscent, &fontDescent,
  309. &strInfo);
  310. mMBWidth = max(MessageBox_LineHMargin * 2 + strInfo.width,
  311. buttonWidth);
  312. mMBWidth = max(mMBWidth, MessageBox_MinWinWidth);
  313. }
  314. // determine the height of the button line
  315. int buttonHeight = MessageBox_ButtonBoxHeight +
  316. MessageBox_ButtonVMargin * 2;
  317. int lineHeight = mFontHeight * mMessageLines.size() +
  318. (mMessageLines.size() - 1) * MessageBox_LineSpacer +
  319. MessageBox_LineVMargin * 2;
  320. mMBHeight = buttonHeight + lineHeight;
  321. }
  322. int XMessageBox::show()
  323. {
  324. if (mDisplay == NULL)
  325. return -1;
  326. int retVal = 0;
  327. retVal = loadFont();
  328. if (retVal < 0)
  329. return retVal;
  330. // set the maximum window dimensions
  331. mScreenWidth = DisplayWidth(mDisplay, DefaultScreen(mDisplay));
  332. mScreenHeight = DisplayHeight(mDisplay, DefaultScreen(mDisplay));
  333. mMaxWindowWidth = min(mScreenWidth, MessageBox_MaxWinWidth);
  334. mMaxWindowHeight = min(mScreenHeight, MessageBox_MaxWinHeight);
  335. // split the message into a vector of lines
  336. splitMessage();
  337. // set the dialog dimensions
  338. setDimensions();
  339. mWin = XCreateSimpleWindow(
  340. mDisplay,
  341. DefaultRootWindow(mDisplay),
  342. (mScreenWidth - mMBWidth) / 2, (mScreenHeight - mMBHeight) / 2,
  343. mMBWidth, mMBHeight,
  344. 1,
  345. BlackPixel(mDisplay, DefaultScreen(mDisplay)),
  346. WhitePixel(mDisplay, DefaultScreen(mDisplay)));
  347. mGC = XCreateGC(mDisplay, mWin, 0, 0);
  348. XSetFont(mDisplay, mGC, mFS->fid);
  349. // set input mask
  350. XSelectInput(mDisplay, mWin,
  351. ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
  352. // set wm protocols in case they hit X
  353. Atom wm_delete_window =
  354. XInternAtom(mDisplay, "WM_DELETE_WINDOW", False);
  355. Atom wm_protocols =
  356. XInternAtom(mDisplay, "WM_PROTOCOLS", False);
  357. XSetWMProtocols (mDisplay, mWin, &wm_delete_window, 1);
  358. // set pop up dialog hint
  359. XSetTransientForHint(mDisplay, mWin, mWin);
  360. // set title
  361. XTextProperty wtitle;
  362. wtitle.value = (unsigned char *)mTitle;
  363. wtitle.encoding = XA_STRING;
  364. wtitle.format = 8;
  365. wtitle.nitems = strlen(mTitle);
  366. XSetWMName(mDisplay, mWin, &wtitle);
  367. // show window
  368. XMapRaised(mDisplay, mWin);
  369. // move it in case some bozo window manager repositioned it
  370. XMoveWindow(mDisplay, mWin,
  371. (mScreenWidth - mMBWidth) / 2, (mScreenHeight - mMBHeight) / 2);
  372. // raise it to top
  373. XRaiseWindow(mDisplay, mWin);
  374. XMessageBoxButton* clickedButton = NULL;
  375. XEvent event;
  376. Vector<XMessageBoxButton>::iterator iter;
  377. bool done = false;
  378. while (!done)
  379. {
  380. XNextEvent(mDisplay, &event);
  381. switch (event.type)
  382. {
  383. case Expose:
  384. repaint();
  385. break;
  386. case MotionNotify:
  387. for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
  388. iter->setMouseCoordinates(event.xmotion.x, event.xmotion.y);
  389. break;
  390. case ButtonPress:
  391. for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
  392. {
  393. if (iter->pointInRect(event.xbutton.x, event.xbutton.y))
  394. {
  395. iter->setMouseDown(true);
  396. iter->setMouseCoordinates(event.xbutton.x, event.xbutton.y);
  397. break;
  398. }
  399. }
  400. break;
  401. case ButtonRelease:
  402. for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
  403. {
  404. if (iter->pointInRect(event.xbutton.x, event.xbutton.y) &&
  405. iter->isMouseDown())
  406. {
  407. // we got a winner!
  408. clickedButton = iter;
  409. done = true;
  410. break;
  411. }
  412. }
  413. if (clickedButton == NULL)
  414. {
  415. // user released outside a button. clear the button states
  416. for (iter = mButtons.begin(); iter != mButtons.end(); ++iter)
  417. iter->setMouseDown(false);
  418. }
  419. break;
  420. case ClientMessage:
  421. if (event.xclient.message_type == wm_protocols &&
  422. event.xclient.data.l[0] == static_cast<long>(wm_delete_window))
  423. done = true;
  424. break;
  425. }
  426. repaint();
  427. }
  428. XUnmapWindow(mDisplay, mWin);
  429. XDestroyWindow(mDisplay, mWin);
  430. XFreeGC(mDisplay, mGC);
  431. XFreeFont(mDisplay, mFS);
  432. if (clickedButton != NULL)
  433. return clickedButton->getClickVal();
  434. else
  435. return -1;
  436. }