X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=otk%2Fwidget.cc;h=612a252d259ad117ce5ffb30799f72762781e212;hb=99cd843fc6dc7a7f55b6c90fd1162f233853aad2;hp=1da7dd9025f15a32e75e6c9b9eeb09c87abc2905;hpb=c187fbdf60956f3bf6392bfd132ba960e43ef4ec;p=chaz%2Fopenbox diff --git a/otk/widget.cc b/otk/widget.cc index 1da7dd90..612a252d 100644 --- a/otk/widget.cc +++ b/otk/widget.cc @@ -1,188 +1,486 @@ // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*- -#ifdef HAVE_CONFIG_H -# include "../config.h" -#endif // HAVE_CONFIG_H - +#include "config.h" #include "widget.hh" - +#include "display.hh" +#include "surface.hh" +#include "rendertexture.hh" +#include "rendercolor.hh" +#include "eventdispatcher.hh" +#include "screeninfo.hh" + +#include +#include #include namespace otk { -OtkWidget::OtkWidget(OtkWidget *parent, Direction direction) - : OtkBaseWidget(parent), - _direction(direction), _stretchable_vert(false), _stretchable_horz(false), - _event_dispatcher(parent->getEventDispatcher()) +Widget::Widget(int screen, EventDispatcher *ed, Direction direction, int bevel, + bool overrideredir) + : _texture(0), + _screen(screen), + _parent(0), + _window(0), + _surface(0), + _event_mask(ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | + ExposureMask | StructureNotifyMask), + _alignment(RenderStyle::CenterJustify), + _direction(direction), + _max_size(UINT_MAX, UINT_MAX), + _visible(false), + _bordercolor(0), + _borderwidth(0), + _bevel(bevel), + _dirty(true), + _dispatcher(ed), + _ignore_config(0) { - assert(parent); - _event_dispatcher->registerHandler(getWindow(), this); + createWindow(overrideredir); + _dispatcher->registerHandler(_window, this); } -OtkWidget::OtkWidget(OtkEventDispatcher *event_dispatcher, Style *style, - Direction direction, Cursor cursor, int bevel_width) - : OtkBaseWidget(style, cursor, bevel_width), - _direction(direction), _stretchable_vert(false), _stretchable_horz(false), - _event_dispatcher(event_dispatcher) +Widget::Widget(Widget *parent, Direction direction, int bevel) + : _texture(0), + _screen(parent->_screen), + _parent(parent), + _window(0), + _surface(0), + _event_mask(ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | + ExposureMask | StructureNotifyMask), + _alignment(RenderStyle::CenterJustify), + _direction(direction), + _max_size(UINT_MAX, UINT_MAX), + _visible(false), + _bordercolor(0), + _borderwidth(0), + _bevel(bevel), + _dirty(true), + _dispatcher(parent->_dispatcher), + _ignore_config(0) { - assert(event_dispatcher); - _event_dispatcher->registerHandler(getWindow(), this); + assert(parent); + createWindow(false); + parent->addChild(this); + parent->layout(); + _dispatcher->registerHandler(_window, this); } -OtkWidget::~OtkWidget() +Widget::~Widget() { - _event_dispatcher->clearHandler(_window); + assert(_children.empty()); // this would be bad. theyd have a hanging _parent + + if (_surface) delete _surface; + if (_parent) _parent->removeChild(this); + + _dispatcher->clearHandler(_window); + XDestroyWindow(**display, _window); } -void OtkWidget::adjust(void) +void Widget::show(bool children) { - if (_direction == Horizontal) - adjustHorz(); - else - adjustVert(); + if (children) { + std::list::iterator it , end = _children.end(); + for (it = _children.begin(); it != end; ++it) + (*it)->show(true); + } + if (!_visible) { + _visible = true; + XMapWindow(**display, _window); + update(); + } } -void OtkWidget::adjustHorz(void) +void Widget::hide() { - if (_children.size() == 0) - return; + if (_visible) { + _visible = false; + XUnmapWindow(**display, _window); + if (_parent) _parent->layout(); + } +} - OtkWidget *tmp; - OtkBaseWidgetList::iterator it, end = _children.end(); +void Widget::setEventMask(long e) +{ + XSelectInput(**display, _window, e); + _event_mask = e; +} - int tallest = 0; - int width = _bevel_width; - OtkBaseWidgetList stretchable; +void Widget::update() +{ + _dirty = true; + if (parent()) + parent()->layout(); // relay-out us and our siblings + else { + render(); + layout(); + } +} - for (it = _children.begin(); it != end; ++it) { - if (!(tmp = dynamic_cast(*it))) continue; - if (tmp->isStretchableVert()) - tmp->setHeight(_rect.height() > _bevel_width * 2 ? - _rect.height() - _bevel_width * 2 : _bevel_width); - if (tmp->isStretchableHorz()) - stretchable.push_back(tmp); - else - width += tmp->_rect.width() + _bevel_width; +void Widget::moveresize(const Rect &r) +{ + unsigned int w, h; + w = std::min(std::max(r.width(), minSize().width()), maxSize().width()); + h = std::min(std::max(r.height(), minSize().height()), maxSize().height()); - if (tmp->_rect.height() > tallest) - tallest = tmp->_rect.height(); + if (r.x() == area().x() && r.y() == area().y() && + w == area().width() && h == area().height()) { + return; // no change, don't cause a big layout chain to occur! } + + internal_moveresize(r.x(), r.y(), w, h); - if (stretchable.size() > 0) { - OtkBaseWidgetList::iterator str_it = stretchable.begin(), - str_end = stretchable.end(); + update(); +} - int str_width = _rect.width() - width / stretchable.size(); +void Widget::internal_moveresize(int x, int y, unsigned w, unsigned int h) +{ + assert(w > 0); + assert(h > 0); + assert(_borderwidth >= 0); + _dirty = true; + XMoveResizeWindow(**display, _window, x, y, + w - _borderwidth * 2, + h - _borderwidth * 2); + _ignore_config++; + + _area = Rect(x, y, w, h); +} - for (; str_it != str_end; ++str_it) - (*str_it)->setWidth(str_width > _bevel_width ? str_width - _bevel_width - : _bevel_width); +void Widget::setAlignment(RenderStyle::Justify a) +{ + _alignment = a; + layout(); +} + +void Widget::createWindow(bool overrideredir) +{ + const ScreenInfo *info = display->screenInfo(_screen); + XSetWindowAttributes attrib; + unsigned long mask = CWEventMask | CWBorderPixel; + + attrib.event_mask = _event_mask; + attrib.border_pixel = (_bordercolor ? + _bordercolor->pixel(): + BlackPixel(**display, _screen)); + + if (overrideredir) { + mask |= CWOverrideRedirect; + attrib.override_redirect = true; } + + _window = XCreateWindow(**display, (_parent ? + _parent->_window : + RootWindow(**display, _screen)), + _area.x(), _area.y(), + _area.width(), _area.height(), + _borderwidth, + info->depth(), + InputOutput, + info->visual(), + mask, + &attrib); + assert(_window != None); + ++_ignore_config; +} - OtkWidget *prev_widget = 0; - - for (it = _children.begin(); it != end; ++it) { - if (!(tmp = dynamic_cast(*it))) continue; - int x, y; - - if (prev_widget) - x = prev_widget->_rect.x() + prev_widget->_rect.width() + _bevel_width; - else - x = _rect.x() + _bevel_width; - y = (tallest - tmp->_rect.height()) / 2 + _bevel_width; +void Widget::setBorderWidth(int w) +{ + assert(w >= 0); + if (!parent()) return; // top-level windows cannot have borders + if (w == borderWidth()) return; // no change + + _borderwidth = w; + XSetWindowBorderWidth(**display, _window, _borderwidth); + + calcDefaultSizes(); + update(); +} - tmp->move(x, y); +void Widget::setMinSize(const Size &s) +{ + _min_size = s; + update(); +} - prev_widget = tmp; - } +void Widget::setMaxSize(const Size &s) +{ + _max_size = s; + update(); +} - internalResize(width, tallest + _bevel_width * 2); +void Widget::setBorderColor(const RenderColor *c) +{ + _bordercolor = c; + XSetWindowBorder(**otk::display, _window, + c ? c->pixel() : BlackPixel(**otk::display, _screen)); } -void OtkWidget::adjustVert(void) +void Widget::setBevel(int b) { - if (_children.size() == 0) - return; + _bevel = b; + calcDefaultSizes(); + layout(); +} - OtkWidget *tmp; - OtkBaseWidgetList::iterator it, end = _children.end(); +void Widget::layout() +{ + if (_direction == Horizontal) + layoutHorz(); + else + layoutVert(); +} - int widest = 0; - int height = _bevel_width; - OtkBaseWidgetList stretchable; +void Widget::layoutHorz() +{ + std::list::iterator it, end; + + // work with just the visible children + std::list visible = _children; + for (it = visible.begin(), end = visible.end(); it != end;) { + std::list::iterator next = it; ++next; + if (!(*it)->visible()) + visible.erase(it); + it = next; + } - for (it = _children.begin(); it != end; ++it) { - if (!(tmp = dynamic_cast(*it))) continue; - if (tmp->isStretchableHorz()) - tmp->setWidth(_rect.width() > _bevel_width * 2 ? - _rect.width() - _bevel_width * 2 : _bevel_width); - if (tmp->isStretchableVert()) - stretchable.push_back(tmp); - else - height += tmp->_rect.height() + _bevel_width; + if (visible.empty()) return; + + if ((unsigned)(_borderwidth * 2 + _bevel * 2) > _area.width() || + (unsigned)(_borderwidth * 2 + _bevel * 2) > _area.height()) + return; // not worth laying anything out! + + int x, y; unsigned int w, h; // working area + x = y = _bevel; + w = _area.width() - _borderwidth * 2 - _bevel * 2; + h = _area.height() - _borderwidth * 2 - _bevel * 2; + + int free = w - (visible.size() - 1) * _bevel; + if (free < 0) free = 0; + unsigned int each; + + std::list adjustable = visible; + + // find the 'free' space, and how many children will be using it + for (it = adjustable.begin(), end = adjustable.end(); it != end;) { + std::list::iterator next = it; ++next; + free -= (*it)->minSize().width(); + if (free < 0) free = 0; + if ((*it)->maxSize().width() - (*it)->minSize().width() <= 0) + adjustable.erase(it); + it = next; + } + // some widgets may have max widths that restrict them, find the 'true' + // amount of free space after these widgets are not included + if (!adjustable.empty()) { + do { + each = free / adjustable.size(); + for (it = adjustable.begin(), end = adjustable.end(); it != end;) { + std::list::iterator next = it; ++next; + unsigned int m = (*it)->maxSize().width() - (*it)->minSize().width(); + if (m > 0 && m < each) { + free -= m; + if (free < 0) free = 0; + adjustable.erase(it); + break; // if one is found to be fixed, then the free space needs to + // change, and the rest need to be reexamined + } + it = next; + } + } while (it != end && !adjustable.empty()); + } - if (tmp->_rect.width() > widest) - widest = tmp->_rect.width(); + // place/size the widgets + if (!adjustable.empty()) + each = free / adjustable.size(); + else + each = 0; + for (it = visible.begin(), end = visible.end(); it != end; ++it) { + unsigned int w; + // is the widget adjustable? + std::list::const_iterator + found = std::find(adjustable.begin(), adjustable.end(), *it); + if (found != adjustable.end()) { + // adjustable + w = (*it)->minSize().width() + each; + } else { + // fixed + w = (*it)->minSize().width(); + } + // align it vertically + int yy = y; + unsigned int hh = std::max(std::min(h, (*it)->_max_size.height()), + (*it)->_min_size.height()); + if (hh < h) { + switch(_alignment) { + case RenderStyle::RightBottomJustify: + yy += h - hh; + break; + case RenderStyle::CenterJustify: + yy += (h - hh) / 2; + break; + case RenderStyle::LeftTopJustify: + break; + } + } + (*it)->internal_moveresize(x, yy, w, hh); + (*it)->render(); + (*it)->layout(); + x += w + _bevel; } +} - if (stretchable.size() > 0) { - OtkBaseWidgetList::iterator str_it = stretchable.begin(), - str_end = stretchable.end(); +void Widget::layoutVert() +{ + std::list::iterator it, end; + + // work with just the visible children + std::list visible = _children; + for (it = visible.begin(), end = visible.end(); it != end;) { + std::list::iterator next = it; ++next; + if (!(*it)->visible()) + visible.erase(it); + it = next; + } - int str_height = _rect.height() - height / stretchable.size(); + if (visible.empty()) return; + + if ((unsigned)(_borderwidth * 2 + _bevel * 2) > _area.width() || + (unsigned)(_borderwidth * 2 + _bevel * 2) > _area.height()) + return; // not worth laying anything out! + + int x, y; unsigned int w, h; // working area + x = y = _bevel; + w = _area.width() - _borderwidth * 2 - _bevel * 2; + h = _area.height() - _borderwidth * 2 - _bevel * 2; + + int free = h - (visible.size() - 1) * _bevel; + if (free < 0) free = 0; + unsigned int each; + + std::list adjustable = visible; + + // find the 'free' space, and how many children will be using it + for (it = adjustable.begin(), end = adjustable.end(); it != end;) { + std::list::iterator next = it; ++next; + free -= (*it)->minSize().height(); + if (free < 0) free = 0; + if ((*it)->maxSize().height() - (*it)->minSize().height() <= 0) + adjustable.erase(it); + it = next; + } + // some widgets may have max heights that restrict them, find the 'true' + // amount of free space after these widgets are not included + if (!adjustable.empty()) { + do { + each = free / adjustable.size(); + for (it = adjustable.begin(), end = adjustable.end(); it != end;) { + std::list::iterator next = it; ++next; + unsigned int m = (*it)->maxSize().height() - (*it)->minSize().height(); + if (m > 0 && m < each) { + free -= m; + if (free < 0) free = 0; + adjustable.erase(it); + break; // if one is found to be fixed, then the free space needs to + // change, and the rest need to be reexamined + } + it = next; + } + } while (it != end && !adjustable.empty()); + } - for (; str_it != str_end; ++str_it) - (*str_it)->setHeight(str_height > _bevel_width ? - str_height - _bevel_width : _bevel_width); + // place/size the widgets + if (!adjustable.empty()) + each = free / adjustable.size(); + else + each = 0; + for (it = visible.begin(), end = visible.end(); it != end; ++it) { + unsigned int h; + // is the widget adjustable? + std::list::const_iterator + found = std::find(adjustable.begin(), adjustable.end(), *it); + if (found != adjustable.end()) { + // adjustable + h = (*it)->minSize().height() + each; + } else { + // fixed + h = (*it)->minSize().height(); + } + // align it horizontally + int xx = x; + unsigned int ww = std::max(std::min(w, (*it)->_max_size.width()), + (*it)->_min_size.width()); + if (ww < w) { + switch(_alignment) { + case RenderStyle::RightBottomJustify: + xx += w - ww; + break; + case RenderStyle::CenterJustify: + xx += (w - ww) / 2; + break; + case RenderStyle::LeftTopJustify: + break; + } + } + (*it)->internal_moveresize(xx, y, ww, h); + (*it)->render(); + (*it)->layout(); + y += h + _bevel; } +} - OtkWidget *prev_widget = 0; +void Widget::render() +{ + if (!_texture || !_dirty) return; + if ((unsigned)_borderwidth * 2 > _area.width() || + (unsigned)_borderwidth * 2 > _area.height()) + return; // no surface to draw on + + Surface *s = new Surface(_screen, Size(_area.width() - _borderwidth * 2, + _area.height() - _borderwidth * 2)); + display->renderControl(_screen)->drawBackground(*s, *_texture); - for (it = _children.begin(); it != end; ++it) { - if (!(tmp = dynamic_cast(*it))) continue; - int x, y; + renderForeground(*s); // for inherited types to render onto the _surface - if (prev_widget) - y = prev_widget->_rect.y() + prev_widget->_rect.height() + _bevel_width; - else - y = _rect.y() + _bevel_width; - x = (widest - tmp->_rect.width()) / 2 + _bevel_width; + XSetWindowBackgroundPixmap(**display, _window, s->pixmap()); + XClearWindow(**display, _window); - tmp->move(x, y); + // delete the old surface *after* its pixmap isn't in use anymore + if (_surface) delete _surface; - prev_widget = tmp; - } + _surface = s; - internalResize(widest + _bevel_width * 2, height); + _dirty = false; } -void OtkWidget::update(void) +void Widget::renderChildren() { - if (_dirty) - adjust(); - - OtkBaseWidget::update(); + std::list::iterator it, end = _children.end(); + for (it = _children.begin(); it != end; ++it) + (*it)->render(); } -void OtkWidget::internalResize(int w, int h) +void Widget::exposeHandler(const XExposeEvent &e) { - assert(w > 0 && h > 0); - -// if (! _fixed_width && ! _fixed_height) - resize(w, h); -// else if (! _fixed_width) -// resize(w, _rect.height()); -// else if (! _fixed_height) -// resize(_rect.width(), h); + EventHandler::exposeHandler(e); + XClearArea(**display, _window, e.x, e.y, e.width, e.height, false); } -void OtkWidget::setEventDispatcher(OtkEventDispatcher *disp) +void Widget::configureHandler(const XConfigureEvent &e) { - if (_event_dispatcher) - _event_dispatcher->clearHandler(_window); - _event_dispatcher = disp; - _event_dispatcher->registerHandler(_window, this); + if (_ignore_config) { + _ignore_config--; + } else { + XEvent ev; + ev.xconfigure.width = e.width; + ev.xconfigure.height = e.height; + while (XCheckTypedWindowEvent(**display, window(), ConfigureNotify, &ev)); + + if (!((unsigned)ev.xconfigure.width == area().width() && + (unsigned)ev.xconfigure.height == area().height())) { + _area = Rect(_area.position(), Size(e.width, e.height)); + update(); + } + } } - }