diff options
Diffstat (limited to 'unix/tx')
-rw-r--r-- | unix/tx/Makefile.in | 17 | ||||
-rw-r--r-- | unix/tx/TXButton.h | 124 | ||||
-rw-r--r-- | unix/tx/TXCheckbox.h | 142 | ||||
-rw-r--r-- | unix/tx/TXDialog.h | 101 | ||||
-rw-r--r-- | unix/tx/TXEntry.h | 191 | ||||
-rw-r--r-- | unix/tx/TXImage.cxx | 353 | ||||
-rw-r--r-- | unix/tx/TXImage.h | 97 | ||||
-rw-r--r-- | unix/tx/TXLabel.h | 126 | ||||
-rw-r--r-- | unix/tx/TXMenu.cxx | 186 | ||||
-rw-r--r-- | unix/tx/TXMenu.h | 67 | ||||
-rw-r--r-- | unix/tx/TXMsgBox.h | 112 | ||||
-rw-r--r-- | unix/tx/TXScrollbar.cxx | 119 | ||||
-rw-r--r-- | unix/tx/TXScrollbar.h | 82 | ||||
-rw-r--r-- | unix/tx/TXViewport.cxx | 155 | ||||
-rw-r--r-- | unix/tx/TXViewport.h | 77 | ||||
-rw-r--r-- | unix/tx/TXWindow.cxx | 486 | ||||
-rw-r--r-- | unix/tx/TXWindow.h | 211 |
17 files changed, 2646 insertions, 0 deletions
diff --git a/unix/tx/Makefile.in b/unix/tx/Makefile.in new file mode 100644 index 00000000..89c30b17 --- /dev/null +++ b/unix/tx/Makefile.in @@ -0,0 +1,17 @@ + +SRCS = TXWindow.cxx TXScrollbar.cxx TXViewport.cxx TXImage.cxx TXMenu.cxx + +OBJS = $(SRCS:.cxx=.o) + +DIR_CPPFLAGS = -I$(top_srcdir) @X_CFLAGS@ # X_CFLAGS are really CPPFLAGS + +library = libtx.a + +all:: $(library) + +$(library): $(OBJS) + rm -f $(library) + $(AR) $(library) $(OBJS) + $(RANLIB) $(library) + +# followed by boilerplate.mk diff --git a/unix/tx/TXButton.h b/unix/tx/TXButton.h new file mode 100644 index 00000000..b7472797 --- /dev/null +++ b/unix/tx/TXButton.h @@ -0,0 +1,124 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXButton.h +// +// A TXButton is a clickable button with some text in it. The button must be +// big enough to contain the text - if not then it will be resized +// appropriately. +// + +#ifndef __TXBUTTON_H__ +#define __TXBUTTON_H__ + +#include "TXWindow.h" +#include <rfb/util.h> + +// TXButtonCallback's buttonActivate() method is called when a button is +// activated. +class TXButton; +class TXButtonCallback { +public: + virtual void buttonActivate(TXButton* button)=0; +}; + + +class TXButton : public TXWindow, public TXEventHandler { +public: + + TXButton(Display* dpy_, const char* text_, TXButtonCallback* cb_=0, + TXWindow* parent_=0, int w=1, int h=1) + : TXWindow(dpy_, w, h, parent_), cb(cb_), down(false), + disabled_(false) + { + setEventHandler(this); + setText(text_); + gc = XCreateGC(dpy, win(), 0, 0); + XSetFont(dpy, gc, defaultFont); + addEventMask(ExposureMask | ButtonPressMask | ButtonReleaseMask); + } + + virtual ~TXButton() { + XFreeGC(dpy, gc); + } + + // setText() changes the text in the button. + void setText(const char* text_) { + text.buf = rfb::strDup(text_); + int textWidth = XTextWidth(defaultFS, text.buf, strlen(text.buf)); + int textHeight = (defaultFS->ascent + defaultFS->descent); + int newWidth = __rfbmax(width(), textWidth + xPad*2 + bevel*2); + int newHeight = __rfbmax(height(), textHeight + yPad*2 + bevel*2); + if (width() < newWidth || height() < newHeight) { + resize(newWidth, newHeight); + } + } + + // disabled() sets or queries the disabled state of the checkbox. A disabled + // checkbox cannot be changed via the user interface. + void disabled(bool b) { disabled_ = b; paint(); } + bool disabled() { return disabled_; } + +private: + + void paint() { + int tw = XTextWidth(defaultFS, text.buf, strlen(text.buf)); + int startx = (width() - tw) / 2; + int starty = (height() + defaultFS->ascent - defaultFS->descent) / 2; + if (down || disabled_) { + drawBevel(gc, 0, 0, width(), height(), bevel, defaultBg, darkBg,lightBg); + startx++; starty++; + } else { + drawBevel(gc, 0, 0, width(), height(), bevel, defaultBg, lightBg,darkBg); + } + + XSetForeground(dpy, gc, disabled_ ? disabledFg : defaultFg); + XDrawString(dpy, win(), gc, startx, starty, text.buf, strlen(text.buf)); + } + + virtual void handleEvent(TXWindow* w, XEvent* ev) { + switch (ev->type) { + case Expose: + paint(); + break; + case ButtonPress: + if (!disabled_) { + down = true; + paint(); + } + break; + case ButtonRelease: + if (!down) break; + down = false; + paint(); + if (ev->xbutton.x >= 0 && ev->xbutton.x < width() && + ev->xbutton.y >= 0 && ev->xbutton.y < height()) { + if (cb) cb->buttonActivate(this); + } + break; + } + } + + GC gc; + rfb::CharArray text; + TXButtonCallback* cb; + bool down; + bool disabled_; +}; + +#endif diff --git a/unix/tx/TXCheckbox.h b/unix/tx/TXCheckbox.h new file mode 100644 index 00000000..0880b38c --- /dev/null +++ b/unix/tx/TXCheckbox.h @@ -0,0 +1,142 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXCheckbox.h +// +// A TXCheckbox has a box which may be "checked" with some text next to it. +// The checkbox window must be big enough to contain the text - if not then it +// will be resized appropriately. +// +// There are two styles of checkbox: the normal style which uses a tick in a +// square box, and the radio style which uses a dot inside a circle. The +// default behaviour when clicking on the checkbox is to toggle it on or off, +// but this behaviour can be changed by the callback object. In particular to +// get radiobutton behaviour, the callback must ensure that only one of a set +// of radiobuttons is selected. +// + +#ifndef __TXCHECKBOX_H__ +#define __TXCHECKBOX_H__ + +#include "TXWindow.h" +#include <rfb/util.h> + +// TXCheckboxCallback's checkboxSelect() method is called when the state of a +// checkbox changes. +class TXCheckbox; +class TXCheckboxCallback { +public: + virtual void checkboxSelect(TXCheckbox* checkbox)=0; +}; + + +class TXCheckbox : public TXWindow, public TXEventHandler { +public: + TXCheckbox(Display* dpy_, const char* text_, TXCheckboxCallback* cb_, + bool radio_=false, TXWindow* parent_=0, int w=1, int h=1) + : TXWindow(dpy_, w, h, parent_), cb(cb_), text(0), + boxSize(radio_ ? 12 : 13), boxPad(4), + checked_(false), disabled_(false), radio(radio_) + { + setEventHandler(this); + setText(text_); + gc = XCreateGC(dpy, win(), 0, 0); + XSetFont(dpy, gc, defaultFont); + addEventMask(ExposureMask| ButtonPressMask | ButtonReleaseMask); + } + + virtual ~TXCheckbox() { + XFreeGC(dpy, gc); + if (text) free(text); + } + + // setText() changes the text in the checkbox. + void setText(const char* text_) { + if (text) free(text); + text = strdup((char*)text_); + int textWidth = XTextWidth(defaultFS, text, strlen(text)); + int textHeight = (defaultFS->ascent + defaultFS->descent); + int newWidth = __rfbmax(width(), textWidth + xPad*2 + boxPad*2 + boxSize); + int newHeight = __rfbmax(height(), textHeight + yPad*2); + if (width() < newWidth || height() < newHeight) { + resize(newWidth, newHeight); + } + } + + // checked() sets or queries the state of the checkbox + void checked(bool b) { checked_ = b; paint(); } + bool checked() { return checked_; } + + // disabled() sets or queries the disabled state of the checkbox. A disabled + // checkbox cannot be changed via the user interface. + void disabled(bool b) { disabled_ = b; paint(); } + bool disabled() { return disabled_; } + +private: + void paint() { + if (disabled_) + drawBevel(gc, xPad + boxPad, (height() - boxSize) / 2, boxSize, boxSize, + bevel, disabledBg, darkBg, lightBg, radio); + else + drawBevel(gc, xPad + boxPad, (height() - boxSize) / 2, boxSize, boxSize, + bevel, enabledBg, darkBg, lightBg, radio); + XSetBackground(dpy, gc, disabled_ ? disabledBg : enabledBg); + XSetForeground(dpy, gc, disabled_ ? disabledFg : defaultFg); + if (checked_) { + Pixmap icon = radio ? dot : tick; + int iconSize = radio ? dotSize : tickSize; + XCopyPlane(dpy, icon, win(), gc, 0, 0, iconSize, iconSize, + xPad + boxPad + (boxSize - iconSize) / 2, + (height() - iconSize) / 2, 1); + } + XDrawString(dpy, win(), gc, xPad + boxSize + boxPad*2, + (height() + defaultFS->ascent - defaultFS->descent) / 2, + text, strlen(text)); + } + + virtual void handleEvent(TXWindow* w, XEvent* ev) { + switch (ev->type) { + case Expose: + paint(); + break; + case ButtonPress: + break; + case ButtonRelease: + if (ev->xbutton.x >= 0 && ev->xbutton.x < width() && + ev->xbutton.y >= 0 && ev->xbutton.y < height()) { + if (!disabled_) { + checked_ = !checked_; + if (cb) cb->checkboxSelect(this); + paint(); + } + } + break; + } + } + + TXCheckboxCallback* cb; + GC gc; + char* text; + int boxSize; + int boxPad; + bool checked_; + bool disabled_; + bool radio; +}; + +#endif diff --git a/unix/tx/TXDialog.h b/unix/tx/TXDialog.h new file mode 100644 index 00000000..c8d601c3 --- /dev/null +++ b/unix/tx/TXDialog.h @@ -0,0 +1,101 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXDialog.h +// +// A TXDialog is a pop-up dialog window. The dialog can be made visible by +// calling its show() method. Dialogs can be modal or non-modal. For a modal +// dialog box, the show() method only returns when the dialog box has been +// dismissed. For a non-modal dialog box, the show() method returns +// immediately. +// + +#ifndef __TXDIALOG_H__ +#define __TXDIALOG_H__ + +#include "TXWindow.h" +#include <errno.h> + +// XXX Lynx/OS 2.3: protos for bzero(), select() +#ifdef Lynx +#include <sys/proto.h> +#endif + +class TXDialog : public TXWindow, public TXDeleteWindowCallback { +public: + TXDialog(Display* dpy, int width, int height, const char* name, + bool modal_=false) + : TXWindow(dpy, width, height), done(false), ok(false), modal(modal_) + { + toplevel(name, this); + resize(width, height); + } + + virtual ~TXDialog() {} + + // show() makes the dialog visible. For a modal dialog box, this processes X + // events until the done flag has been set, after which it returns the value + // of the ok flag. For a non-modal dialog box it always returns true + // immediately. + bool show() { + ok = false; + done = false; + initDialog(); + raise(); + map(); + if (modal) { + while (true) { + TXWindow::handleXEvents(dpy); + if (done) { + return ok; + } + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(ConnectionNumber(dpy), &rfds); + int n = select(FD_SETSIZE, &rfds, 0, 0, 0); + if (n < 0) throw rdr::SystemException("select",errno); + } + } + return true; + } + + // initDialog() can be overridden in a derived class. Typically it is used + // to make sure that checkboxes have the right state, etc. + virtual void initDialog() {} + + // resize() is overidden here to re-center the dialog + void resize(int w, int h) { + TXWindow::resize(w,h); + int dpyWidth = WidthOfScreen(DefaultScreenOfDisplay(dpy)); + int dpyHeight = HeightOfScreen(DefaultScreenOfDisplay(dpy)); + setUSPosition((dpyWidth - width() - 10) / 2, (dpyHeight - height() - 30) / 2); + } + +protected: + virtual void deleteWindow(TXWindow* w) { + ok = false; + done = true; + unmap(); + } + + bool done; + bool ok; + bool modal; +}; + +#endif diff --git a/unix/tx/TXEntry.h b/unix/tx/TXEntry.h new file mode 100644 index 00000000..1785a5f5 --- /dev/null +++ b/unix/tx/TXEntry.h @@ -0,0 +1,191 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXEntry.h +// +// A TXEntry allows you to enter a single line of text in a window. The entry +// must be tall enough to contain a line of text - if not then it will be +// resized appropriately. If the passwd argument to the constructor is true, +// then the text in the entry will be replaced by asterisks on the screen. +// + +#ifndef __TXENTRY_H__ +#define __TXENTRY_H__ + +#include "TXWindow.h" +#include <X11/keysym.h> + +#ifndef XK_ISO_Left_Tab +#define XK_ISO_Left_Tab 0xFE20 +#endif +#ifndef XK_KP_Delete +#define XK_KP_Delete 0xFF9F +#endif + +// TXEntryCallback's entryCallback() method is called when one of three special +// key presses have happened: Enter/Return, forward tab, or backward tab. +class TXEntry; +class TXEntryCallback { +public: + enum Detail { ENTER, NEXT_FOCUS, PREV_FOCUS }; + virtual void entryCallback(TXEntry* entry, Detail detail, Time time)=0; +}; + + +class TXEntry : public TXWindow, public TXEventHandler { +public: + + TXEntry(Display* dpy_, TXEntryCallback* cb_=0, + TXWindow* parent_=0, bool passwd_=false, int w=1, int h=1) + : TXWindow(dpy_, w, h, parent_), cb(cb_), + passwd(passwd_), disabled_(false), gotFocus(false) + { + setEventHandler(this); + gc = XCreateGC(dpy, win(), 0, 0); + addEventMask(ExposureMask | KeyPressMask | FocusChangeMask + | ButtonPressMask); + text[0] = 0; + int textHeight = (defaultFS->ascent + defaultFS->descent); + int newHeight = __rfbmax(height(), textHeight + yPad*2 + bevel*2); + if (height() < newHeight) { + resize(width(), newHeight); + } + } + + virtual ~TXEntry() { + XFreeGC(dpy, gc); + // overwrite memory used to store password - not critical, but can avoid + // accidental exposure of a password in uninitialised memory. + if (passwd) + memset(text, 0, maxLen); + } + + // getText() gets the text in the entry. + const char* getText() { return text; } + + // setText() sets the text in the entry. + void setText(const char* text_) { + strncpy(text, text_, maxLen-1); + text[maxLen-1] = 0; + paint(); + } + + // disabled() sets or queries the disabled state of the entry. A disabled + // entry cannot have text entered into it. + void disabled(bool b) { disabled_ = b; paint(); } + bool disabled() { return disabled_; } + +private: + void paint() { + if (disabled_) + drawBevel(gc, 0, 0, width(), height(), bevel, disabledBg,darkBg,lightBg); + else + drawBevel(gc, 0, 0, width(), height(), bevel, enabledBg, darkBg,lightBg); + char* str = text; + char stars[maxLen]; + if (passwd) { + int i; + for (i = 0; i < (int)strlen(text); i++) stars[i] = '*'; + stars[i] = 0; + str = stars; + } + int tw = XTextWidth(defaultFS, str, strlen(str)); + int startx = bevel + xPad; + if (startx + tw > width() - 2*bevel) { + startx = width() - 2*bevel - tw; + } + XDrawString(dpy, win(), defaultGC, startx, + (height() + defaultFS->ascent - defaultFS->descent) / 2, + str, strlen(str)); + if (!disabled_ && gotFocus) + XDrawLine(dpy, win(), defaultGC, startx+tw, + (height() - defaultFS->ascent - defaultFS->descent) / 2, + startx+tw, + (height() + defaultFS->ascent + defaultFS->descent) / 2); + } + + virtual void handleEvent(TXWindow* w, XEvent* ev) { + switch (ev->type) { + case Expose: + paint(); + break; + + case FocusIn: + gotFocus = true; + paint(); + break; + + case FocusOut: + gotFocus = false; + paint(); + break; + + case ButtonPress: + if (!disabled_) + XSetInputFocus(dpy, win(), RevertToParent, ev->xbutton.time); + break; + + case KeyPress: + { + if (disabled_ || !gotFocus) break; + KeySym keysym; + XComposeStatus compose; + char buf[10]; + int count = XLookupString(&ev->xkey, buf, 10, &keysym, &compose); + if (count >= 1 && buf[0] >= ' ' && buf[0] <= '~') { + if (strlen(text) + count >= maxLen) { + XBell(dpy, 0); + } else { + strncat(text, buf, count); + paint(); + } + } else if (keysym == XK_BackSpace || keysym == XK_Delete || + keysym == XK_KP_Delete) { + if (strlen(text) > 0) { + text[strlen(text)-1] = 0; + paint(); + } + } else if (keysym == XK_Return || keysym == XK_KP_Enter || + keysym == XK_Linefeed) { + if (cb) cb->entryCallback(this, TXEntryCallback::ENTER, + ev->xkey.time); + } else if ((keysym == XK_Tab || keysym == XK_KP_Tab) + && !(ev->xkey.state & ShiftMask)) + { + if (cb) cb->entryCallback(this, TXEntryCallback::NEXT_FOCUS, + ev->xkey.time); + } else if (((keysym == XK_Tab || keysym == XK_KP_Tab) + && (ev->xkey.state & ShiftMask)) + || keysym == XK_ISO_Left_Tab) + { + if (cb) cb->entryCallback(this, TXEntryCallback::PREV_FOCUS, + ev->xkey.time); + } + } + } + } + GC gc; + enum { maxLen = 256 }; + char text[maxLen]; + TXEntryCallback* cb; + bool passwd; + bool disabled_; + bool gotFocus; +}; + +#endif diff --git a/unix/tx/TXImage.cxx b/unix/tx/TXImage.cxx new file mode 100644 index 00000000..5fa68288 --- /dev/null +++ b/unix/tx/TXImage.cxx @@ -0,0 +1,353 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXImage.cxx +// + + +#include <stdio.h> +#include <strings.h> +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <list> +#include <rfb/TransImageGetter.h> +#include <rfb/Exception.h> +#include <rfb/LogWriter.h> +#include "TXWindow.h" +#include "TXImage.h" + +using namespace rfb; + +static rfb::LogWriter vlog("TXImage"); + +TXImage::TXImage(Display* d, int width, int height, Visual* vis_, int depth_) + : xim(0), dpy(d), vis(vis_), depth(depth_), tig(0), cube(0) +{ +#ifdef HAVE_MITSHM + shminfo = 0; +#endif + width_ = width; + height_ = height; + for (int i = 0; i < 256; i++) + colourMap[i].r = colourMap[i].g = colourMap[i].b = 0; + + if (!vis) + vis = DefaultVisual(dpy,DefaultScreen(dpy)); + if (!depth) + depth = DefaultDepth(dpy,DefaultScreen(dpy)); + + createXImage(); + getNativePixelFormat(vis, depth); + colourmap = this; + format.bpp = 0; // just make it different to any valid format, so that... + setPF(nativePF); // ...setPF() always works +} + +TXImage::~TXImage() +{ + if (data != (rdr::U8*)xim->data) delete [] data; + destroyXImage(); + delete tig; + delete cube; +} + +void TXImage::resize(int w, int h) +{ + if (w == width() && h == height()) return; + + int oldStrideBytes = getStride() * (format.bpp/8); + int rowsToCopy = __rfbmin(h, height()); + int bytesPerRow = __rfbmin(w, width()) * (format.bpp/8); + rdr::U8* oldData = 0; + bool allocData = false; + + if (data != (rdr::U8*)xim->data) { + oldData = (rdr::U8*)data; + allocData = true; + } else { + oldData = new rdr::U8[xim->bytes_per_line * height()]; + memcpy(oldData, xim->data, xim->bytes_per_line * height()); + } + + destroyXImage(); + width_ = w; + height_ = h; + createXImage(); + + if (allocData) + data = new rdr::U8[width() * height() * (format.bpp/8)]; + else + data = (rdr::U8*)xim->data; + + int newStrideBytes = getStride() * (format.bpp/8); + for (int i = 0; i < rowsToCopy; i++) + memcpy((rdr::U8*)data + newStrideBytes * i, oldData + oldStrideBytes * i, + bytesPerRow); + delete [] oldData; +} + +void TXImage::setPF(const PixelFormat& newPF) +{ + if (newPF.equal(format)) return; + format = newPF; + + if (data != (rdr::U8*)xim->data) delete [] data; + delete tig; + tig = 0; + + if (format.equal(nativePF) && format.trueColour) { + data = (rdr::U8*)xim->data; + } else { + data = new rdr::U8[width() * height() * (format.bpp/8)]; + tig = new TransImageGetter(); + tig->init(this, nativePF, 0, cube); + } +} + +int TXImage::getStride() const +{ + if (data == (rdr::U8*)xim->data) + return xim->bytes_per_line / (xim->bits_per_pixel / 8); + else + return width(); +} + +void TXImage::put(Window win, GC gc, const rfb::Rect& r) +{ + if (r.is_empty()) return; + int x = r.tl.x; + int y = r.tl.y; + int w = r.width(); + int h = r.height(); + if (data != (rdr::U8*)xim->data) { + rdr::U8* ximDataStart = ((rdr::U8*)xim->data + y * xim->bytes_per_line + + x * (xim->bits_per_pixel / 8)); + tig->getImage(ximDataStart, r, + xim->bytes_per_line / (xim->bits_per_pixel / 8)); + } +#ifdef HAVE_MITSHM + if (usingShm()) { + XShmPutImage(dpy, win, gc, xim, x, y, x, y, w, h, False); + return; + } +#endif + XPutImage(dpy, win, gc, xim, x, y, x, y, w, h); +} + +void TXImage::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs) +{ + for (int i = 0; i < nColours; i++) { + colourMap[firstColour+i].r = rgbs[i*3]; + colourMap[firstColour+i].g = rgbs[i*3+1]; + colourMap[firstColour+i].b = rgbs[i*3+2]; + } +} + +void TXImage::updateColourMap() +{ + tig->setColourMapEntries(0, 0, 0); +} + +void TXImage::lookup(int index, int* r, int* g, int* b) +{ + *r = colourMap[index].r; + *g = colourMap[index].g; + *b = colourMap[index].b; +} + +#ifdef HAVE_MITSHM +static bool caughtError = false; + +static int XShmAttachErrorHandler(Display *dpy, XErrorEvent *error) +{ + caughtError = true; + return 0; +} + +class TXImageCleanup { +public: + std::list<TXImage*> images; + ~TXImageCleanup() { + while (!images.empty()) + delete images.front(); + } +}; + +static TXImageCleanup imageCleanup; +#endif + +void TXImage::createXImage() +{ +#ifdef HAVE_MITSHM + int major, minor; + Bool pixmaps; + + if (XShmQueryVersion(dpy, &major, &minor, &pixmaps)) { + shminfo = new XShmSegmentInfo; + + xim = XShmCreateImage(dpy, vis, depth, ZPixmap, + 0, shminfo, width(), height()); + + if (xim) { + shminfo->shmid = shmget(IPC_PRIVATE, + xim->bytes_per_line * xim->height, + IPC_CREAT|0777); + + if (shminfo->shmid != -1) { + shminfo->shmaddr = xim->data = (char*)shmat(shminfo->shmid, 0, 0); + + if (shminfo->shmaddr != (char *)-1) { + + shminfo->readOnly = False; + + XErrorHandler oldHdlr = XSetErrorHandler(XShmAttachErrorHandler); + XShmAttach(dpy, shminfo); + XSync(dpy, False); + XSetErrorHandler(oldHdlr); + + if (!caughtError) { + vlog.debug("Using shared memory XImage"); + imageCleanup.images.push_back(this); + return; + } + + shmdt(shminfo->shmaddr); + } else { + vlog.error("shmat failed"); + perror("shmat"); + } + + shmctl(shminfo->shmid, IPC_RMID, 0); + } else { + vlog.error("shmget failed"); + perror("shmget"); + } + + XDestroyImage(xim); + xim = 0; + } else { + vlog.error("XShmCreateImage failed"); + } + + delete shminfo; + shminfo = 0; + } +#endif + + xim = XCreateImage(dpy, vis, depth, ZPixmap, + 0, 0, width(), height(), BitmapPad(dpy), 0); + + xim->data = (char*)malloc(xim->bytes_per_line * xim->height); + if (!xim->data) { + vlog.error("malloc failed"); + exit(1); + } +} + +void TXImage::destroyXImage() +{ +#ifdef HAVE_MITSHM + if (shminfo) { + vlog.debug("Freeing shared memory XImage"); + shmdt(shminfo->shmaddr); + shmctl(shminfo->shmid, IPC_RMID, 0); + delete shminfo; + shminfo = 0; + imageCleanup.images.remove(this); + } +#endif + // XDestroyImage() will free(xim->data) if appropriate + if (xim) XDestroyImage(xim); + xim = 0; +} + + +static bool supportedBPP(int bpp) { + return (bpp == 8 || bpp == 16 || bpp == 32); +} + +static int depth2bpp(Display* dpy, int depth) +{ + int nformats; + XPixmapFormatValues* format = XListPixmapFormats(dpy, &nformats); + + int i; + for (i = 0; i < nformats; i++) + if (format[i].depth == depth) break; + + if (i == nformats || !supportedBPP(format[i].bits_per_pixel)) + throw rfb::Exception("Error: couldn't find suitable pixmap format"); + + int bpp = format[i].bits_per_pixel; + XFree(format); + return bpp; +} + +void TXImage::getNativePixelFormat(Visual* vis, int depth) +{ + cube = 0; + nativePF.depth = depth; + nativePF.bpp = depth2bpp(dpy, depth); + nativePF.bigEndian = (ImageByteOrder(dpy) == MSBFirst); + nativePF.trueColour = (vis->c_class == TrueColor); + + vlog.info("Using default colormap and visual, %sdepth %d.", + (vis->c_class == TrueColor) ? "TrueColor, " : + ((vis->c_class == PseudoColor) ? "PseudoColor, " : ""), + depth); + + if (nativePF.trueColour) { + + nativePF.redShift = ffs(vis->red_mask) - 1; + nativePF.greenShift = ffs(vis->green_mask) - 1; + nativePF.blueShift = ffs(vis->blue_mask) - 1; + nativePF.redMax = vis->red_mask >> nativePF.redShift; + nativePF.greenMax = vis->green_mask >> nativePF.greenShift; + nativePF.blueMax = vis->blue_mask >> nativePF.blueShift; + + } else { + + XColor xc[256]; + cube = new rfb::ColourCube(6,6,6); + int r; + for (r = 0; r < cube->nRed; r++) { + for (int g = 0; g < cube->nGreen; g++) { + for (int b = 0; b < cube->nBlue; b++) { + int i = (r * cube->nGreen + g) * cube->nBlue + b; + xc[i].red = r * 65535 / (cube->nRed-1); + xc[i].green = g * 65535 / (cube->nGreen-1); + xc[i].blue = b * 65535 / (cube->nBlue-1); + } + } + } + + TXWindow::getColours(dpy, xc, cube->size()); + + for (r = 0; r < cube->nRed; r++) { + for (int g = 0; g < cube->nGreen; g++) { + for (int b = 0; b < cube->nBlue; b++) { + int i = (r * cube->nGreen + g) * cube->nBlue + b; + cube->set(r, g, b, xc[i].pixel); + } + } + } + } +} diff --git a/unix/tx/TXImage.h b/unix/tx/TXImage.h new file mode 100644 index 00000000..055bd22d --- /dev/null +++ b/unix/tx/TXImage.h @@ -0,0 +1,97 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXImage.h +// +// A TXImage represents a rectangular off-screen image in any RFB pixel format. +// By default it will use the "native" pixel format for the screen, which will +// be an 8-bit colourmap unless the X display is TrueColor. The pixel format +// can be changed via the setPF() method. The pixel data is accessible via the +// data member inherited from FullFramePixelBuffer, or can be set via the +// fillRect(), imageRect(), copyRect() and maskRect() methods, also inherited +// from PixelBuffer. A rectangle of the image can be drawn into an X Window +// via the put() method. If using a colourmap, the setColourMapEntries() and +// updateColourMap() methods must be called to set up the colourmap as +// appropriate. + + +#ifndef __TXIMAGE_H__ +#define __TXIMAGE_H__ + +#include <X11/Xlib.h> +#include <rfb/PixelBuffer.h> +#include <rfb/ColourMap.h> +#include <rfb/ColourCube.h> +#ifdef HAVE_MITSHM +#include <X11/extensions/XShm.h> +#endif + +namespace rfb { class TransImageGetter; } + +class TXImage : public rfb::FullFramePixelBuffer, public rfb::ColourMap { +public: + TXImage(Display* dpy, int width, int height, Visual* vis=0, int depth=0); + ~TXImage(); + + // resize() resizes the image, preserving the image data where possible. + void resize(int w, int h); + + // put causes the given rectangle to be drawn onto the given window. + void put(Window win, GC gc, const rfb::Rect& r); + + // setColourMapEntries() changes some of the entries in the colourmap. + // However these settings won't take effect until updateColourMap() is + // called. This is because recalculating the internal translation table can + // be expensive. + void setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs); + void updateColourMap(); + +#ifdef HAVE_MITSHM + bool usingShm() { return shminfo; } +#else + bool usingShm() { return 0; } +#endif + + // PixelBuffer methods + // width(), height(), getPF() etc are inherited from PixelBuffer + virtual void setPF(const rfb::PixelFormat& pf); + virtual int getStride() const; + +private: + + // ColourMap method + virtual void lookup(int index, int* r, int* g, int* b); + + void createXImage(); + void destroyXImage(); + void getNativePixelFormat(Visual* vis, int depth); + + XImage* xim; + Display* dpy; + Visual* vis; + int depth; +#ifdef HAVE_MITSHM + XShmSegmentInfo* shminfo; +#endif + rfb::TransImageGetter* tig; + rfb::Colour colourMap[256]; + rfb::PixelFormat nativePF; + rfb::ColourCube* cube; +}; + +#endif diff --git a/unix/tx/TXLabel.h b/unix/tx/TXLabel.h new file mode 100644 index 00000000..3d5200d6 --- /dev/null +++ b/unix/tx/TXLabel.h @@ -0,0 +1,126 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXLabel.h +// +// An TXLabel allows you to put up multiline text in a window with various +// alignments. The label must be big enough to contain the text - if not then +// it will be resized appropriately. +// + +#ifndef __TXLABEL_H__ +#define __TXLABEL_H__ + +#include <stdlib.h> +#include "TXWindow.h" +#include <rfb/util.h> + +class TXLabel : public TXWindow, public TXEventHandler { +public: + enum HAlign { left, centre, right }; + enum VAlign { top, middle, bottom }; + + TXLabel(Display* dpy_, const char* text_, TXWindow* parent_=0, + int w=1, int h=1, HAlign ha=centre, VAlign va=middle) + : TXWindow(dpy_, w, h, parent_), lineSpacing(2), lines(0), + halign(ha), valign(va) + { + setEventHandler(this); + setText(text_); + addEventMask(ExposureMask); + } + + // setText() changes the text in the label. + void setText(const char* text_) { + text.buf = rfb::strDup(text_); + lines = 0; + int lineStart = 0; + int textWidth = 0; + int i = -1; + do { + i++; + if (text.buf[i] == '\n' || text.buf[i] == 0) { + int tw = XTextWidth(defaultFS, &text.buf[lineStart], i-lineStart); + if (tw > textWidth) textWidth = tw; + lineStart = i+1; + lines++; + } + } while (text.buf[i] != 0); + int textHeight = ((defaultFS->ascent + defaultFS->descent + lineSpacing) + * lines); + int newWidth = __rfbmax(width(), textWidth + xPad*2); + int newHeight = __rfbmax(height(), textHeight + yPad*2); + if (width() < newWidth || height() < newHeight) { + resize(newWidth, newHeight); + } + invalidate(); + } + +private: + int xOffset(int textWidth) { + switch (halign) { + case left: return xPad; + case right: return width() - xPad - textWidth; + default: return (width() - textWidth) / 2; + } + } + + int yOffset(int lineNum) { + int textHeight = ((defaultFS->ascent + defaultFS->descent + lineSpacing) + * lines); + int lineOffset = ((defaultFS->ascent + defaultFS->descent + lineSpacing) + * lineNum + defaultFS->ascent); + switch (valign) { + case top: return yPad + lineOffset; + case bottom: return height() - yPad - textHeight + lineOffset; + default: return (height() - textHeight) / 2 + lineOffset; + } + } + + void paint() { + int lineNum = 0; + int lineStart = 0; + int i = -1; + do { + i++; + if (text.buf[i] == '\n' || text.buf[i] == 0) { + int tw = XTextWidth(defaultFS, &text.buf[lineStart], i-lineStart); + XDrawString(dpy, win(), defaultGC, xOffset(tw), yOffset(lineNum), + &text.buf[lineStart], i-lineStart); + lineStart = i+1; + lineNum++; + } + } while (text.buf[i] != 0); + } + + virtual void handleEvent(TXWindow* w, XEvent* ev) { + switch (ev->type) { + case Expose: + paint(); + break; + } + } + + int lineSpacing; + rfb::CharArray text; + int lines; + HAlign halign; + VAlign valign; +}; + +#endif diff --git a/unix/tx/TXMenu.cxx b/unix/tx/TXMenu.cxx new file mode 100644 index 00000000..92712f55 --- /dev/null +++ b/unix/tx/TXMenu.cxx @@ -0,0 +1,186 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXMenu.cxx +// + +#include "TXMenu.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <rfb/util.h> +#include <X11/keysym.h> + +TXMenu::TXMenu(Display* dpy_, TXMenuCallback* cb_, int w, int h, + TXWindow* parent_) + : TXWindow(dpy_, w, h, parent_), cb(cb_), nEntries(0), + highlight(-1) +{ + setEventHandler(this); + gc = XCreateGC(dpy, win(), 0, 0); + addEventMask(ExposureMask | ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | EnterWindowMask | LeaveWindowMask); +} + +TXMenu::~TXMenu() +{ + XFreeGC(dpy, gc); + for (int i = 0; i < nEntries; i++) + delete [] text[i]; +} + +inline int TXMenu::entryHeight(int i) +{ + if (text[i]) + return defaultFS->ascent + defaultFS->descent + bevel*2 + yPad*2; + else + return yPad*2 + 1; +} + +void TXMenu::addEntry(const char* text_, long id_) +{ + assert(nEntries < maxEntries); + text[nEntries] = rfb::strDup(text_); + checked[nEntries] = false; + id[nEntries++] = id_; + int tw = 0; + if (text_) + tw = XTextWidth(defaultFS, text_, strlen(text_)); + int newWidth = width(); + if (tw + bevel*2 + xPad*5 + tickSize > width()) + newWidth = tw + bevel*2 + xPad*5 + tickSize; + int newHeight = 0; + for (int i = 0; i < nEntries; i++) + newHeight += entryHeight(i); + resize(newWidth, newHeight); +} + +void TXMenu::check(long id_, bool checked_) +{ + for (int i = 0; i < nEntries; i++) { + if (id[i] == id_) { + checked[i] = checked_; + break; + } + } +} + +void TXMenu::paint() +{ + int y = 0; + for (int i = 0; i < nEntries; i++) { + if (text[i]) { + if (i == highlight) + drawBevel(gc, 0, y, width(), entryHeight(i), bevel, + defaultBg, darkBg, lightBg); + else + XClearArea(dpy, win(), 0, y, width(), entryHeight(i), false); + if (checked[i]) + XCopyPlane(dpy, tick, win(), defaultGC, 0, 0, tickSize, tickSize, + bevel + xPad, + y + bevel + yPad + defaultFS->ascent - tickSize, 1); + + XDrawImageString(dpy, win(), defaultGC, bevel + xPad*2 + tickSize, + y + bevel + yPad + defaultFS->ascent, + text[i], strlen(text[i])); + } else { + XDrawLine(dpy, win(), defaultGC, bevel + xPad, y + entryHeight(i) / 2, + width() - bevel - xPad, y + entryHeight(i) / 2); + } + y += entryHeight(i); + } +} + +void TXMenu::handleEvent(TXWindow* w, XEvent* ev) +{ + switch (ev->type) { + case Expose: + paint(); + break; + + case ButtonRelease: + { + int y = ev->xmotion.y; + int entryY = 0; + for (int i = 0; i < nEntries; i++) { + if (y >= entryY && y <= entryY + entryHeight(i)) { + if (cb && text[i]) + cb->menuSelect(id[i], this); + break; + } + entryY += entryHeight(i); + } + highlight = -1; + paint(); + break; + } + + case ButtonPress: + case MotionNotify: + { + int y = ev->xmotion.y; + int entryY = 0; + for (int i = 0; i < nEntries; i++) { + if (y >= entryY && y <= entryY + entryHeight(i)) { + if (highlight != i) { + highlight = i; + paint(); + } + break; + } + entryY += entryHeight(i); + } + break; + } + + case KeyPress: + { + KeySym ks; + char str[256]; + XLookupString(&ev->xkey, str, 256, &ks, NULL); + if (ks == XK_Escape) { + highlight = -1; + unmap(); + } else if (ks == XK_Down || ks == XK_Up) { + if (nEntries < 1) break; + if (highlight < 0) + highlight = (ks == XK_Down ? nEntries-1 : 0); + int start = highlight; + int inc = (ks == XK_Down ? 1 : nEntries-1); + do { + highlight = (highlight + inc) % nEntries; + } while (highlight != start && !text[highlight]); + paint(); + } else if (ks == XK_space || ks == XK_KP_Space || + ks == XK_Return || ks == XK_KP_Enter) { + if (cb && highlight >= 0 && text[highlight]) + cb->menuSelect(id[highlight], this); + highlight = -1; + paint(); + } + break; + } + + case EnterNotify: + case LeaveNotify: + highlight = -1; + paint(); + break; + } +} diff --git a/unix/tx/TXMenu.h b/unix/tx/TXMenu.h new file mode 100644 index 00000000..d0245ee9 --- /dev/null +++ b/unix/tx/TXMenu.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXMenu.h +// +// A TXMenu consists of multiple entries which can be added one at a time. +// Each entry consists of some text, and has an associated integer identifier. +// A callback is made when a menu entry is selected. +// + +#ifndef __TXMENU_H__ +#define __TXMENU_H__ + +#include "TXWindow.h" + +// TXMenuCallback's menuSelect() method is called when a particular menu entry +// is selected. The id argument identifies the menu entry. +class TXMenu; +class TXMenuCallback { +public: + virtual void menuSelect(long id, TXMenu* menu)=0; +}; + +class TXMenu : public TXWindow, public TXEventHandler { +public: + TXMenu(Display* dpy_, TXMenuCallback* cb=0, int width=1, int height=1, + TXWindow* parent_=0); + virtual ~TXMenu(); + + // addEntry() adds an entry to the end of the menu with the given text and + // identifier. + void addEntry(const char* text, long id); + + // check() sets whether the given menu entry should have a tick next to it. + void check(long id, bool checked); + +private: + int entryHeight(int i); + virtual void handleEvent(TXWindow* w, XEvent* ev); + void paint(); + + GC gc; + TXMenuCallback* cb; + enum { maxEntries = 64 }; + char* text[maxEntries]; + long id[maxEntries]; + bool checked[maxEntries]; + int nEntries; + int highlight; +}; + +#endif diff --git a/unix/tx/TXMsgBox.h b/unix/tx/TXMsgBox.h new file mode 100644 index 00000000..ff84e6ae --- /dev/null +++ b/unix/tx/TXMsgBox.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXMsgBox.h +// +// A TXMsgBox is a specialised pop-up dialog window, designed to present +// the user with a small amount of textual information, and potentially to +// obtain their response. +// TXMsgBoxes are always modal, and may have an Ok button, Ok+Cancel buttons, +// or Yes+No buttons. +// The MsgBox helper function creates a TXMsgBox on the fly, runs it, and +// returns the result. +// + +#ifndef __TXMSGBOX_H__ +#define __TXMSGBOX_H__ + +#include "TXDialog.h" +#include "TXLabel.h" +#include "TXButton.h" + +enum TXMsgBoxFlags { + MB_OK = 0, + MB_OKCANCEL = 1, + MB_YESNO = 4, + MB_ICONERROR = 0x10, + MB_ICONQUESTION = 0x20, + MB_ICONWARNING = 0x30, + MB_ICONINFORMATION = 0x40, + MB_DEFBUTTON1 = 0, + MB_DEFBUTTON2 = 0x100 +}; + +class TXMsgBox : public TXDialog, public TXButtonCallback { +public: + TXMsgBox(Display* dpy, const char* text, unsigned int flags, const char* title=0) + : TXDialog(dpy, 1, 1, "Message", true), + textLabel(dpy, "", this), + okButton(dpy, "OK", this, this, 60), + cancelButton(dpy, "Cancel", this, this, 60) + { + textLabel.xPad = 8; + textLabel.move(0, yPad*4); + textLabel.setText(text); + resize(textLabel.width(), + textLabel.height() + okButton.height() + yPad*12); + + switch (flags & 0x30) { + case MB_ICONERROR: + toplevel("Error", this); break; + case MB_ICONQUESTION: + toplevel("Question", this); break; + case MB_ICONWARNING: + toplevel("Warning", this); break; + case MB_ICONINFORMATION: + toplevel("Information", this); break; + default: + if (title) + toplevel(title, this); + break; + }; + + switch (flags & 0x7) { + default: + okButton.move((width() - okButton.width()) / 2, + height() - yPad*4 - okButton.height()); + cancelButton.unmap(); + break; + case MB_OKCANCEL: + case MB_YESNO: + + okButton.move(((width()/2) - okButton.width()) / 2, + height() - yPad*4 - okButton.height()); + cancelButton.move(((width()*3/2) - cancelButton.width()) / 2, + height() - yPad*4 - cancelButton.height()); + if ((flags & 0x7) == MB_YESNO) { + okButton.setText("Yes"); + cancelButton.setText("No"); + } + break; + }; + + setBorderWidth(1); + } + + virtual void buttonActivate(TXButton* b) { + ok = (b == &okButton); + done = true; + unmap(); + } + + TXLabel textLabel; + TXButton okButton; + TXButton cancelButton; +}; + +#endif diff --git a/unix/tx/TXScrollbar.cxx b/unix/tx/TXScrollbar.cxx new file mode 100644 index 00000000..47e124c3 --- /dev/null +++ b/unix/tx/TXScrollbar.cxx @@ -0,0 +1,119 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXScrollbar.cxx +// + +#include "TXScrollbar.h" +#include <stdio.h> +#include <assert.h> + +TXScrollbar::TXScrollbar(Display* dpy_, int width, int height, bool vert, + TXScrollbarCallback* cb_, TXWindow* parent_) + : TXWindow(dpy_, width, height, parent_), cb(cb_), vertical(vert), + clickedInThumb(false) +{ + setEventHandler(this); + gc = XCreateGC(dpy, win(), 0, 0); + addEventMask(ExposureMask | ButtonPressMask | ButtonReleaseMask | + ButtonMotionMask); + setBg(scrollbarBg); + limit[0] = len[0] = limit[1] = len[1] = 1; + start[0] = start[1] = 0; +} + +TXScrollbar::~TXScrollbar() +{ + XFreeGC(dpy, gc); +} + +void TXScrollbar::set(int limit_, int start_, int len_, bool vert) +{ + assert(limit_ > 0 && len_ >= 0 && len_ <= limit_); + + if (start_ < 0) start_ = 0; + if (start_ > limit_ - len_) start_ = limit_ - len_; + + if (limit[vert] != limit_ || start[vert] != start_ || len[vert] != len_) { + limit[vert] = limit_; + start[vert] = start_; + len[vert] = len_; + paint(); + } +} + +void TXScrollbar::paint() +{ + int x = scaleToBarX(start[0]); + int y = scaleToBarY(start[1]); + int w = scaleToBarX(len[0]); + int h = scaleToBarY(len[1]); + if (y > 0) XClearArea(dpy, win(), 0, 0, 0, y, false); + if (x > 0) XClearArea(dpy, win(), 0, y, x, y+h, false); + XClearArea(dpy, win(), x+w, y, 0, y+h, false); + XClearArea(dpy, win(), 0, y+h, 0, 0, false); + drawBevel(gc, x, y, w, h, bevel, defaultBg, lightBg, darkBg); +} + +void TXScrollbar::handleEvent(TXWindow* w, XEvent* ev) +{ + switch (ev->type) { + case Expose: + paint(); + break; + + case ButtonPress: + { + xDown = ev->xbutton.x; + yDown = ev->xbutton.y; + xStart = start[0]; + yStart = start[1]; + bool clickedInThumbX = false; + if (xDown < scaleToBarX(start[0])) { + set(limit[0], start[0] - len[0], len[0], false); + } else if (xDown >= scaleToBarX(start[0]+len[0])) { + set(limit[0], start[0] + len[0], len[0], false); + } else { + clickedInThumbX = true; + } + bool clickedInThumbY = false; + if (yDown < scaleToBarY(start[1])) { + set(limit[1], start[1] - len[1], len[1], true); + } else if (yDown >= scaleToBarY(start[1]+len[1])) { + set(limit[1], start[1] + len[1], len[1], true); + } else { + clickedInThumbY = true; + } + clickedInThumb = clickedInThumbX && clickedInThumbY; + if (cb) cb->scrollbarPos(start[0], start[1], this); + } + break; + + case ButtonRelease: + case MotionNotify: + while (XCheckTypedWindowEvent(dpy, win(), MotionNotify, ev)); + if (clickedInThumb) { + int dx = ev->xmotion.x - xDown; + int dy = ev->xmotion.y - yDown; + set(limit[0], xStart + barToScaleX(dx), len[0], false); + set(limit[1], yStart + barToScaleY(dy), len[1], true); + if (cb) cb->scrollbarPos(start[0], start[1], this); + } + break; + } +} diff --git a/unix/tx/TXScrollbar.h b/unix/tx/TXScrollbar.h new file mode 100644 index 00000000..87fec3d8 --- /dev/null +++ b/unix/tx/TXScrollbar.h @@ -0,0 +1,82 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXScrollbar.h +// +// A TXScrollbar represents a range of values starting at start, of length len, +// between zero and limit. The vertical argument to the constructor says +// whether the scrollbar is horizontal or vertical. +// +// In fact it can represent a range in each dimension but usually one of the +// dimensions is fixed, according to the vertical flag (for a vertical +// scrollbar, the horizontal dimension is fixed, and vice-versa). +// +// The TXScrollbarCallback argument is an object which will be notified when +// the user has attempted to move the scrollbar. The x and y arguments to the +// scrollbarPos() method give the start values in the respective dimensions. +// They are guaranteed to be between 0 and limit-len. +// + +#ifndef __TXSCROLLBAR_H__ +#define __TXSCROLLBAR_H__ + +#include "TXWindow.h" + +class TXScrollbarCallback; + +class TXScrollbar : public TXWindow, public TXEventHandler { +public: + TXScrollbar(Display* dpy_, int width=1, int height=1, bool vertical=false, + TXScrollbarCallback* cb=0, TXWindow* parent_=0); + virtual ~TXScrollbar(); + + // set() sets the limit, start and length of the range represented by the + // scrollbar. The values of limit and len passed in must be valid + // (i.e. limit > 0 and 0 <= len <= limit). Values of start are clipped to + // the range 0 to limit-len. + void set(int limit, int start, int len) { set(limit, start, len, vertical); } + + // set() with an extra argument vert can be used to represent a range in both + // dimensions simultaneously. + void set(int limit, int start, int len, bool vert); + + virtual void handleEvent(TXWindow* w, XEvent* ev); + +private: + int scaleToBarX(int x) { return (x * width() + limit[0]/2) / limit[0]; } + int scaleToBarY(int y) { return (y * height() + limit[1]/2) / limit[1]; } + int barToScaleX(int x) { return (x * limit[0] + width()/2) / width(); } + int barToScaleY(int y) { return (y * limit[1] + height()/2) / height(); } + void paint(); + + GC gc; + TXScrollbarCallback* cb; + int limit[2]; + int start[2]; + int len[2]; + int xDown, yDown; + int xStart, yStart; + bool vertical; + bool clickedInThumb; +}; + +class TXScrollbarCallback { +public: + virtual void scrollbarPos(int x, int y, TXScrollbar* sb)=0; +}; +#endif diff --git a/unix/tx/TXViewport.cxx b/unix/tx/TXViewport.cxx new file mode 100644 index 00000000..abe51734 --- /dev/null +++ b/unix/tx/TXViewport.cxx @@ -0,0 +1,155 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXViewport.cxx +// + +#include "TXViewport.h" +#include <stdio.h> + +TXViewport::TXViewport(Display* dpy_, int w, int h, TXWindow* parent_) + : TXWindow(dpy_, w, h, parent_), child(0), hScrollbar(0), + vScrollbar(0), scrollbarSize(15), xOff(0), yOff(0), bumpScrollTimer(this), + bumpScroll(false), needScrollbars(false), bumpScrollX(0), bumpScrollY(0) +{ + clipper = new TXWindow(dpy, width()-scrollbarSize, height()-scrollbarSize, + this); + clipper->setBg(black); + hScrollbar = new TXScrollbar(dpy, width()-scrollbarSize, scrollbarSize, + false, this, this); + vScrollbar = new TXScrollbar(dpy, scrollbarSize, height()-scrollbarSize, + true, this, this); +} + +TXViewport::~TXViewport() +{ + delete clipper; + delete hScrollbar; + delete vScrollbar; +} + +void TXViewport::setChild(TXWindow* child_) +{ + child = child_; + XReparentWindow(dpy, child->win(), clipper->win(), 0, 0); + xOff = yOff = 0; + child->map(); + resizeNotify(); +} + +bool TXViewport::setOffset(int x, int y) +{ + if (clipper->width() >= child->width()) { + x = (clipper->width() - child->width()) / 2; + } else { + if (x > 0) x = 0; + if (x + child->width() < clipper->width()) + x = clipper->width() - child->width(); + } + + if (clipper->height() >= child->height()) { + y = (clipper->height() - child->height()) / 2; + } else { + if (y > 0) y = 0; + if (y + child->height() < clipper->height()) + y = clipper->height() - child->height(); + } + + if (x != xOff || y != yOff) { + xOff = x; + yOff = y; + child->move(xOff, yOff); + return true; + } + + return false; +} + +void TXViewport::setBumpScroll(bool b) +{ + bumpScroll = b; + resizeNotify(); +} + +// Note: bumpScrollEvent() only works if the viewport is positioned at 0,0 and +// is the same width and height as the screen. +bool TXViewport::bumpScrollEvent(XMotionEvent* ev) +{ + if (!bumpScroll) return false; + int bumpScrollPixels = 20; + bumpScrollX = bumpScrollY = 0; + + if (ev->x_root == width()-1) bumpScrollX = -bumpScrollPixels; + else if (ev->x_root == 0) bumpScrollX = bumpScrollPixels; + if (ev->y_root == height()-1) bumpScrollY = -bumpScrollPixels; + else if (ev->y_root == 0) bumpScrollY = bumpScrollPixels; + + if (bumpScrollX || bumpScrollY) { + if (bumpScrollTimer.isStarted()) return true; + if (setOffset(xOff + bumpScrollX, yOff + bumpScrollY)) { + bumpScrollTimer.start(25); + return true; + } + } + + bumpScrollTimer.stop(); + return false; +} + +bool TXViewport::handleTimeout(rfb::Timer* timer) { + return setOffset(xOff + bumpScrollX, yOff + bumpScrollY); +} + +void TXViewport::resizeNotify() +{ + needScrollbars = (!bumpScroll && + (width() < child->width() || height() < child->height()) && + (width() > scrollbarSize && height() > scrollbarSize)); + if (needScrollbars) { + clipper->resize(width()-scrollbarSize, height()-scrollbarSize); + hScrollbar->map(); + vScrollbar->map(); + } else { + clipper->resize(width(), height()); + hScrollbar->unmap(); + vScrollbar->unmap(); + } + + setOffset(xOff, yOff); + + if (needScrollbars) { + hScrollbar->move(0, height()-scrollbarSize); + hScrollbar->resize(width()-scrollbarSize, scrollbarSize); + hScrollbar->set(child->width(), -xOff, width()-scrollbarSize); + vScrollbar->move(width()-scrollbarSize, 0); + vScrollbar->resize(scrollbarSize, height()-scrollbarSize); + vScrollbar->set(child->height(), -yOff, height()-scrollbarSize); + } +} + +void TXViewport::scrollbarPos(int x, int y, TXScrollbar* sb) +{ + if (sb == hScrollbar) { + x = -x; + y = yOff; + } else { + x = xOff; + y = -y; + } + setOffset(x, y); +} diff --git a/unix/tx/TXViewport.h b/unix/tx/TXViewport.h new file mode 100644 index 00000000..0c9857d2 --- /dev/null +++ b/unix/tx/TXViewport.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXViewport.h +// +// A TXViewport allows a large window to be viewed by adding scrollbars to the +// right and bottom if necessary. It also has a bump-scroll mode where there +// are no scrollbars, and scrolling is achieved by bumping up against the edge +// of the screen instead. Note that this only works when the viewport fills +// the entire screen. If the child window is smaller than the viewport, it is +// always positioned centrally in the viewport. + +#ifndef __TXVIEWPORT_H__ +#define __TXVIEWPORT_H__ + +#include <rfb/Timer.h> +#include "TXWindow.h" +#include "TXScrollbar.h" + +class TXViewport : public TXWindow, public TXScrollbarCallback, + public rfb::Timer::Callback { +public: + TXViewport(Display* dpy_, int width, int height, TXWindow* parent_=0); + virtual ~TXViewport(); + + // setChild() sets the child window which is to be viewed in the viewport. + void setChild(TXWindow* child_); + + // setOffset() sets the position of the child in the viewport. Note that the + // offsets are negative. For example when the offset is (-100,-30), position + // (100,30) in the child window is at the top-left of the viewport. The + // offsets given are clipped to keep the child window filling the viewport + // (except where the child window is smaller than the viewport, in which case + // it is always positioned centrally in the viewport). It returns true if + // the child was repositioned. + bool setOffset(int x, int y); + + // setBumpScroll() puts the viewport in bump-scroll mode. + void setBumpScroll(bool b); + + // bumpScrollEvent() can be called with a MotionNotify event which may + // potentially be against the edge of the screen. It returns true if the + // event was used for bump-scrolling, false if it should be processed + // normally. + bool bumpScrollEvent(XMotionEvent* ev); + +private: + virtual void resizeNotify(); + virtual void scrollbarPos(int x, int y, TXScrollbar* sb); + virtual bool handleTimeout(rfb::Timer* timer); + TXWindow* clipper; + TXWindow* child; + TXScrollbar* hScrollbar; + TXScrollbar* vScrollbar; + const int scrollbarSize; + int xOff, yOff; + rfb::Timer bumpScrollTimer; + bool bumpScroll; + bool needScrollbars; + int bumpScrollX, bumpScrollY; +}; +#endif diff --git a/unix/tx/TXWindow.cxx b/unix/tx/TXWindow.cxx new file mode 100644 index 00000000..6d211beb --- /dev/null +++ b/unix/tx/TXWindow.cxx @@ -0,0 +1,486 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXWindow.cxx +// + +#include <X11/Xatom.h> +#include "TXWindow.h" +#include <list> +#include <stdio.h> +#include <stdlib.h> +#include <rfb/util.h> + +std::list<TXWindow*> windows; + +Atom wmProtocols, wmDeleteWindow, wmTakeFocus; +Atom xaTIMESTAMP, xaTARGETS, xaSELECTION_TIME, xaSELECTION_STRING; +Atom xaCLIPBOARD; +unsigned long TXWindow::black, TXWindow::white; +unsigned long TXWindow::defaultFg, TXWindow::defaultBg; +unsigned long TXWindow::lightBg, TXWindow::darkBg; +unsigned long TXWindow::disabledFg, TXWindow::disabledBg; +unsigned long TXWindow::enabledBg; +unsigned long TXWindow::scrollbarBg; +Colormap TXWindow::cmap = 0; +GC TXWindow::defaultGC = 0; +Font TXWindow::defaultFont = 0; +XFontStruct* TXWindow::defaultFS = 0; +Time TXWindow::cutBufferTime = 0; +Pixmap TXWindow::dot = 0, TXWindow::tick = 0; +const int TXWindow::dotSize = 4, TXWindow::tickSize = 8; +char* TXWindow::defaultWindowClass; + +void TXWindow::init(Display* dpy, const char* defaultWindowClass_) +{ + cmap = DefaultColormap(dpy,DefaultScreen(dpy)); + wmProtocols = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmDeleteWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmTakeFocus = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + xaTIMESTAMP = XInternAtom(dpy, "TIMESTAMP", False); + xaTARGETS = XInternAtom(dpy, "TARGETS", False); + xaSELECTION_TIME = XInternAtom(dpy, "SELECTION_TIME", False); + xaSELECTION_STRING = XInternAtom(dpy, "SELECTION_STRING", False); + xaCLIPBOARD = XInternAtom(dpy, "CLIPBOARD", False); + XColor cols[6]; + cols[0].red = cols[0].green = cols[0].blue = 0x0000; + cols[1].red = cols[1].green = cols[1].blue = 0xbbbb; + cols[2].red = cols[2].green = cols[2].blue = 0xeeee; + cols[3].red = cols[3].green = cols[3].blue = 0x5555; + cols[4].red = cols[4].green = cols[4].blue = 0x8888; + cols[5].red = cols[5].green = cols[5].blue = 0xffff; + getColours(dpy, cols, 6); + black = defaultFg = cols[0].pixel; + defaultBg = disabledBg = cols[1].pixel; + lightBg = cols[2].pixel; + darkBg = disabledFg = cols[3].pixel; + scrollbarBg = cols[4].pixel; + white = enabledBg = cols[5].pixel; + defaultGC = XCreateGC(dpy, DefaultRootWindow(dpy), 0, 0); + defaultFS + = XLoadQueryFont(dpy, "-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*"); + if (!defaultFS) { + defaultFS = XLoadQueryFont(dpy, "fixed"); + if (!defaultFS) { + fprintf(stderr,"Failed to load any font\n"); + exit(1); + } + } + defaultFont = defaultFS->fid; + XSetForeground(dpy, defaultGC, defaultFg); + XSetBackground(dpy, defaultGC, defaultBg); + XSetFont(dpy, defaultGC, defaultFont); + XSelectInput(dpy, DefaultRootWindow(dpy), PropertyChangeMask); + + static char dotBits[] = { 0x06, 0x0f, 0x0f, 0x06}; + dot = XCreateBitmapFromData(dpy, DefaultRootWindow(dpy), dotBits, + dotSize, dotSize); + static char tickBits[] = { 0x80, 0xc0, 0xe2, 0x76, 0x3e, 0x1c, 0x08, 0x00}; + tick = XCreateBitmapFromData(dpy, DefaultRootWindow(dpy), tickBits, + tickSize, tickSize); + defaultWindowClass = rfb::strDup(defaultWindowClass_); +} + +void TXWindow::handleXEvents(Display* dpy) +{ + while (XPending(dpy)) { + XEvent ev; + XNextEvent(dpy, &ev); + if (ev.type == MappingNotify) { + XRefreshKeyboardMapping(&ev.xmapping); + } else if (ev.type == PropertyNotify && + ev.xproperty.window == DefaultRootWindow(dpy) && + ev.xproperty.atom == XA_CUT_BUFFER0) { + cutBufferTime = ev.xproperty.time; + } else { + std::list<TXWindow*>::iterator i; + for (i = windows.begin(); i != windows.end(); i++) { + if ((*i)->win() == ev.xany.window) + (*i)->handleXEvent(&ev); + } + } + } +} + +void TXWindow::getColours(Display* dpy, XColor* cols, int nCols) +{ + bool* got = new bool[nCols]; + bool failed = false; + int i; + for (i = 0; i < nCols; i++) { + if (XAllocColor(dpy, cmap, &cols[i])) { + got[i] = true; + } else { + got[i] = false; + failed = true; + } + } + + if (!failed) { + delete [] got; + return; + } + + // AllocColor has failed. This is because the colormap is full. So the + // only thing we can do is use the "shared" pixels in the colormap. The + // code below is designed to work even when the colormap isn't full so is + // more complex than it needs to be in this case. However it would be + // useful one day to be able to restrict the number of colours allocated by + // an application so I'm leaving it in here. + + // For each pixel in the colormap, try to allocate exactly its RGB values. + // If this returns a different pixel then it must be a private or + // unallocated pixel, so we can't use it. If it returns us the same pixel + // again, it's almost certainly a shared colour, so we can use it (actually + // it is possible that it was an unallocated pixel which we've now + // allocated - by going through the pixels in reverse order we make this + // unlikely except for the lowest unallocated pixel - this works because of + // the way the X server allocates new pixels). + + int cmapSize = DisplayCells(dpy,DefaultScreen(dpy)); + + XColor* cm = new XColor[cmapSize]; + bool* shared = new bool[cmapSize]; + bool* usedAsNearest = new bool[cmapSize]; + + for (i = 0; i < cmapSize; i++) { + cm[i].pixel = i; + shared[i] = usedAsNearest[i] = false; + } + + XQueryColors(dpy, cmap, cm, cmapSize); + + for (i = cmapSize-1; i >= 0; i--) { + if (XAllocColor(dpy, cmap, &cm[i])) { + if (cm[i].pixel == (unsigned long)i) { + shared[i] = true; + } else { + XFreeColors(dpy, cmap, &cm[i].pixel, 1, 0); + } + } + } + + for (int j = 0; j < nCols; j++) { + unsigned long minDistance = ULONG_MAX; + unsigned long nearestPixel = 0; + if (!got[j]) { + for (i = 0; i < cmapSize; i++) { + if (shared[i]) { + unsigned long rd = (cm[i].red - cols[j].red)/2; + unsigned long gd = (cm[i].green - cols[j].green)/2; + unsigned long bd = (cm[i].blue - cols[j].blue)/2; + unsigned long distance = (rd*rd + gd*gd + bd*bd); + + if (distance < minDistance) { + minDistance = distance; + nearestPixel = i; + } + } + } + + cols[j].pixel = nearestPixel; + usedAsNearest[nearestPixel] = true; + } + } + + for (i = 0; i < cmapSize; i++) { + if (shared[i] && !usedAsNearest[i]) { + unsigned long p = i; + XFreeColors(dpy, cmap, &p, 1, 0); + } + } +} + +Window TXWindow::windowWithName(Display* dpy, Window top, const char* name) +{ + char* windowName; + if (XFetchName(dpy, top, &windowName)) { + if (strcmp(windowName, name) == 0) { + XFree(windowName); + return top; + } + XFree(windowName); + } + + Window* children; + Window dummy; + unsigned int nchildren; + if (!XQueryTree(dpy, top, &dummy, &dummy, &children,&nchildren) || !children) + return 0; + + for (int i = 0; i < (int)nchildren; i++) { + Window w = windowWithName(dpy, children[i], name); + if (w) { + XFree((char*)children); + return w; + } + } + XFree((char*)children); + return 0; +} + + +TXWindow::TXWindow(Display* dpy_, int w, int h, TXWindow* parent_, + int borderWidth) + : dpy(dpy_), xPad(3), yPad(3), bevel(2), parent(parent_), width_(w), + height_(h), eventHandler(0), dwc(0), eventMask(0), toplevel_(false) +{ + sizeHints.flags = 0; + XSetWindowAttributes attr; + attr.background_pixel = defaultBg; + attr.border_pixel = 0; + Window par = parent ? parent->win() : DefaultRootWindow(dpy); + win_ = XCreateWindow(dpy, par, 0, 0, width_, height_, borderWidth, + CopyFromParent, CopyFromParent, CopyFromParent, + CWBackPixel | CWBorderPixel, &attr); + if (parent) map(); + + windows.push_back(this); +} + +TXWindow::~TXWindow() +{ + windows.remove(this); + XDestroyWindow(dpy, win()); +} + +void TXWindow::toplevel(const char* name, TXDeleteWindowCallback* dwc_, + int argc, char** argv, const char* windowClass, + bool iconic) +{ + toplevel_ = true; + XWMHints wmHints; + wmHints.flags = InputHint|StateHint; + wmHints.input = True; + wmHints.initial_state = iconic ? IconicState : NormalState; + XClassHint classHint; + if (!windowClass) windowClass = defaultWindowClass; + classHint.res_name = (char*)name; + classHint.res_class = (char*)windowClass; + XSetWMProperties(dpy, win(), 0, 0, argv, argc, + &sizeHints, &wmHints, &classHint); + XStoreName(dpy, win(), name); + XSetIconName(dpy, win(), name); + Atom protocols[10]; + int nProtocols = 0; + protocols[nProtocols++] = wmTakeFocus; + dwc = dwc_; + if (dwc) + protocols[nProtocols++] = wmDeleteWindow; + XSetWMProtocols(dpy, win(), protocols, nProtocols); + addEventMask(StructureNotifyMask); +} + +void TXWindow::setMaxSize(int w, int h) +{ + sizeHints.flags |= PMaxSize; + sizeHints.max_width = w; + sizeHints.max_height = h; + XSetWMNormalHints(dpy, win(), &sizeHints); +} + +void TXWindow::setUSPosition(int x, int y) +{ + sizeHints.flags |= USPosition; + sizeHints.x = x; + sizeHints.y = y; + XSetWMNormalHints(dpy, win(), &sizeHints); + move(x, y); +} + +void TXWindow::setGeometry(const char* geom, int x, int y, int w, int h) +{ + char defGeom[256]; + sprintf(defGeom,"%dx%d+%d+%d",w,h,x,y); + XWMGeometry(dpy, DefaultScreen(dpy), strEmptyToNull((char*)geom), defGeom, + 0, &sizeHints, &x, &y, &w, &h, &sizeHints.win_gravity); + sizeHints.flags |= PWinGravity; + setUSPosition(x, y); + resize(w, h); +} + +TXEventHandler* TXWindow::setEventHandler(TXEventHandler* h) +{ + TXEventHandler* old = eventHandler; + eventHandler = h; + return old; +} + +void TXWindow::addEventMask(long mask) +{ + eventMask |= mask; + XSelectInput(dpy, win(), eventMask); +} + +void TXWindow::removeEventMask(long mask) +{ + eventMask &= ~mask; + XSelectInput(dpy, win(), eventMask); +} + +void TXWindow::unmap() +{ + XUnmapWindow(dpy, win()); + if (toplevel_) { + XUnmapEvent ue; + ue.type = UnmapNotify; + ue.display = dpy; + ue.event = DefaultRootWindow(dpy); + ue.window = win(); + ue.from_configure = False; + XSendEvent(dpy, DefaultRootWindow(dpy), False, + (SubstructureRedirectMask|SubstructureNotifyMask), + (XEvent*)&ue); + } +} + +void TXWindow::resize(int w, int h) +{ + //if (w == width_ && h == height_) return; + XResizeWindow(dpy, win(), w, h); + width_ = w; + height_ = h; + resizeNotify(); +} + +void TXWindow::setBorderWidth(int bw) +{ + XWindowChanges c; + c.border_width = bw; + XConfigureWindow(dpy, win(), CWBorderWidth, &c); +} + +void TXWindow::ownSelection(Atom selection, Time time) +{ + XSetSelectionOwner(dpy, selection, win(), time); + if (XGetSelectionOwner(dpy, selection) == win()) { + selectionOwner_[selection] = true; + selectionOwnTime[selection] = time; + } +} + +void TXWindow::handleXEvent(XEvent* ev) +{ + switch (ev->type) { + + case ClientMessage: + if (ev->xclient.message_type == wmProtocols) { + if ((Atom)ev->xclient.data.l[0] == wmDeleteWindow) { + if (dwc) dwc->deleteWindow(this); + } else if ((Atom)ev->xclient.data.l[0] == wmTakeFocus) { + takeFocus(ev->xclient.data.l[1]); + } + } + break; + + case ConfigureNotify: + if (ev->xconfigure.width != width_ || ev->xconfigure.height != height_) { + width_ = ev->xconfigure.width; + height_ = ev->xconfigure.height; + resizeNotify(); + } + break; + + case SelectionNotify: + if (ev->xselection.property != None) { + Atom type; + int format; + unsigned long nitems, after; + unsigned char *data; + XGetWindowProperty(dpy, win(), ev->xselection.property, 0, 16384, True, + AnyPropertyType, &type, &format, + &nitems, &after, &data); + if (type != None) { + selectionNotify(&ev->xselection, type, format, nitems, data); + XFree(data); + break; + } + } + selectionNotify(&ev->xselection, 0, 0, 0, 0); + break; + + case SelectionRequest: + { + XSelectionEvent se; + se.type = SelectionNotify; + se.display = ev->xselectionrequest.display; + se.requestor = ev->xselectionrequest.requestor; + se.selection = ev->xselectionrequest.selection; + se.time = ev->xselectionrequest.time; + se.target = ev->xselectionrequest.target; + if (ev->xselectionrequest.property == None) + ev->xselectionrequest.property = ev->xselectionrequest.target; + if (!selectionOwner_[se.selection]) { + se.property = None; + } else { + se.property = ev->xselectionrequest.property; + if (se.target == xaTARGETS) { + Atom targets[2]; + targets[0] = xaTIMESTAMP; + targets[1] = XA_STRING; + XChangeProperty(dpy, se.requestor, se.property, XA_ATOM, 32, + PropModeReplace, (unsigned char*)targets, 2); + } else if (se.target == xaTIMESTAMP) { + rdr::U32 t = selectionOwnTime[se.selection]; + XChangeProperty(dpy, se.requestor, se.property, XA_INTEGER, 32, + PropModeReplace, (unsigned char*)&t, 1); + } else if (se.target == XA_STRING) { + if (!selectionRequest(se.requestor, se.selection, se.property)) + se.property = None; + } + } + XSendEvent(dpy, se.requestor, False, 0, (XEvent*)&se); + break; + } + + case SelectionClear: + selectionOwner_[ev->xselectionclear.selection] = false; + break; + } + + if (eventHandler) eventHandler->handleEvent(this, ev); +} + +void TXWindow::drawBevel(GC gc, int x, int y, int w, int h, int b, + unsigned long middle, unsigned long tl, + unsigned long br, bool round) +{ + if (round) { + XGCValues gcv; + gcv.line_width = b; + XChangeGC(dpy, gc, GCLineWidth, &gcv); + XSetForeground(dpy, gc, middle); + XFillArc(dpy, win(), gc, x, y, w-b/2, h-b/2, 0, 360*64); + XSetForeground(dpy, gc, tl); + XDrawArc(dpy, win(), gc, x, y, w-b/2, h-b/2, 45*64, 180*64); + XSetForeground(dpy, gc, br); + XDrawArc(dpy, win(), gc, x, y, w-b/2, h-b/2, 225*64, 180*64); + } else { + XSetForeground(dpy, gc, middle); + if (w-2*b > 0 && h-2*b > 0) + XFillRectangle(dpy, win(), gc, x+b, y+b, w-2*b, h-2*b); + XSetForeground(dpy, gc, tl); + XFillRectangle(dpy, win(), gc, x, y, w, b); + XFillRectangle(dpy, win(), gc, x, y, b, h); + XSetForeground(dpy, gc, br); + for (int i = 0; i < b; i++) { + if (w-i > 0) XFillRectangle(dpy, win(), gc, x+i, y+h-1-i, w-i, 1); + if (h-1-i > 0) XFillRectangle(dpy, win(), gc, x+w-1-i, y+i+1, 1, h-1-i); + } + } +} diff --git a/unix/tx/TXWindow.h b/unix/tx/TXWindow.h new file mode 100644 index 00000000..5ada181b --- /dev/null +++ b/unix/tx/TXWindow.h @@ -0,0 +1,211 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// +// TXWindow.h +// +// A TXWindow is the base class for all tx windows (widgets). In addition it +// contains a number of static methods and members which are used throughout +// tx. +// +// Before calling any other tx methods, TXWindow::init() must be called with +// the X display to use. + +#ifndef __TXWINDOW_H__ +#define __TXWINDOW_H__ + +#include <rdr/types.h> +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <map> + + +// TXDeleteWindowCallback's deleteWindow() method is called when a top-level +// window is "deleted" (closed) by the user using the window manager. +class TXWindow; +class TXDeleteWindowCallback { +public: + virtual void deleteWindow(TXWindow* w) = 0; +}; + +// TXEventHandler is an interface implemented by classes wanting to handle X +// events on a window. Most derived classes of window are their own event +// handlers. +class TXEventHandler { +public: + virtual void handleEvent(TXWindow* w, XEvent* ev) = 0; +}; + +class TXWindow { +public: + + // Constructor - creates a window of the given size, with the default + // background (currently grey). It is mapped by default if it has a parent. + // If no parent is specified its parent is the root window and it will remain + // unmapped. + TXWindow(Display* dpy_, int width=1, int height=1, TXWindow* parent_=0, + int borderWidth=0); + virtual ~TXWindow(); + + // toplevel() declares that this is a top-level window. Various + // window-manager-related properties are set on the window. The given + // TXDeleteWindowCallback is notified when the window is "deleted" (cloesd) + // by the user. + void toplevel(const char* name, TXDeleteWindowCallback* dwc=0, + int argc=0, char** argv=0, const char* windowClass=0, + bool iconic=false); + + // setMaxSize() tells the window manager the maximum size to allow a + // top-level window. It has no effect on a non-top-level window. + void setMaxSize(int w, int h); + + // setUSPosition() tells the window manager the position which the "user" has + // asked for a top-level window. Most window managers ignore requests by a + // program for position, so you have to tell it that the "user" asked for the + // position. This has no effect on a non-top-level window. + void setUSPosition(int x, int y); + + void setGeometry(const char* geom, int x, int y, int w, int h); + + // setTransientFor() tells the window manager that this window is "owned" by + // the given window. The window manager can use this information as it sees + // fit. + void setTransientFor(Window w) { XSetTransientForHint(dpy, win(), w); } + + // setEventHandler() sets the TXEventHandler to handle X events for this + // window. It returns the previous event handler, so that handlers can chain + // themselves. + TXEventHandler* setEventHandler(TXEventHandler* h); + + // Accessor methods + Window win() { return win_; } + int width() { return width_; } + int height() { return height_; } + + // selectionOwner() returns true if this window owns the given selection. + bool selectionOwner(Atom selection) { return selectionOwner_[selection]; } + + // Wrappers around common Xlib calls + void addEventMask(long mask); + void removeEventMask(long mask); + void map() { XMapWindow(dpy, win()); } + void unmap(); + void setBg(unsigned long bg) { XSetWindowBackground(dpy, win(), bg); } + void move(int x, int y) { XMoveWindow(dpy, win(), x, y); } + void resize(int w, int h); + void raise() { XRaiseWindow(dpy, win()); } + void setBorderWidth(int bw); + void invalidate(int x=0, int y=0, int w=0, int h=0) { XClearArea(dpy, win(), x, y, w, h, True); } + + // ownSelection requests that the window owns the given selection from the + // given time (the time should be taken from an X event). + void ownSelection(Atom selection, Time time); + + + // drawBevel draws a rectangular or circular bevel filling the given + // rectangle, using the given colours for the middle, the top/left and the + // bottom/right. + void drawBevel(GC gc, int x, int y, int w, int h, int b, + unsigned long middle, unsigned long tl, unsigned long br, + bool round=false); + + // Methods to be overridden in a derived class + + // resizeNotify() is called whenever the window's dimensions may have + // changed. + virtual void resizeNotify() {} + + // takeFocus() is called when the window has received keyboard focus from the + // window manager. + virtual void takeFocus(Time time) {} + + // selectionNotify() is called when the selection owner has replied to a + // request for information about a selection from the selection owner. + virtual void selectionNotify(XSelectionEvent* ev, Atom type, int format, + int nitems, void* data) {} + + // selectionRequest() is called when this window is the selection owner and + // another X client has requested the selection. It should set the given + // property on the given window to the value of the given selection, + // returning true if successful, false otherwise. + virtual bool selectionRequest(Window requestor, + Atom selection, Atom property) { return false;} + + // Static methods + + // init() must be called before any other tx methods. + static void init(Display* dpy, const char* defaultWindowClass); + + // getColours() sets the pixel values in the cols array to the best available + // for the given rgb values, even in the case of a full colormap. + static void getColours(Display* dpy, XColor* cols, int nCols); + + // handleXEvents() should be called whenever there are events to handle on + // the connection to the X display. It process all available events, then + // returns when there are no more events to process. + static void handleXEvents(Display* dpy); + + // windowWithName() locates a window with a given name on a display. + static Window windowWithName(Display* dpy, Window top, const char* name); + + // strEmptyToNull() returns the string it's given but turns an empty string + // into null, which can be useful for passing rfb parameters to Xlib calls. + static char* strEmptyToNull(char* s) { return s && s[0] ? s : 0; } + + // The following are default values for various things. + static unsigned long black, white; + static unsigned long defaultFg, defaultBg, lightBg, darkBg; + static unsigned long disabledFg, disabledBg, enabledBg; + static unsigned long scrollbarBg; + static GC defaultGC; + static Colormap cmap; + static Font defaultFont; + static XFontStruct* defaultFS; + static Time cutBufferTime; + static Pixmap dot, tick; + static const int dotSize, tickSize; + static char* defaultWindowClass; + + Display* const dpy; + + int xPad, yPad, bevel; + +private: + + // handleXEvent() is called from handleXEvents() when an event for this + // window arrives. It does general event processing before calling on to the + // event handler. + void handleXEvent(XEvent* ev); + + TXWindow* parent; + Window win_; + int width_, height_; + TXEventHandler* eventHandler; + TXDeleteWindowCallback* dwc; + long eventMask; + XSizeHints sizeHints; + std::map<Atom,Time> selectionOwnTime; + std::map<Atom,bool> selectionOwner_; + bool toplevel_; +}; + +extern Atom wmProtocols, wmDeleteWindow, wmTakeFocus; +extern Atom xaTIMESTAMP, xaTARGETS, xaSELECTION_TIME, xaSELECTION_STRING; +extern Atom xaCLIPBOARD; + +#endif |