xenocara/app/wm2/Border.C

746 lines
18 KiB
C

#include "Border.h"
#include "Client.h"
#include "Manager.h"
#include "Rotated.h"
// These are degenerate initialisations, don't change them
int Border::m_tabWidth = -1;
XRotFontStruct *Border::m_tabFont = 0;
GC Border::m_drawGC = 0;
unsigned long Border::m_foregroundPixel;
unsigned long Border::m_backgroundPixel;
unsigned long Border::m_frameBackgroundPixel;
unsigned long Border::m_buttonBackgroundPixel;
unsigned long Border::m_borderPixel;
static int borderCounter = 0;
declareList(RectangleList, XRectangle);
implementList(RectangleList, XRectangle);
class BorderRectangleList : public RectangleList
{
public:
BorderRectangleList() { }
~BorderRectangleList() { }
void appendRect(int x, int y, int w, int h);
};
void BorderRectangleList::appendRect(int x, int y, int w, int h)
{
XRectangle r;
r.x = x; r.y = y; r.width = w; r.height = h;
append(r);
}
Border::Border(Client *const c, Window child) :
m_client(c), m_parent(0), m_tab(0),
m_child(child), m_button(0), m_resize(0), m_label(0),
m_tabHeight(-1), m_prevW(-1), m_prevH(-1)
{
m_parent = root();
if (m_tabFont == 0) {
if (!(m_tabFont = XRotLoadFont(display(), CONFIG_NICE_FONT, 90.0)) &&
!(m_tabFont = XRotLoadFont(display(), CONFIG_NASTY_FONT, 90.0))) {
windowManager()->fatal
("couldn't load default rotated font, bailing out");
}
m_tabWidth = m_tabFont->height + 4;
if (m_tabWidth < TAB_TOP_HEIGHT * 2 + 8) {
m_tabWidth = TAB_TOP_HEIGHT * 2 + 8;
}
m_foregroundPixel = windowManager()->allocateColour
(CONFIG_TAB_FOREGROUND, "tab foreground");
m_backgroundPixel = windowManager()->allocateColour
(CONFIG_TAB_BACKGROUND, "tab background");
m_frameBackgroundPixel = windowManager()->allocateColour
(CONFIG_FRAME_BACKGROUND, "frame background");
m_buttonBackgroundPixel = windowManager()->allocateColour
(CONFIG_BUTTON_BACKGROUND, "button background");
m_borderPixel = windowManager()->allocateColour
(CONFIG_BORDERS, "border");
XGCValues values;
values.foreground = m_foregroundPixel;
values.background = m_backgroundPixel;
values.function = GXcopy;
values.line_width = 0;
values.subwindow_mode = IncludeInferiors;
m_drawGC = XCreateGC(display(), root(),
GCForeground | GCBackground | GCFunction |
GCLineWidth | GCSubwindowMode,
&values);
if (!m_drawGC) {
windowManager()->fatal("couldn't allocate border GC");
}
}
++borderCounter;
}
Border::~Border()
{
if (m_parent != root()) {
if (!m_parent) fprintf(stderr,"wm2: zero parent in Border::~Border\n");
else {
XDestroyWindow(display(), m_tab);
XDestroyWindow(display(), m_button);
XDestroyWindow(display(), m_parent);
// bad window if its parent has already gone:
XDestroyWindow(display(), m_resize);
}
}
if (m_label) free(m_label);
if (--borderCounter == 0) {
XFreeGC(display(), m_drawGC);
}
}
void Border::fatal(char *s)
{
windowManager()->fatal(s);
}
Display *Border::display()
{
return m_client->display();
}
WindowManager *Border::windowManager()
{
return m_client->windowManager();
}
Window Border::root()
{
return m_client->root();
}
void Border::expose(XExposeEvent *e)
{
if (e->window != m_tab) return;
drawLabel();
}
void Border::drawLabel()
{
if (m_label) {
XClearWindow(display(), m_tab);
XRotDrawString(display(), m_tabFont, m_tab, m_drawGC,
2 + m_tabFont->max_ascent, m_tabHeight - 1,
m_label, strlen(m_label));
}
}
Boolean Border::isTransient(void)
{
return m_client->isTransient();
}
Boolean Border::isFixedSize(void)
{
return m_client->isFixedSize();
}
void Border::fixTabHeight(int maxHeight)
{
m_tabHeight = 0x7fff;
maxHeight -= m_tabWidth; // for diagonal
if (m_label) free(m_label);
m_label = NewString(m_client->label());
if (m_label) {
m_tabHeight =
XRotTextWidth(m_tabFont, m_label, strlen(m_label)) + 6 + m_tabWidth;
}
if (m_tabHeight <= maxHeight) return;
if (m_label) free(m_label);
m_label = m_client->iconName() ?
NewString(m_client->iconName()) : NewString("incognito");
int len = strlen(m_label);
m_tabHeight = XRotTextWidth(m_tabFont, m_label, len) + 6 + m_tabWidth;
if (m_tabHeight <= maxHeight) return;
char *newLabel = (char *)malloc(len + 4);
do {
strlcpy(newLabel, m_label, len + 4);
strlcat(newLabel, "...", len + 4);
m_tabHeight = XRotTextWidth(m_tabFont, newLabel,
strlen(newLabel)) + 6 + m_tabWidth;
--len;
} while (m_tabHeight > maxHeight && len > 2);
free(m_label);
m_label = newLabel;
if (m_tabHeight > maxHeight) m_tabHeight = maxHeight;
}
void Border::shapeTransientParent(int w, int h)
{
XRectangle r;
r.x = xIndent() - 1; r.y = yIndent() - 1;
r.width = w + 2; r.height = h + 2;
XShapeCombineRectangles
(display(), m_parent, ShapeBounding, 0, 0, &r, 1, ShapeSet, YXBanded);
r.x = xIndent(); r.y = yIndent();
r.width = w; r.height = h;
XShapeCombineRectangles
(display(), m_parent, ShapeClip, 0, 0, &r, 1, ShapeSet, YXBanded);
}
void Border::setTransientFrameVisibility(Boolean visible, int w, int h)
{
int i;
BorderRectangleList rl;
rl.appendRect(0, 0, w + 1, yIndent() - 1);
for (i = 1; i < yIndent(); ++i) {
rl.appendRect(w + 1, i - 1, i + 1, 1);
}
rl.appendRect(0, yIndent() - 1, xIndent() - 1, h - yIndent() + 2);
for (i = 1; i < yIndent(); ++i) {
rl.appendRect(i - 1, h, 1, i + 2);
}
XShapeCombineRectangles
(display(), m_parent, ShapeBounding,
0, 0, rl.array(0, rl.count()), rl.count(),
visible ? ShapeUnion : ShapeSubtract, YXSorted);
rl.remove_all();
rl.appendRect(1, 1, w, yIndent() - 2);
for (i = 2; i < yIndent(); ++i) {
rl.appendRect(w + 1, i - 1, i, 1);
}
rl.appendRect(1, yIndent() - 1, xIndent() - 2, h - yIndent() + 1);
for (i = 2; i < yIndent(); ++i) {
rl.appendRect(i - 1, h, 1, i + 1);
}
XShapeCombineRectangles
(display(), m_parent, ShapeClip,
0, 0, rl.array(0, rl.count()), rl.count(),
visible ? ShapeUnion : ShapeSubtract, YXSorted);
}
void Border::shapeParent(int w, int h)
{
int i;
int mainRect;
BorderRectangleList rl;
if (isTransient()) {
shapeTransientParent(w, h);
return;
}
// Bounding rectangles -- clipping will be the same except for
// child window border
// top of tab
rl.appendRect(0, 0, w + m_tabWidth + 1, TAB_TOP_HEIGHT + 2);
// struts in tab, left...
rl.appendRect(0, TAB_TOP_HEIGHT + 1,
TAB_TOP_HEIGHT + 2, m_tabWidth - TAB_TOP_HEIGHT*2 - 1);
// ...and right
rl.appendRect(m_tabWidth - TAB_TOP_HEIGHT, TAB_TOP_HEIGHT + 1,
TAB_TOP_HEIGHT + 2, m_tabWidth - TAB_TOP_HEIGHT*2 - 1);
mainRect = rl.count();
rl.appendRect(xIndent() - 1, yIndent() - 1, w + 2, h + 2);
// main tab
rl.appendRect(0, m_tabWidth - TAB_TOP_HEIGHT, m_tabWidth + 2,
m_tabHeight - m_tabWidth + TAB_TOP_HEIGHT);
// diagonal
for (i = 1; i < m_tabWidth - 1; ++i) {
rl.appendRect(i, m_tabHeight + i - 1, m_tabWidth - i + 2, 1);
}
XShapeCombineRectangles
(display(), m_parent, ShapeBounding,
0, 0, rl.array(0, rl.count()), rl.count(), ShapeSet, YXSorted);
rl.item(mainRect).x ++;
rl.item(mainRect).y ++;
rl.item(mainRect).width -= 2;
rl.item(mainRect).height -= 2;
XShapeCombineRectangles
(display(), m_parent, ShapeClip,
0, 0, rl.array(0, rl.count()), rl.count(), ShapeSet, YXSorted);
}
void Border::shapeTab(int w, int)
{
int i;
BorderRectangleList rl;
if (isTransient()) {
return;
}
// Bounding rectangles
rl.appendRect(0, 0, w + m_tabWidth + 1, TAB_TOP_HEIGHT + 2);
rl.appendRect(0, TAB_TOP_HEIGHT + 1, TAB_TOP_HEIGHT + 2,
m_tabWidth - TAB_TOP_HEIGHT*2 - 1);
rl.appendRect(m_tabWidth - TAB_TOP_HEIGHT, TAB_TOP_HEIGHT + 1,
TAB_TOP_HEIGHT + 2, m_tabWidth - TAB_TOP_HEIGHT*2 - 1);
rl.appendRect(0, m_tabWidth - TAB_TOP_HEIGHT, m_tabWidth + 2,
m_tabHeight - m_tabWidth + TAB_TOP_HEIGHT);
for (i = 1; i < m_tabWidth - 1; ++i) {
rl.appendRect(i, m_tabHeight + i - 1, m_tabWidth - i + 2, 1);
}
XShapeCombineRectangles
(display(), m_tab, ShapeBounding,
0, 0, rl.array(0, rl.count()), rl.count(), ShapeSet, YXSorted);
rl.remove_all();
// Clipping rectangles
rl.appendRect(1, 1, w + m_tabWidth - 1, TAB_TOP_HEIGHT);
rl.appendRect(1, TAB_TOP_HEIGHT + 1, TAB_TOP_HEIGHT,
m_tabWidth + TAB_TOP_HEIGHT*2 - 1);
rl.appendRect(m_tabWidth - TAB_TOP_HEIGHT + 1, TAB_TOP_HEIGHT + 1,
TAB_TOP_HEIGHT, m_tabWidth + TAB_TOP_HEIGHT*2 - 1);
rl.appendRect(1, m_tabWidth - TAB_TOP_HEIGHT + 1, m_tabWidth,
m_tabHeight - m_tabWidth + TAB_TOP_HEIGHT - 1);
for (i = 1; i < m_tabWidth - 2; ++i) {
rl.appendRect(i + 1, m_tabHeight + i - 1, m_tabWidth - i, 1);
}
XShapeCombineRectangles
(display(), m_tab, ShapeClip,
0, 0, rl.array(0, rl.count()), rl.count(), ShapeSet, YXSorted);
}
void Border::resizeTab(int h)
{
int i;
XRectangle r;
BorderRectangleList rl;
int shorter, longer, operation;
if (isTransient()) {
return;
}
int prevTabHeight = m_tabHeight;
fixTabHeight(h);
if (m_tabHeight == prevTabHeight) return;
XWindowChanges wc;
wc.height = m_tabHeight + 2 + m_tabWidth;
XConfigureWindow(display(), m_tab, CWHeight, &wc);
if (m_tabHeight > prevTabHeight) {
shorter = prevTabHeight;
longer = m_tabHeight;
operation = ShapeUnion;
} else {
shorter = m_tabHeight;
longer = prevTabHeight + m_tabWidth;
operation = ShapeSubtract;
}
r.x = 0; r.y = shorter /*- 2*/;
r.width = m_tabWidth + 2; r.height = longer - shorter;
XShapeCombineRectangles(display(), m_parent, ShapeBounding,
0, 0, &r, 1, operation, YXBanded);
XShapeCombineRectangles(display(), m_parent, ShapeClip,
0, 0, &r, 1, operation, YXBanded);
XShapeCombineRectangles(display(), m_tab, ShapeBounding,
0, 0, &r, 1, operation, YXBanded);
r.x ++; r.width -= 2;
XShapeCombineRectangles(display(), m_tab, ShapeClip,
0, 0, &r, 1, operation, YXBanded);
if (m_client->isActive()) {
// restore a bit of the frame edge
r.x = m_tabWidth + 1; r.y = shorter;
r.width = FRAME_WIDTH - 1; r.height = longer - shorter;
XShapeCombineRectangles(display(), m_parent, ShapeBounding,
0, 0, &r, 1, ShapeUnion, YXBanded);
}
for (i = 1; i < m_tabWidth - 1; ++i) {
rl.appendRect(i, m_tabHeight + i - 1, m_tabWidth - i + 2, 1);
}
XShapeCombineRectangles
(display(), m_parent, ShapeBounding,
0, 0, rl.array(0, rl.count()), rl.count(), ShapeUnion, YXBanded);
XShapeCombineRectangles
(display(), m_parent, ShapeClip,
0, 0, rl.array(0, rl.count()), rl.count(), ShapeUnion, YXBanded);
XShapeCombineRectangles
(display(), m_tab, ShapeBounding,
0, 0, rl.array(0, rl.count()), rl.count(), ShapeUnion, YXBanded);
if (rl.count() < 2) return;
for (i = 0; i < rl.count() - 1; ++i) {
rl.item(i).x ++; rl.item(i).width -= 2;
}
XShapeCombineRectangles
(display(), m_tab, ShapeClip,
0, 0, rl.array(0, rl.count() - 1), rl.count() - 1,
ShapeUnion, YXBanded);
}
void Border::shapeResize()
{
int i;
BorderRectangleList rl;
for (i = 0; i < FRAME_WIDTH*2; ++i) {
rl.appendRect(FRAME_WIDTH*2 - i - 1, i, i + 1, 1);
}
XShapeCombineRectangles
(display(), m_resize, ShapeBounding, 0, 0,
rl.array(0, rl.count()), rl.count(), ShapeSet, YXBanded);
rl.remove_all();
for (i = 1; i < FRAME_WIDTH*2; ++i) {
rl.appendRect(FRAME_WIDTH*2 - i, i, i, 1);
}
XShapeCombineRectangles
(display(), m_resize, ShapeClip, 0, 0,
rl.array(0, rl.count()), rl.count(), ShapeSet, YXBanded);
rl.remove_all();
for (i = 0; i < FRAME_WIDTH*2 - 3; ++i) {
rl.appendRect(FRAME_WIDTH*2 - i - 1, i + 3, 1, 1);
}
XShapeCombineRectangles
(display(), m_resize, ShapeClip, 0, 0,
rl.array(0, rl.count()), rl.count(), ShapeSubtract, YXBanded);
windowManager()->installCursorOnWindow
(WindowManager::DownrightCursor, m_resize);
}
void Border::setFrameVisibility(Boolean visible, int w, int h)
{
BorderRectangleList rl;
if (CONFIG_PROD_SHAPE) {
shapeParent(w, h);
shapeTab(w, h);
}
if (isTransient()) {
setTransientFrameVisibility(visible, w, h);
return;
}
// Bounding rectangles
rl.appendRect(m_tabWidth + w + 1, 0, FRAME_WIDTH + 1, FRAME_WIDTH);
rl.appendRect(m_tabWidth + 2, TAB_TOP_HEIGHT + 2, w,
FRAME_WIDTH - TAB_TOP_HEIGHT - 2);
// for button
int ww = m_tabWidth - TAB_TOP_HEIGHT*2 - 4;
rl.appendRect((m_tabWidth + 2 - ww) / 2, (m_tabWidth+2 - ww) / 2, ww, ww);
rl.appendRect(m_tabWidth + 2, FRAME_WIDTH,
FRAME_WIDTH - 2, m_tabHeight + m_tabWidth - FRAME_WIDTH - 2);
// swap last two if sorted wrong
if (rl.item(rl.count()-2).y > rl.item(rl.count()-1).y) {
rl.append(rl.item(rl.count()-2));
rl.remove(rl.count()-3);
}
int final = rl.count();
rl.append(rl.item(final-1));
rl.item(final).x -= 1;
rl.item(final).y += rl.item(final).height;
rl.item(final).width += 1;
rl.item(final).height = h - rl.item(final).height + 2;
XShapeCombineRectangles(display(), m_parent, ShapeBounding,
0, 0, rl.array(0, rl.count()), rl.count(),
visible ? ShapeUnion : ShapeSubtract, YXSorted);
rl.remove_all();
// Clip rectangles
rl.appendRect(m_tabWidth + w + 1, 1, FRAME_WIDTH, FRAME_WIDTH - 1);
rl.appendRect(m_tabWidth + 2, TAB_TOP_HEIGHT + 2, w,
FRAME_WIDTH - TAB_TOP_HEIGHT - 2);
// for button
ww = m_tabWidth - TAB_TOP_HEIGHT*2 - 6;
rl.appendRect((m_tabWidth + 2 - ww) / 2, (m_tabWidth+2 - ww) / 2, ww, ww);
rl.appendRect(m_tabWidth + 2, FRAME_WIDTH,
FRAME_WIDTH - 2, h - FRAME_WIDTH);
// swap last two if sorted wrong
if (rl.item(rl.count()-2).y > rl.item(rl.count()-1).y) {
rl.append(rl.item(rl.count()-2));
rl.remove(rl.count()-3);
}
rl.appendRect(m_tabWidth + 2, h, FRAME_WIDTH - 2, FRAME_WIDTH + 1);
XShapeCombineRectangles(display(), m_parent, ShapeClip,
0, 0, rl.array(0, rl.count()), rl.count(),
visible ? ShapeUnion : ShapeSubtract, YXSorted);
rl.remove_all();
if (visible && !isFixedSize()) {
XMapRaised(display(), m_resize);
} else {
XUnmapWindow(display(), m_resize);
}
}
void Border::configure(int x, int y, int w, int h,
unsigned long mask, int detail,
Boolean force) // must reshape everything
{
if (!m_parent || m_parent == root()) {
// create windows, then shape them afterwards
m_parent = XCreateSimpleWindow
(display(), root(), 1, 1, 1, 1, 0,
m_borderPixel, m_frameBackgroundPixel);
m_tab = XCreateSimpleWindow
(display(), m_parent, 1, 1, 1, 1, 0,
m_borderPixel, m_backgroundPixel);
m_button = XCreateSimpleWindow
(display(), m_parent, 1, 1, 1, 1, 0,
m_borderPixel, m_buttonBackgroundPixel);
m_resize = XCreateWindow
(display(), m_child, 1, 1, FRAME_WIDTH*2, FRAME_WIDTH*2, 0,
CopyFromParent, InputOutput, CopyFromParent, 0L, 0);
shapeResize();
XSelectInput(display(), m_parent,
SubstructureRedirectMask | SubstructureNotifyMask |
ButtonPressMask | ButtonReleaseMask);
if (!isTransient()) {
XSelectInput(display(), m_tab,
ExposureMask | ButtonPressMask | ButtonReleaseMask |
EnterWindowMask/* | LeaveWindowMask*/);
}
XSelectInput(display(), m_button,
ButtonPressMask | ButtonReleaseMask/* | LeaveWindowMask*/);
XSelectInput(display(), m_resize, ButtonPressMask | ButtonReleaseMask);
mask |= CWX | CWY | CWWidth | CWHeight | CWBorderWidth;
}
XWindowChanges wc;
wc.x = x - xIndent();
wc.y = y - yIndent();
wc.width = w + xIndent() + 1;
wc.height = h + yIndent() + 1;
wc.border_width = 0;
wc.sibling = None;
wc.stack_mode = detail;
XConfigureWindow(display(), m_parent, mask, &wc);
unsigned long rmask = 0L;
if (mask & CWWidth) rmask |= CWX;
if (mask & CWHeight) rmask |= CWY;
wc.x = w - FRAME_WIDTH*2;
wc.y = h - FRAME_WIDTH*2;
XConfigureWindow(display(), m_resize, rmask, &wc);
if (force ||
(m_prevW < 0 || m_prevH < 0) ||
((mask & (CWWidth | CWHeight)) && (w != m_prevW || h != m_prevH))) {
int prevTabHeight = m_tabHeight;
if (isTransient()) m_tabHeight = 10; // arbitrary
else fixTabHeight(h);
shapeParent(w, h);
setFrameVisibility(m_client->isActive(), w, h);
if (force ||
prevTabHeight != m_tabHeight || m_prevW < 0 || m_prevH < 0) {
wc.x = 0;
wc.y = 0;
wc.width = w + xIndent();
wc.height = m_tabHeight + 2 + m_tabWidth;
XConfigureWindow(display(), m_tab, mask, &wc);
shapeTab(w, h);
}
m_prevW = w;
m_prevH = h;
} else {
resizeTab(h);
}
wc.x = TAB_TOP_HEIGHT + 2;
wc.y = wc.x;
wc.width = wc.height = m_tabWidth - TAB_TOP_HEIGHT*2 - 4;
XConfigureWindow(display(), m_button, mask, &wc);
}
void Border::moveTo(int x, int y)
{
XWindowChanges wc;
wc.x = x - xIndent();
wc.y = y - yIndent();
XConfigureWindow(display(), m_parent, CWX | CWY, &wc);
}
void Border::map()
{
if (m_parent == root()) {
fprintf(stderr, "wm2: bad parent in Border::map()\n");
} else {
XMapWindow(display(), m_parent);
if (!isTransient()) {
XMapWindow(display(), m_tab);
XMapWindow(display(), m_button);
if (!isFixedSize()) XMapWindow(display(), m_resize);
}
}
}
void Border::mapRaised()
{
if (m_parent == root()) {
fprintf(stderr, "wm2: bad parent in Border::mapRaised()\n");
} else {
XMapRaised(display(), m_parent);
if (!isTransient()) {
XMapWindow(display(), m_tab);
XMapRaised(display(), m_button);
if (!isFixedSize()) XMapRaised(display(), m_resize);
}
}
}
void Border::lower()
{
XLowerWindow(display(), m_parent);
}
void Border::unmap()
{
if (m_parent == root()) {
fprintf(stderr, "wm2: bad parent in Border::unmap()\n");
} else {
XUnmapWindow(display(), m_parent);
if (!isTransient()) {
XUnmapWindow(display(), m_tab);
XUnmapWindow(display(), m_button);
// XUnmapWindow(display(), m_resize); // no, will unmap with parent
}
}
}
void Border::decorate(Boolean active, int w, int h)
{
setFrameVisibility(active, w, h);
}
void Border::reparent()
{
XReparentWindow(display(), m_child, m_parent, xIndent(), yIndent());
}