mirror of
https://github.com/edeproject/ede.git
synced 2023-08-10 21:13:03 +03:00
2405 lines
71 KiB
C++
2405 lines
71 KiB
C++
//
|
|
// PDecor.cc for pekwm
|
|
// Copyright © 2004-2009 Claes Nästén <me@pekdon.net>
|
|
//
|
|
// This program is licensed under the GNU GPL.
|
|
// See the LICENSE file for more information.
|
|
//
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif // HAVE_CONFIG_H
|
|
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <cwchar>
|
|
|
|
extern "C" {
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#ifdef HAVE_SHAPE
|
|
#include <X11/extensions/shape.h>
|
|
#endif // HAVE_SHAPE
|
|
}
|
|
|
|
#include "Compat.hh"
|
|
#include "Config.hh"
|
|
#include "PWinObj.hh"
|
|
#include "PFont.hh"
|
|
#include "PDecor.hh"
|
|
#include "PScreen.hh"
|
|
#include "PTexture.hh"
|
|
#include "PTexturePlain.hh" // PTextureSolid
|
|
#include "ActionHandler.hh"
|
|
#include "ScreenResources.hh"
|
|
#include "StatusWindow.hh"
|
|
#include "KeyGrabber.hh"
|
|
#include "Theme.hh"
|
|
#include "PixmapHandler.hh"
|
|
#include "Workspaces.hh"
|
|
|
|
using std::cerr;
|
|
using std::endl;
|
|
using std::find;
|
|
using std::list;
|
|
using std::map;
|
|
using std::mem_fun;
|
|
using std::string;
|
|
using std::vector;
|
|
using std::swprintf;
|
|
|
|
// PDecor::Button
|
|
|
|
//! @brief PDecor::Button constructor
|
|
PDecor::Button::Button(PWinObj *parent, Theme::PDecorButtonData *data, uint width, uint height)
|
|
: PWinObj(),
|
|
_data(data), _state(BUTTON_STATE_UNFOCUSED),
|
|
_left(_data->isLeft())
|
|
{
|
|
_parent = parent;
|
|
|
|
_gm.width = width;
|
|
_gm.height = height;
|
|
|
|
XSetWindowAttributes attr;
|
|
attr.event_mask = EnterWindowMask|LeaveWindowMask;
|
|
attr.override_redirect = True;
|
|
_window =
|
|
XCreateWindow(_dpy, _parent->getWindow(),
|
|
-_gm.width, -_gm.height, _gm.width, _gm.height, 0,
|
|
CopyFromParent, InputOutput, CopyFromParent,
|
|
CWEventMask|CWOverrideRedirect, &attr);
|
|
|
|
_bg = ScreenResources::instance()->getPixmapHandler()->getPixmap(_gm.width, _gm.height, PScreen::instance()->getDepth());
|
|
|
|
setBackgroundPixmap(_bg);
|
|
setState(_state);
|
|
}
|
|
|
|
//! @brief PDecor::Button destructor
|
|
PDecor::Button::~Button(void)
|
|
{
|
|
XDestroyWindow(_dpy, _window);
|
|
ScreenResources::instance()->getPixmapHandler()->returnPixmap(_bg);
|
|
}
|
|
|
|
//! @brief Searches the PDecorButtonData for an action matching ev
|
|
ActionEvent*
|
|
PDecor::Button::findAction(XButtonEvent *ev)
|
|
{
|
|
list<ActionEvent>::iterator it(_data->begin());
|
|
for (; it != _data->end(); ++it) {
|
|
if (it->mod == ev->state && it->sym == ev->button)
|
|
return &*it;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//! @brief Sets the state of the button
|
|
void
|
|
PDecor::Button::setState(ButtonState state)
|
|
{
|
|
if (state == BUTTON_STATE_NO) {
|
|
return;
|
|
}
|
|
|
|
// Only update if we don't hoover, we want to be able to turn back
|
|
if (state != BUTTON_STATE_HOVER) {
|
|
_state = state;
|
|
}
|
|
|
|
PTexture *texture = _data->getTexture(state);
|
|
if (texture) {
|
|
texture->render(_bg, 0, 0, _gm.width, _gm.height);
|
|
|
|
#ifdef HAVE_SHAPE
|
|
// Get shape mask
|
|
bool need_free;
|
|
Pixmap shape = _data->getTexture(state)->getMask(0, 0, need_free);
|
|
if (shape != None) {
|
|
XShapeCombineMask(_dpy, _window, ShapeBounding, 0, 0, shape, ShapeSet);
|
|
if (need_free) {
|
|
ScreenResources::instance()->getPixmapHandler()->returnPixmap(shape);
|
|
}
|
|
} else {
|
|
XRectangle rect;
|
|
rect.x = 0; rect.y = 0;
|
|
rect.width = _gm.width;
|
|
rect.height = _gm.height;
|
|
XShapeCombineRectangles(_dpy, _window, ShapeBounding,
|
|
0, 0, &rect, 1, ShapeSet, YXBanded);
|
|
}
|
|
#endif // HAVE_SHAPE
|
|
clear();
|
|
}
|
|
}
|
|
|
|
//! @brief Update visible version of title
|
|
void
|
|
PDecor::TitleItem::updateVisible(void) {
|
|
// Start with empty string
|
|
_visible = L"";
|
|
|
|
// Add client info to title
|
|
if ((_info != 0)
|
|
&& ((_info != INFO_ID) || Config::instance()->isShowClientID())) {
|
|
_visible.append(L"[");
|
|
|
|
if (infoIs(INFO_ID) && Config::instance()->isShowClientID()) {
|
|
_visible.append(Util::to_wide_str(Util::to_string<uint>(_id)));
|
|
}
|
|
if (infoIs(INFO_MARKED)) {
|
|
_visible.append(L"M");
|
|
}
|
|
|
|
_visible.append(L"] ");
|
|
}
|
|
|
|
// Add title
|
|
if (_user.size() > 0) {
|
|
_visible.append(_user);
|
|
} else if (_custom.size() > 0) {
|
|
_visible.append(_custom);
|
|
} else {
|
|
_visible.append(_real);
|
|
}
|
|
|
|
// Add client number to title
|
|
if (_count > 0) {
|
|
_visible.append(Util::to_wide_str(Config::instance()->getClientUniqueNamePre()));
|
|
_visible.append(Util::to_wide_str(Util::to_string(_count)));
|
|
_visible.append(Util::to_wide_str(Config::instance()->getClientUniqueNamePost()));
|
|
}
|
|
}
|
|
|
|
// PDecor
|
|
|
|
const string PDecor::DEFAULT_DECOR_NAME = string("DEFAULT");
|
|
const string PDecor::DEFAULT_DECOR_NAME_BORDERLESS = string("BORDERLESS");
|
|
const string PDecor::DEFAULT_DECOR_NAME_TITLEBARLESS = string("TITLEBARLESS");
|
|
|
|
list<PDecor*> PDecor::_pdecor_list = list<PDecor*>();
|
|
|
|
//! @brief PDecor constructor
|
|
//! @param dpy Display
|
|
//! @param theme Theme
|
|
//! @param decor_name String, if not DEFAULT_DECOR_NAME sets _decor_name_override
|
|
PDecor::PDecor(Theme *theme,
|
|
const std::string decor_name, const Window child_window)
|
|
: PWinObj(),
|
|
_theme(theme),_decor_name(decor_name),
|
|
_child(0), _button(0), _button_press_win(None),
|
|
_pointer_x(0), _pointer_y(0),
|
|
_click_x(0), _click_y(0),
|
|
_decor_cfg_keep_empty(false), _decor_cfg_child_move_overloaded(false),
|
|
_decor_cfg_bpr_replay_pointer(false),
|
|
_decor_cfg_bpr_al_child(MOUSE_ACTION_LIST_CHILD_OTHER),
|
|
_decor_cfg_bpr_al_title(MOUSE_ACTION_LIST_TITLE_OTHER),
|
|
_maximized_vert(false), _maximized_horz(false),
|
|
_fullscreen(false), _skip(0), _data(0),
|
|
_border(true), _titlebar(true), _shaded(false),
|
|
_need_shape(false), _need_client_shape(false),
|
|
_dirty_resized(true), _real_height(1),
|
|
_title_wo(), _title_bg(None),
|
|
_title_active(0), _titles_left(0), _titles_right(1)
|
|
{
|
|
if (_decor_name != PDecor::DEFAULT_DECOR_NAME) {
|
|
_decor_name_override = _decor_name;
|
|
}
|
|
|
|
// we be reset in loadDecor later on, inlines using the _data used before
|
|
// loadDecor needs this though
|
|
_data = _theme->getPDecorData(_decor_name);
|
|
if (! _data) {
|
|
_data = _theme->getPDecorData(DEFAULT_DECOR_NAME);
|
|
}
|
|
|
|
CreateWindowParams window_params;
|
|
getParentWindowAttributes(window_params, child_window);
|
|
createParentWindow(window_params);
|
|
if (window_params.mask & CWColormap) {
|
|
window_params.depth = PScreen::instance()->getDepth();
|
|
window_params.visual = PScreen::instance()->getVisual()->getXVisual();
|
|
window_params.attr.colormap = PScreen::instance()->getColormap();
|
|
}
|
|
createTitle(window_params);
|
|
createBorder(window_params);
|
|
|
|
// sets buttons etc up
|
|
loadDecor();
|
|
|
|
// map title and border windows
|
|
XMapSubwindows(_dpy, _window);
|
|
|
|
_pdecor_list.push_back(this);
|
|
}
|
|
|
|
/**
|
|
* Create window attributes
|
|
*/
|
|
void
|
|
PDecor::getParentWindowAttributes(CreateWindowParams ¶ms,
|
|
Window child_window)
|
|
{
|
|
params.mask = CWOverrideRedirect|CWEventMask|CWBorderPixel|CWBackPixel;
|
|
params.depth = CopyFromParent;
|
|
params.visual = CopyFromParent;
|
|
params.attr.override_redirect = True;
|
|
params.attr.border_pixel = 0;
|
|
params.attr.background_pixel = 0;
|
|
|
|
if (child_window != None) {
|
|
getChildWindowAttributes(params, child_window);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get window attributes from child window
|
|
*/
|
|
void
|
|
PDecor::getChildWindowAttributes(CreateWindowParams ¶ms,
|
|
Window child_window)
|
|
{
|
|
XWindowAttributes attr;
|
|
Status status = XGetWindowAttributes(_dpy, child_window, &attr);
|
|
if (status != BadDrawable && status != BadWindow && attr.depth == 32) {
|
|
params.mask |= CWColormap;
|
|
params.depth = attr.depth;
|
|
params.visual = attr.visual;
|
|
params.attr.colormap = XCreateColormap(_dpy,
|
|
PScreen::instance()->getRoot(),
|
|
params.visual, AllocNone);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create container window.
|
|
*/
|
|
void
|
|
PDecor::createParentWindow(CreateWindowParams ¶ms)
|
|
{
|
|
params.attr.event_mask = ButtonPressMask|ButtonReleaseMask|
|
|
ButtonMotionMask|EnterWindowMask|SubstructureRedirectMask|
|
|
SubstructureNotifyMask;
|
|
_window = XCreateWindow(_dpy, PScreen::instance()->getRoot(),
|
|
_gm.x, _gm.y, _gm.width, _gm.height, 0,
|
|
params.depth, InputOutput, params.visual,
|
|
params.mask, ¶ms.attr);
|
|
}
|
|
|
|
/**
|
|
* Create title window.
|
|
*/
|
|
void
|
|
PDecor::createTitle(CreateWindowParams ¶ms)
|
|
{
|
|
params.attr.event_mask = ButtonPressMask|ButtonReleaseMask|
|
|
ButtonMotionMask|EnterWindowMask;
|
|
Window title = XCreateWindow(_dpy, _window,
|
|
borderLeft(), borderTop(), 1, 1, 0,
|
|
params.depth, InputOutput, params.visual,
|
|
params.mask, ¶ms.attr);
|
|
_title_wo.setWindow(title);
|
|
addChildWindow(_title_wo.getWindow());
|
|
}
|
|
|
|
/**
|
|
* Create border windows
|
|
*/
|
|
void
|
|
PDecor::createBorder(CreateWindowParams ¶ms)
|
|
{
|
|
params.attr.event_mask = ButtonPressMask|ButtonReleaseMask|
|
|
ButtonMotionMask|EnterWindowMask;
|
|
|
|
ScreenResources *sr = ScreenResources::instance();
|
|
for (uint i = 0; i < BORDER_NO_POS; ++i) {
|
|
params.attr.cursor = sr->getCursor(ScreenResources::CursorType(i));
|
|
|
|
_border_win[i] =
|
|
XCreateWindow(_dpy, _window, -1, -1, 1, 1, 0,
|
|
params.depth, InputOutput, params.visual,
|
|
params.mask|CWCursor, ¶ms.attr);
|
|
_border_pos_map[BorderPosition(i)] = None;
|
|
addChildWindow(_border_win[i]);
|
|
}
|
|
}
|
|
|
|
//! @brief PDecor destructor
|
|
PDecor::~PDecor(void)
|
|
{
|
|
_pdecor_list.remove(this);
|
|
|
|
if (_child_list.size() > 0) {
|
|
while (_child_list.size() != 0) {
|
|
removeChild(_child_list.back(), false); // Don't call delete this.
|
|
}
|
|
}
|
|
|
|
// Make things look smoother, buttons will be noticed as deleted
|
|
// otherwise. Using X call directly to avoid re-drawing and other
|
|
// special features not required when removing the window.
|
|
XUnmapWindow(_dpy, _window);
|
|
|
|
// free buttons
|
|
unloadDecor();
|
|
|
|
// free border pixmaps
|
|
map<BorderPosition, Pixmap>::iterator it(_border_pos_map.begin());
|
|
for (; it != _border_pos_map.end(); ++it) {
|
|
if (it->second != None) {
|
|
ScreenResources::instance()->getPixmapHandler()->returnPixmap(it->second);
|
|
}
|
|
}
|
|
ScreenResources::instance()->getPixmapHandler()->returnPixmap(_title_bg);
|
|
|
|
removeChildWindow(_title_wo.getWindow());
|
|
XDestroyWindow(_dpy, _title_wo.getWindow());
|
|
for (uint i = 0; i < BORDER_NO_POS; ++i) {
|
|
removeChildWindow(_border_win[i]);
|
|
XDestroyWindow(_dpy, _border_win[i]);
|
|
}
|
|
XDestroyWindow(_dpy, _window);
|
|
}
|
|
|
|
// START - PWinObj interface.
|
|
|
|
//! @brief Map decor and all children
|
|
void
|
|
PDecor::mapWindow(void)
|
|
{
|
|
if (! _mapped) {
|
|
PWinObj::mapWindow();
|
|
for_each(_child_list.begin(), _child_list.end(),
|
|
mem_fun(&PWinObj::mapWindow));
|
|
}
|
|
}
|
|
|
|
//! @brief Maps the window raised
|
|
void
|
|
PDecor::mapWindowRaised(void)
|
|
{
|
|
if (_mapped) {
|
|
return;
|
|
}
|
|
|
|
mapWindow();
|
|
|
|
raise(); // XMapRaised wouldn't preserver layers
|
|
}
|
|
|
|
//! @brief Unmaps decor and all children
|
|
void
|
|
PDecor::unmapWindow(void)
|
|
{
|
|
if (_mapped) {
|
|
if (_iconified) {
|
|
for_each(_child_list.begin(), _child_list.end(),
|
|
mem_fun(&PWinObj::iconify));
|
|
} else {
|
|
for_each(_child_list.begin(), _child_list.end(),
|
|
mem_fun(&PWinObj::unmapWindow));
|
|
}
|
|
PWinObj::unmapWindow();
|
|
}
|
|
}
|
|
|
|
//! @brief Moves the decor
|
|
void
|
|
PDecor::move(int x, int y)
|
|
{
|
|
// update real position
|
|
PWinObj::move(x, y);
|
|
if (_child && (_decor_cfg_child_move_overloaded)) {
|
|
_child->move(x + borderLeft(), y + borderTop() + getTitleHeight());
|
|
}
|
|
}
|
|
|
|
//! @brief Resizes the decor, and active child if any
|
|
void
|
|
PDecor::resize(uint width, uint height)
|
|
{
|
|
// If shaded, don't resize to the size specified just update width
|
|
// and set _real_height to height
|
|
if (_shaded) {
|
|
_real_height = height;
|
|
|
|
height = getTitleHeight();
|
|
// Shading in non full width title mode will make border go away
|
|
if (! _data->getTitleWidthMin()) {
|
|
height += borderTop() + borderBottom();
|
|
}
|
|
}
|
|
|
|
PWinObj::resize(width, height);
|
|
|
|
// Update size before moving and shaping the rest as shaping
|
|
// depends on the child window
|
|
if (_child) {
|
|
_child->resize(getChildWidth(), getChildHeight());
|
|
}
|
|
|
|
// place / resize title and border
|
|
resizeTitle();
|
|
placeBorder();
|
|
|
|
// render title and border
|
|
_dirty_resized = true;
|
|
|
|
// Set and apply shape on window, all parts of the border can now
|
|
// be shaped.
|
|
setBorderShape();
|
|
applyBorderShape();
|
|
|
|
renderTitle();
|
|
renderBorder();
|
|
|
|
_dirty_resized = false;
|
|
}
|
|
|
|
//! @brief Move and resize window.
|
|
void
|
|
PDecor::moveResize(int x, int y, uint width, uint height)
|
|
{
|
|
// If shaded, don't resize to the size specified just update width
|
|
// and set _real_height to height
|
|
if (_shaded) {
|
|
_real_height = height;
|
|
|
|
height = getTitleHeight();
|
|
// Shading in non full width title mode will make border go away
|
|
if (! _data->getTitleWidthMin()) {
|
|
height += borderTop() + borderBottom();
|
|
}
|
|
}
|
|
|
|
PWinObj::moveResize(x, y, width, height);
|
|
|
|
// Update size before moving and shaping the rest as shaping
|
|
// depends on the child window
|
|
if (_child) {
|
|
_child->moveResize(x + borderLeft(), y + borderTop() + getTitleHeight(),
|
|
getChildWidth(), getChildHeight());
|
|
|
|
// The client window may have its window gravity set to something different
|
|
// than NorthWestGravity (see Xlib manual chapter 3.2.3). Therefore the
|
|
// X server may not move its top left corner along with the decoration.
|
|
// We correct these cases by calling alignChild(). It is called only for
|
|
// _child and not all members of _child_list, because activateChild.*()
|
|
// does it itself.
|
|
alignChild(_child);
|
|
}
|
|
|
|
// Place and resize title and border
|
|
resizeTitle();
|
|
placeBorder();
|
|
|
|
// render title and border
|
|
_dirty_resized = true;
|
|
|
|
// Apply shape on window, all parts of the border can now be shaped.
|
|
setBorderShape();
|
|
applyBorderShape();
|
|
|
|
renderTitle();
|
|
renderBorder();
|
|
|
|
_dirty_resized = false;
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
PDecor::resizeTitle(void)
|
|
{
|
|
if (getTitleHeight()) {
|
|
_title_wo.resize(calcTitleWidth(), getTitleHeight());
|
|
calcTabsWidth();
|
|
}
|
|
|
|
// place buttons, also updates title information
|
|
placeButtons();
|
|
}
|
|
|
|
//! @brief Raises the window, taking _layer into account
|
|
void
|
|
PDecor::raise(void)
|
|
{
|
|
Workspaces::instance()->raise(this);
|
|
Workspaces::instance()->updateClientStackingList();
|
|
}
|
|
|
|
//! @brief Lowers the window, taking _layer into account
|
|
void
|
|
PDecor::lower(void)
|
|
{
|
|
Workspaces::instance()->lower(this);
|
|
Workspaces::instance()->updateClientStackingList();
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
PDecor::setFocused(bool focused)
|
|
{
|
|
if (_focused != focused) { // save repaints
|
|
PWinObj::setFocused(focused);
|
|
|
|
renderTitle();
|
|
renderButtons();
|
|
|
|
renderBorder();
|
|
setBorderShape();
|
|
applyBorderShape();
|
|
}
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
PDecor::setWorkspace(uint workspace)
|
|
{
|
|
if (workspace != NET_WM_STICKY_WINDOW) {
|
|
if (workspace >= Workspaces::instance ()->size ()) {
|
|
#ifdef DEBUG
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "PDecor(" << this << ")::setWorkspace(" << workspace << ")"
|
|
<< endl << " *** workspace > number of workspaces:"
|
|
<< Workspaces::instance ()->size () << endl;
|
|
#endif // DEBUG
|
|
workspace = Workspaces::instance()->size() - 1;
|
|
}
|
|
_workspace = workspace;
|
|
}
|
|
|
|
list<PWinObj*>::iterator it (_child_list.begin ());
|
|
for (; it != _child_list.end (); ++it) {
|
|
(*it)->setWorkspace (workspace);
|
|
}
|
|
|
|
if (! _mapped && ! _iconified) {
|
|
if (_sticky || (_workspace == Workspaces::instance()->getActive())) {
|
|
mapWindow();
|
|
}
|
|
} else if (! _sticky && (_workspace != Workspaces::instance()->getActive())) {
|
|
unmapWindow();
|
|
}
|
|
}
|
|
|
|
//! @brief Gives decor input focus, fails if not mapped or not visible
|
|
void
|
|
PDecor::giveInputFocus(void)
|
|
{
|
|
if (_mapped && _child) {
|
|
_child->giveInputFocus();
|
|
} else {
|
|
#ifdef DEBUG
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "PDecor::giveInputFocus(" << this << ")" << endl << " *** reverting to root" << endl;
|
|
#endif // DEBUG
|
|
PWinObj::getRootPWinObj()->giveInputFocus();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle button press events.
|
|
*/
|
|
ActionEvent*
|
|
PDecor::handleButtonPress(XButtonEvent *ev)
|
|
{
|
|
ActionEvent *ae = 0;
|
|
list<ActionEvent> *actions = 0;
|
|
|
|
// Remove state modifiers from event
|
|
PScreen::stripStateModifiers(&ev->state);
|
|
PScreen::stripButtonModifiers(&ev->state);
|
|
|
|
// Try to do something about frame buttons
|
|
if (ev->subwindow != None && (_button = findButton(ev->subwindow)) != 0) {
|
|
ae = handleButtonPressButton(ev, _button);
|
|
|
|
} else {
|
|
// Record position, used in the motion handler when doing a window move.
|
|
_click_x = _gm.x;
|
|
_click_y = _gm.y;
|
|
_pointer_x = ev->x_root;
|
|
_pointer_y = ev->y_root;
|
|
|
|
// Allow us to get clicks from anywhere on the window.
|
|
if (_decor_cfg_bpr_replay_pointer) {
|
|
XAllowEvents(_dpy, ReplayPointer, CurrentTime);
|
|
}
|
|
|
|
if (ev->window == _child->getWindow()
|
|
|| (ev->state == 0 && ev->subwindow == _child->getWindow())) {
|
|
// Clicks on the child window
|
|
// NOTE: If the we're matching against the subwindow we need to make
|
|
// sure that the state is 0, meaning we didn't have any modifier
|
|
// because if we don't care about the modifier we'll get two actions
|
|
// performed when using modifiers.
|
|
_button_press_win = _child->getWindow();
|
|
actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_child);
|
|
|
|
} else if (_title_wo == ev->window) {
|
|
// Clicks on the decor title
|
|
_button_press_win = ev->window;
|
|
actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_title);
|
|
|
|
} else {
|
|
// Clicks on the decor border, default case. Try both window and sub-window.
|
|
uint pos = getBorderPosition(ev->window);
|
|
if (pos != BORDER_NO_POS) {
|
|
_button_press_win = ev->window;
|
|
actions = Config::instance()->getBorderListFromPosition(pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! ae && actions) {
|
|
ae = ActionHandler::findMouseAction(ev->button, ev->state, MOUSE_EVENT_PRESS, actions);
|
|
}
|
|
|
|
return ae;
|
|
}
|
|
|
|
/**
|
|
* Handle button press events pressing decor buttons.
|
|
*/
|
|
ActionEvent*
|
|
PDecor::handleButtonPressButton(XButtonEvent *ev, PDecor::Button *button)
|
|
{
|
|
// Keep track of pressed button.
|
|
_button->setState(BUTTON_STATE_PRESSED);
|
|
|
|
ActionEvent *ae = _button->findAction(ev);
|
|
|
|
// if the button is used for resizing, we don't want to wait for release
|
|
if (ae && ae->isOnlyAction(ACTION_RESIZE)) {
|
|
_button->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED);
|
|
_button = 0;
|
|
} else {
|
|
ae = 0;
|
|
}
|
|
|
|
return ae;
|
|
}
|
|
|
|
/**
|
|
* Handle button release events.
|
|
*/
|
|
ActionEvent*
|
|
PDecor::handleButtonRelease(XButtonEvent *ev)
|
|
{
|
|
ActionEvent *ae = 0;
|
|
list<ActionEvent> *actions = 0;
|
|
MouseEventType mb = MOUSE_EVENT_RELEASE;
|
|
|
|
// Remove state modifiers from event
|
|
PScreen::stripStateModifiers(&ev->state);
|
|
PScreen::stripButtonModifiers(&ev->state);
|
|
|
|
// handle titlebar buttons
|
|
if (_button) {
|
|
ae = handleButtonReleaseButton(ev, _button);
|
|
|
|
} else {
|
|
// Allow us to get clicks from anywhere on the window.
|
|
if (_decor_cfg_bpr_replay_pointer) {
|
|
XAllowEvents(_dpy, ReplayPointer, CurrentTime);
|
|
}
|
|
|
|
// clicks on the child window
|
|
if (ev->window == _child->getWindow()
|
|
|| (ev->state == 0 && ev->subwindow == _child->getWindow())) {
|
|
// NOTE: If the we're matching against the subwindow we need to make
|
|
// sure that the state is 0, meaning we didn't have any modifier
|
|
// because if we don't care about the modifier we'll get two actions
|
|
// performed when using modifiers.
|
|
if (_button_press_win == _child->getWindow()) {
|
|
actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_child);
|
|
}
|
|
|
|
} else if (_title_wo == ev->window) {
|
|
if (_button_press_win == ev->window) {
|
|
// Handle clicks on the decor title, checking double clicks first.
|
|
if (PScreen::instance()->isDoubleClick(ev->window, ev->button - 1, ev->time,
|
|
Config::instance()->getDoubleClickTime())) {
|
|
PScreen::instance()->setLastClickID(ev->window);
|
|
PScreen::instance()->setLastClickTime(ev->button - 1, 0);
|
|
mb = MOUSE_EVENT_DOUBLE;
|
|
} else {
|
|
PScreen::instance()->setLastClickID(ev->window);
|
|
PScreen::instance()->setLastClickTime(ev->button - 1, ev->time);
|
|
}
|
|
|
|
actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_title);
|
|
}
|
|
} else {
|
|
// Clicks on the decor border, check subwindow then window.
|
|
uint pos = getBorderPosition(ev->window);
|
|
if (pos != BORDER_NO_POS && (_button_press_win == ev->window)) {
|
|
actions = Config::instance()->getBorderListFromPosition(pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! ae && actions) {
|
|
ae = ActionHandler::findMouseAction(ev->button, ev->state, mb, actions);
|
|
}
|
|
|
|
return ae;
|
|
}
|
|
|
|
/**
|
|
* Handle button release events when button is in pressed state.
|
|
*/
|
|
ActionEvent*
|
|
PDecor::handleButtonReleaseButton(XButtonEvent *ev, PDecor::Button *button)
|
|
{
|
|
// First restore the pressed buttons state
|
|
_button->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED);
|
|
|
|
ActionEvent *ae = 0;
|
|
|
|
// Then see if the button was released over ( to execute an action )
|
|
if (*_button == ev->subwindow) {
|
|
ae = _button->findAction(ev);
|
|
|
|
// This is a little hack, resizing isn't wanted on both press and release
|
|
if (ae && ae->isOnlyAction(ACTION_RESIZE)) {
|
|
ae = 0;
|
|
}
|
|
}
|
|
|
|
_button = 0;
|
|
|
|
return ae;
|
|
}
|
|
|
|
//! @brief
|
|
ActionEvent*
|
|
PDecor::handleMotionEvent(XMotionEvent *ev)
|
|
{
|
|
uint button = PScreen::instance()->getButtonFromState(ev->state);
|
|
return ActionHandler::findMouseAction(button, ev->state,
|
|
MOUSE_EVENT_MOTION,
|
|
Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_OTHER));
|
|
}
|
|
|
|
/**
|
|
* Handle enter event, find action and toggle hoover state if enter
|
|
* was on a button.
|
|
*/
|
|
ActionEvent*
|
|
PDecor::handleEnterEvent(XCrossingEvent *ev)
|
|
{
|
|
PDecor::Button *button = findButton(ev->window);
|
|
if (button) {
|
|
button->setState(BUTTON_STATE_HOVER);
|
|
}
|
|
|
|
return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER,
|
|
Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_OTHER));
|
|
}
|
|
|
|
/**
|
|
* Handle leave event, find action and toggle hoover state if leave
|
|
* was from a button.
|
|
*/
|
|
ActionEvent*
|
|
PDecor::handleLeaveEvent(XCrossingEvent *ev)
|
|
{
|
|
PDecor::Button *button = findButton(ev->window);
|
|
if (button) {
|
|
button->setState(button->getState());
|
|
}
|
|
|
|
return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_LEAVE,
|
|
Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_OTHER));
|
|
}
|
|
|
|
// END - PWinObj interface.
|
|
|
|
//! @brief Adds a children in another decor to this decor
|
|
void
|
|
PDecor::addDecor(PDecor *decor)
|
|
{
|
|
if (this == decor) {
|
|
#ifdef DEBUG
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "PDecor(" << this << ")::addDecor(" << decor << ")" << " *** this == decor" << endl;
|
|
#endif // DEBUG
|
|
return;
|
|
}
|
|
|
|
list<PWinObj*>::iterator it(decor->begin());
|
|
for (; it != decor->end(); ++it) {
|
|
addChild(*it);
|
|
(*it)->setWorkspace(_workspace);
|
|
}
|
|
|
|
decor->_child_list.clear();
|
|
delete decor;
|
|
}
|
|
|
|
//! @brief Sets preferred decor_name
|
|
//! @return True on change, False if unchanged
|
|
bool
|
|
PDecor::setDecor(const std::string &name)
|
|
{
|
|
string new_name(_decor_name_override);
|
|
if (new_name.size() == 0) {
|
|
new_name = name;
|
|
}
|
|
|
|
if (_decor_name == new_name) {
|
|
return false;
|
|
}
|
|
|
|
_decor_name = new_name;
|
|
loadDecor();
|
|
|
|
return true;
|
|
}
|
|
|
|
//! @brief Sets override decor name
|
|
void
|
|
PDecor::setDecorOverride(StateAction sa, const std::string &name)
|
|
{
|
|
if ((sa == STATE_SET) ||
|
|
((sa == STATE_TOGGLE) && (_decor_name_override.size() == 0))) {
|
|
_decor_name_override = name;
|
|
setDecor("");
|
|
|
|
} else if ((sa == STATE_UNSET) ||
|
|
((sa == STATE_TOGGLE) && (_decor_name_override.size() > 0))) {
|
|
_decor_name_override = ""; // .clear() doesn't work with old g++
|
|
updateDecorName();
|
|
}
|
|
}
|
|
|
|
//! @brief Load and update decor.
|
|
void
|
|
PDecor::loadDecor(void)
|
|
{
|
|
unloadDecor();
|
|
|
|
// Get decordata with name.
|
|
_data = _theme->getPDecorData(_decor_name);
|
|
if (! _data) {
|
|
_data = _theme->getPDecorData(DEFAULT_DECOR_NAME);
|
|
}
|
|
assert(_data);
|
|
|
|
// Load decor.
|
|
list<Theme::PDecorButtonData*>::iterator b_it(_data->buttonBegin());
|
|
for (; b_it != _data->buttonEnd(); ++b_it) {
|
|
uint width = std::max(static_cast<uint>(1), (*b_it)->getWidth() ? (*b_it)->getWidth() : getTitleHeight());
|
|
uint height = std::max(static_cast<uint>(1), (*b_it)->getHeight() ? (*b_it)->getHeight() : getTitleHeight());
|
|
|
|
_button_list.push_back(new PDecor::Button(&_title_wo, *b_it, width, height));
|
|
_button_list.back()->mapWindow();
|
|
addChildWindow(_button_list.back()->getWindow());
|
|
}
|
|
|
|
// Update title position.
|
|
if (_data->getTitleWidthMin() == 0) {
|
|
_title_wo.move(borderTopLeft(), borderTop());
|
|
_need_shape = false;
|
|
} else {
|
|
_title_wo.move(0, 0);
|
|
_need_shape = true;
|
|
}
|
|
|
|
// Update child positions.
|
|
list<PWinObj*>::iterator c_it(_child_list.begin());
|
|
for (; c_it != _child_list.end(); ++c_it) {
|
|
alignChild(*c_it);
|
|
}
|
|
|
|
// Make sure it gets rendered correctly.
|
|
_dirty_resized = true;
|
|
_focused = ! _focused;
|
|
setFocused(! _focused);
|
|
_dirty_resized = false;
|
|
|
|
// Update the dimension of the frame.
|
|
if (_child) {
|
|
resizeChild(_child->getWidth(), _child->getHeight());
|
|
}
|
|
|
|
// Child theme change.
|
|
loadTheme();
|
|
}
|
|
|
|
//! @brief Frees resources used by PDecor.
|
|
void
|
|
PDecor::unloadDecor(void)
|
|
{
|
|
// Set active button to 0 as it can not be valid after deleting
|
|
// the current buttons.
|
|
_button = 0;
|
|
|
|
list<PDecor::Button*>::iterator it(_button_list.begin());
|
|
for (; it != _button_list.end(); ++it) {
|
|
removeChildWindow((*it)->getWindow());
|
|
delete *it;
|
|
}
|
|
_button_list.clear();
|
|
}
|
|
|
|
//! @brief
|
|
PDecor::Button*
|
|
PDecor::findButton(Window win)
|
|
{
|
|
list<PDecor::Button*>::iterator it(_button_list.begin());
|
|
for (; it != _button_list.end(); ++it) {
|
|
if (**it == win) {
|
|
return *it;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//! @brief
|
|
PWinObj*
|
|
PDecor::getChildFromPos(int x)
|
|
{
|
|
if (! _child_list.size() || (_child_list.size() != _title_list.size()))
|
|
return 0;
|
|
if (_child_list.size() == 1)
|
|
return _child_list.front();
|
|
|
|
if (x < static_cast<int>(_titles_left)) {
|
|
return _child_list.front();
|
|
} else if (x > static_cast<int>(_title_wo.getWidth() - _titles_right)) {
|
|
return _child_list.back();
|
|
}
|
|
|
|
list<PWinObj*>::iterator c_it(_child_list.begin());
|
|
list<PDecor::TitleItem*>::iterator t_it(_title_list.begin());
|
|
|
|
// FIXME: make getChildFromPos separator aware!
|
|
uint pos = _titles_left, xx = x;
|
|
for (uint i = 0; i < _title_list.size(); ++i, ++t_it, ++c_it) {
|
|
if ((xx >= pos) && (xx <= (pos + (*t_it)->getWidth()))) {
|
|
return *c_it;
|
|
}
|
|
pos += (*t_it)->getWidth();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//! @brief Moves making the child be positioned at x y
|
|
void
|
|
PDecor::moveChild(int x, int y)
|
|
{
|
|
move(x - borderLeft(), y - borderTop() - getTitleHeight());
|
|
}
|
|
|
|
//! @brief Resizes the decor, giving width x height space for the child
|
|
void
|
|
PDecor::resizeChild(uint width, uint height)
|
|
{
|
|
resize(width + borderLeft() + borderRight(),
|
|
height + borderTop() + borderBottom() + getTitleHeight());
|
|
}
|
|
|
|
//! @brief Sets border state of the decor
|
|
void
|
|
PDecor::setBorder(StateAction sa)
|
|
{
|
|
if (! ActionUtil::needToggle(sa, _border)) {
|
|
return;
|
|
}
|
|
|
|
// If we are going to remove the border, we need to check carefully
|
|
// that we don't try to make the window disappear.
|
|
if (! _border) {
|
|
_border = true;
|
|
} else if (! _shaded || _titlebar) {
|
|
_border = false;
|
|
}
|
|
|
|
restackBorder();
|
|
if (! updateDecorName() && _child)
|
|
resizeChild(_child->getWidth(), _child->getHeight());
|
|
}
|
|
|
|
//! @brief Sets titlebar state of the decor
|
|
void
|
|
PDecor::setTitlebar(StateAction sa)
|
|
{
|
|
if (! ActionUtil::needToggle(sa, _titlebar))
|
|
return;
|
|
|
|
// If we are going to remove the titlebar, we need to check carefully
|
|
// that we don't try to make the window disappear.
|
|
if (! _titlebar) {
|
|
_title_wo.mapWindow();
|
|
_titlebar = true;
|
|
} else if (! _shaded || _border) {
|
|
_title_wo.unmapWindow();
|
|
_titlebar = false;
|
|
}
|
|
|
|
// If updateDecorName returns true, it already loaded decor stuff for us.
|
|
if (! updateDecorName() && _child) {
|
|
alignChild(_child);
|
|
resizeChild(_child->getWidth(), _child->getHeight());
|
|
}
|
|
}
|
|
|
|
//! @brief Calculate title height, 0 if titlebar is disabled.
|
|
uint
|
|
PDecor::getTitleHeight(void) const
|
|
{
|
|
if (! _titlebar) {
|
|
return 0;
|
|
}
|
|
|
|
if (_data->isTitleHeightAdapt()) {
|
|
return getFont(getFocusedState(false))->getHeight()
|
|
+ _data->getPad(PAD_UP) + _data->getPad(PAD_DOWN);
|
|
} else {
|
|
return _data->getTitleHeight();
|
|
}
|
|
}
|
|
|
|
//! @brief Adds a child to the decor, reparenting the window
|
|
void
|
|
PDecor::addChild(PWinObj *child, std::list<PWinObj*>::iterator *it)
|
|
{
|
|
child->reparent(this, borderLeft(), borderTop() + getTitleHeight());
|
|
if (it == 0) {
|
|
_child_list.push_back(child);
|
|
} else {
|
|
_child_list.insert(*it, child);
|
|
}
|
|
|
|
updatedChildOrder();
|
|
|
|
// Sync focused state if it is the first child, the child will be
|
|
// activated later on. If there are children here already fit the
|
|
// child into the decor.
|
|
if (_child_list.size() == 1) {
|
|
_focused = ! _focused;
|
|
setFocused(! _focused);
|
|
} else {
|
|
alignChild(child);
|
|
child->resize(getChildWidth(), getChildHeight());
|
|
}
|
|
}
|
|
|
|
//! @brief Removes PWinObj from this PDecor.
|
|
//! @param child PWinObj to remove from the this PDecor.
|
|
//! @param do_delete Wheter to call delete this when empty. (Defaults to true)
|
|
void
|
|
PDecor::removeChild(PWinObj *child, bool do_delete)
|
|
{
|
|
child->reparent(PWinObj::getRootPWinObj(),
|
|
_gm.x + borderLeft(),
|
|
_gm.y + borderTop() + getTitleHeight());
|
|
|
|
list<PWinObj*>::iterator it(find(_child_list.begin(), _child_list.end(), child));
|
|
if (it == _child_list.end()) {
|
|
#ifdef DEBUG
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "PDecor(" << this << ")::removeChild(" << child << ")" << endl
|
|
<< " *** child not in _child_list, bailing out." << endl;
|
|
#endif // DEBUG
|
|
return;
|
|
}
|
|
|
|
it = _child_list.erase(it);
|
|
|
|
if (_child_list.size() > 0) {
|
|
if (_child == child) {
|
|
if (it == _child_list.end()) {
|
|
--it;
|
|
}
|
|
|
|
activateChild(*it);
|
|
}
|
|
|
|
updatedChildOrder();
|
|
|
|
} else if (_decor_cfg_keep_empty) {
|
|
updatedChildOrder();
|
|
|
|
} else if (do_delete) {
|
|
delete this; // no children, and we don't want empty PDecors
|
|
}
|
|
}
|
|
|
|
//! @brief Activates the child, updating size and position
|
|
void
|
|
PDecor::activateChild(PWinObj *child)
|
|
{
|
|
// sync state
|
|
_focusable = child->isFocusable();
|
|
|
|
alignChild(child); // place correct acording to border and title
|
|
child->resize(getChildWidth(), getChildHeight());
|
|
child->raise();
|
|
_child = child;
|
|
|
|
restackBorder();
|
|
|
|
updatedChildOrder();
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
PDecor::getDecorInfo(wchar_t *buf, uint size)
|
|
{
|
|
swprintf(buf, size, L"%dx%d+%d+%d", _gm.width, _gm.height, _gm.x, _gm.y);
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
PDecor::activateChildNum(uint num)
|
|
{
|
|
if (num >= _child_list.size()) {
|
|
#ifdef DEBUG
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "PDecor(" << this << ")::activeChildNum(" << num << ")" << endl
|
|
<< " *** num > _child_list.size()" << endl;
|
|
#endif // DEBUG
|
|
return;
|
|
}
|
|
|
|
list<PWinObj*>::iterator it(_child_list.begin());
|
|
for (uint i = 0; i < num; ++i, ++it);
|
|
|
|
activateChild(*it);
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
PDecor::activateChildRel(int off)
|
|
{
|
|
PWinObj *child = getChildRel(off);
|
|
if (! child) {
|
|
child = _child_list.front();
|
|
}
|
|
activateChild(child);
|
|
}
|
|
|
|
//! @brief Moves the current child off steps
|
|
//! @param off - for left and + for right
|
|
void
|
|
PDecor::moveChildRel(int off)
|
|
{
|
|
PWinObj *child = getChildRel(off);
|
|
if (! child || (child == _child)) {
|
|
return;
|
|
}
|
|
|
|
bool at_begin = (_child == _child_list.front());
|
|
bool at_end = (_child == _child_list.back());
|
|
|
|
// switch place
|
|
_child_list.remove(_child);
|
|
list<PWinObj*>::iterator it(find(_child_list.begin(), _child_list.end(), child));
|
|
if (off > 0) {
|
|
// when wrapping, we want to be able to insert at first place
|
|
if (at_begin || (*it != _child_list.front())) {
|
|
++it;
|
|
}
|
|
|
|
_child_list.insert(it, _child);
|
|
} else {
|
|
if (! at_end && (*it == _child_list.back())) {
|
|
++it;
|
|
}
|
|
_child_list.insert(it, _child);
|
|
}
|
|
|
|
updatedChildOrder();
|
|
}
|
|
|
|
//! @brief
|
|
PWinObj*
|
|
PDecor::getChildRel(int off)
|
|
{
|
|
if ((off == 0) || (_child_list.size() < 2)) {
|
|
#ifdef DEBUG
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "PDecor(" << this << ")::getChildRel(" << off << ")" << endl
|
|
<< " *** off == 0 or not enough children" << endl;
|
|
#endif // DEBUG
|
|
return 0;
|
|
}
|
|
|
|
list<PWinObj*>::iterator it(find(_child_list.begin(), _child_list.end(), _child));
|
|
// if no active child, use first
|
|
if (it == _child_list.end()) {
|
|
it = _child_list.begin();
|
|
}
|
|
|
|
int dir = (off > 0) ? 1 : -1;
|
|
off = abs(off);
|
|
|
|
for (int i = 0; i < off; ++i) {
|
|
if (dir == 1) { // forward
|
|
if (++it == _child_list.end()) {
|
|
it = _child_list.begin();
|
|
}
|
|
|
|
} else { // backward
|
|
if (it == _child_list.begin()) {
|
|
it = _child_list.end();
|
|
}
|
|
--it;
|
|
}
|
|
}
|
|
|
|
return *it;
|
|
}
|
|
|
|
//! @brief Do move of Decor with the mouse.
|
|
//! @param x_root X position of the pointer when event was triggered.
|
|
//! @param y_root Y position of the pointer when event was triggered.
|
|
void
|
|
PDecor::doMove(int x_root, int y_root)
|
|
{
|
|
if (! allowMove()) {
|
|
return;
|
|
}
|
|
|
|
PScreen *scr = PScreen::instance(); // convenience
|
|
StatusWindow *sw = StatusWindow::instance(); // convenience
|
|
|
|
if (! scr->grabPointer(scr->getRoot(), ButtonMotionMask|ButtonReleaseMask,
|
|
ScreenResources::instance()->getCursor(ScreenResources::CURSOR_MOVE))) {
|
|
return;
|
|
}
|
|
|
|
// Get relative position to root
|
|
int x = x_root - _gm.x;
|
|
int y = y_root - _gm.y;
|
|
|
|
bool outline = ! Config::instance()->getOpaqueMove();
|
|
bool center_on_root = Config::instance()->isShowStatusWindowOnRoot();
|
|
EdgeType edge;
|
|
|
|
// grab server, we don't want invert traces
|
|
if (outline) {
|
|
scr->grabServer();
|
|
}
|
|
|
|
wchar_t buf[128];
|
|
getDecorInfo(buf, 128);
|
|
|
|
if (Config::instance()->isShowStatusWindow()) {
|
|
sw->draw(buf, true, center_on_root ? 0 : &_gm);
|
|
sw->mapWindowRaised();
|
|
sw->draw(buf, true, center_on_root ? 0 : &_gm);
|
|
}
|
|
|
|
Geometry last_gm(_gm);
|
|
|
|
XEvent e;
|
|
const long move_mask = ButtonPressMask|ButtonReleaseMask|PointerMotionMask;
|
|
bool exit = false;
|
|
while (! exit) {
|
|
if (outline) {
|
|
drawOutline(_gm);
|
|
}
|
|
XMaskEvent(_dpy, move_mask, &e);
|
|
if (outline) {
|
|
drawOutline(_gm); // clear
|
|
}
|
|
|
|
switch (e.type) {
|
|
case MotionNotify:
|
|
// Flush all pointer motion, no need to redraw and redraw.
|
|
PScreen::removeMotionEvents();
|
|
|
|
_gm.x = e.xmotion.x_root - x;
|
|
_gm.y = e.xmotion.y_root - y;
|
|
checkSnap();
|
|
|
|
if (! outline && _gm != last_gm) {
|
|
last_gm = _gm;
|
|
move(_gm.x, _gm.y);
|
|
}
|
|
|
|
edge = doMoveEdgeFind(e.xmotion.x_root, e.xmotion.y_root);
|
|
if (edge != SCREEN_EDGE_NO) {
|
|
doMoveEdgeAction(&e.xmotion, edge);
|
|
}
|
|
|
|
getDecorInfo(buf, 128);
|
|
if (Config::instance()->isShowStatusWindow()) {
|
|
sw->draw(buf, true, center_on_root ? 0 : &_gm);
|
|
}
|
|
|
|
break;
|
|
case ButtonRelease:
|
|
exit = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Config::instance()->isShowStatusWindow()) {
|
|
sw->unmapWindow();
|
|
}
|
|
|
|
// ungrab the server
|
|
if (outline) {
|
|
move(_gm.x, _gm.y);
|
|
scr->ungrabServer(true);
|
|
}
|
|
|
|
scr->ungrabPointer();
|
|
}
|
|
|
|
//! @brief Matches cordinates against screen edge
|
|
EdgeType
|
|
PDecor::doMoveEdgeFind(int x, int y)
|
|
{
|
|
EdgeType edge = SCREEN_EDGE_NO;
|
|
if (x <= signed(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_LEFT))) {
|
|
edge = SCREEN_EDGE_LEFT;
|
|
} else if (x >= signed(PScreen::instance()->getWidth() -
|
|
Config::instance()->getScreenEdgeSize(SCREEN_EDGE_RIGHT))) {
|
|
edge = SCREEN_EDGE_RIGHT;
|
|
} else if (y <= signed(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_TOP))) {
|
|
edge = SCREEN_EDGE_TOP;
|
|
} else if (y >= signed(PScreen::instance()->getHeight() -
|
|
Config::instance()->getScreenEdgeSize(SCREEN_EDGE_BOTTOM))) {
|
|
edge = SCREEN_EDGE_BOTTOM;
|
|
}
|
|
|
|
return edge;
|
|
}
|
|
|
|
//! @brief Finds and executes action if any
|
|
void
|
|
PDecor::doMoveEdgeAction(XMotionEvent *ev, EdgeType edge)
|
|
{
|
|
ActionEvent *ae;
|
|
|
|
uint button = PScreen::instance()->getButtonFromState(ev->state);
|
|
ae = ActionHandler::findMouseAction(button, ev->state,
|
|
MOUSE_EVENT_ENTER_MOVING,
|
|
Config::instance()->getEdgeListFromPosition(edge));
|
|
|
|
if (ae) {
|
|
ActionPerformed ap(this, *ae);
|
|
ap.type = ev->type;
|
|
ap.event.motion = ev;
|
|
|
|
ActionHandler::instance()->handleAction(ap);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Move and resize window with keybindings.
|
|
*/
|
|
void
|
|
PDecor::doKeyboardMoveResize(void)
|
|
{
|
|
PScreen *scr = PScreen::instance(); // convenience
|
|
StatusWindow *sw = StatusWindow::instance(); // convenience
|
|
|
|
if (! scr->grabPointer(scr->getRoot(), NoEventMask,
|
|
ScreenResources::instance()->getCursor(ScreenResources::CURSOR_MOVE))) {
|
|
return;
|
|
}
|
|
if (! scr->grabKeyboard(scr->getRoot())) {
|
|
scr->ungrabPointer();
|
|
return;
|
|
}
|
|
|
|
Geometry old_gm = _gm; // backup geometry if we cancel
|
|
bool outline = (! Config::instance()->getOpaqueMove() ||
|
|
! Config::instance()->getOpaqueResize());
|
|
ActionEvent *ae;
|
|
list<Action>::iterator it;
|
|
|
|
wchar_t buf[128];
|
|
getDecorInfo(buf, 128);
|
|
|
|
bool center_on_root = Config::instance()->isShowStatusWindowOnRoot();
|
|
if (Config::instance()->isShowStatusWindow()) {
|
|
sw->draw(buf, true, center_on_root ? 0 : &_gm);
|
|
sw->mapWindowRaised();
|
|
sw->draw(buf, true, center_on_root ? 0 : &_gm);
|
|
}
|
|
|
|
if (outline) {
|
|
PScreen::instance()->grabServer();
|
|
}
|
|
|
|
XEvent e;
|
|
bool exit = false;
|
|
while (! exit) {
|
|
if (outline) {
|
|
drawOutline(_gm);
|
|
}
|
|
XMaskEvent(_dpy, KeyPressMask, &e);
|
|
if (outline) {
|
|
drawOutline(_gm); // clear
|
|
}
|
|
|
|
if ((ae = KeyGrabber::instance()->findMoveResizeAction(&e.xkey)) != 0) {
|
|
for (it = ae->action_list.begin(); it != ae->action_list.end(); ++it) {
|
|
switch (it->getAction()) {
|
|
case MOVE_HORIZONTAL:
|
|
_gm.x += it->getParamI(0);
|
|
if (! outline) {
|
|
move(_gm.x, _gm.y);
|
|
}
|
|
break;
|
|
case MOVE_VERTICAL:
|
|
_gm.y += it->getParamI(0);
|
|
if (! outline) {
|
|
move(_gm.x, _gm.y);
|
|
}
|
|
break;
|
|
case RESIZE_HORIZONTAL:
|
|
_gm.width += resizeHorzStep(it->getParamI(0));
|
|
if (! outline) {
|
|
resize(_gm.width, _gm.height);
|
|
}
|
|
break;
|
|
case RESIZE_VERTICAL:
|
|
_gm.height += resizeVertStep(it->getParamI(0));
|
|
if (! outline) {
|
|
resize(_gm.width, _gm.height);
|
|
}
|
|
break;
|
|
case MOVE_SNAP:
|
|
checkSnap();
|
|
if (! outline) {
|
|
move(_gm.x, _gm.y);
|
|
}
|
|
break;
|
|
case MOVE_CANCEL:
|
|
_gm = old_gm; // restore position
|
|
|
|
if (! outline) {
|
|
move(_gm.x, _gm.y);
|
|
resize(_gm.width, _gm.height);
|
|
}
|
|
|
|
exit = true;
|
|
break;
|
|
case MOVE_END:
|
|
if (outline) {
|
|
if ((_gm.x != old_gm.x) || (_gm.y != old_gm.y))
|
|
move(_gm.x, _gm.y);
|
|
if ((_gm.width != old_gm.width) || (_gm.height != old_gm.height))
|
|
resize(_gm.width, _gm.height);
|
|
}
|
|
exit = true;
|
|
break;
|
|
default:
|
|
// do nothing
|
|
break;
|
|
}
|
|
|
|
getDecorInfo(buf, 128);
|
|
if (Config::instance()->isShowStatusWindow()) {
|
|
sw->draw(buf, true, center_on_root ? 0 : &_gm);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Config::instance()->isShowStatusWindow()) {
|
|
sw->unmapWindow();
|
|
}
|
|
|
|
if (outline) {
|
|
scr->ungrabServer(true);
|
|
}
|
|
|
|
scr->ungrabKeyboard();
|
|
scr->ungrabPointer();
|
|
}
|
|
|
|
//! @brief Sets shaded state
|
|
void
|
|
PDecor::setShaded(StateAction sa)
|
|
{
|
|
if (! ActionUtil::needToggle(sa, _shaded))
|
|
return;
|
|
|
|
// If we are going to shade the window, we need to check carefully
|
|
// that we don't try to make the window disappear.
|
|
if (_shaded) {
|
|
_gm.height = _real_height;
|
|
_shaded = false;
|
|
// If we have a titlebar OR border (border will only be visible in
|
|
// full-width mode only)
|
|
} else if (_titlebar || (_border && ! _data->getTitleWidthMin())) {
|
|
_real_height = _gm.height;
|
|
_gm.height = getTitleHeight();
|
|
// shading in non full width title mode will make border go away
|
|
if (! _data->getTitleWidthMin()) {
|
|
_gm.height += borderTop() + borderBottom();
|
|
}
|
|
_shaded = true;
|
|
}
|
|
|
|
PWinObj::resize(_gm.width, _gm.height);
|
|
setBorderShape();
|
|
placeBorder();
|
|
restackBorder();
|
|
}
|
|
|
|
//! @brief Sets skip state.
|
|
void
|
|
PDecor::setSkip(uint skip)
|
|
{
|
|
_skip = skip;
|
|
}
|
|
|
|
//! @brief Remove iconified state.
|
|
void PDecor::deiconify(void) {
|
|
if (_iconified) {
|
|
if (_workspace == Workspaces::instance()->getActive()) {
|
|
mapWindow();
|
|
}
|
|
_iconified = false;
|
|
}
|
|
}
|
|
|
|
//! @brief Renders and sets title background
|
|
void
|
|
PDecor::renderTitle(void)
|
|
{
|
|
if (! getTitleHeight()) {
|
|
return;
|
|
}
|
|
|
|
bool force_update = false;
|
|
if (_data->getTitleWidthMin()) {
|
|
uint width_before = _title_wo.getWidth();
|
|
|
|
resizeTitle();
|
|
applyBorderShape(); // update title shape
|
|
|
|
if (width_before != _title_wo.getWidth()) {
|
|
force_update = true;
|
|
}
|
|
} else {
|
|
calcTabsWidth();
|
|
}
|
|
|
|
PFont *font = getFont(getFocusedState(false));
|
|
PTexture *t_sep = _data->getTextureSeparator(getFocusedState(false));
|
|
PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler();
|
|
|
|
// Get new title pixmap
|
|
if (_dirty_resized || force_update) {
|
|
pm->returnPixmap(_title_bg);
|
|
_title_bg = pm->getPixmap(_title_wo.getWidth(), _title_wo.getHeight(), PScreen::instance()->getDepth());
|
|
_title_wo.setBackgroundPixmap(_title_bg);
|
|
}
|
|
|
|
// Render main background on pixmap
|
|
_data->getTextureMain(getFocusedState(false))->render(_title_bg, 0, 0,
|
|
_title_wo.getWidth(), _title_wo.getHeight());
|
|
|
|
bool sel; // Current tab selected flag
|
|
uint x = _titles_left; // Position
|
|
uint pad_horiz = _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT); // Amount of horizontal padding
|
|
|
|
list<PDecor::TitleItem*>::iterator it(_title_list.begin());
|
|
for (uint i = 0; it != _title_list.end(); ++i, ++it) {
|
|
sel = (_title_active == i);
|
|
|
|
// render tab
|
|
_data->getTextureTab(getFocusedState(sel))->render(_title_bg, x, 0,
|
|
(*it)->getWidth(), _title_wo.getHeight());
|
|
|
|
font = getFont(getFocusedState(sel));
|
|
font->setColor(_data->getFontColor(getFocusedState(sel)));
|
|
|
|
if ((*it)) {
|
|
PFont::TrimType trim = PFont::FONT_TRIM_MIDDLE;
|
|
if ((*it)->isCustom() || (*it)->isUserSet()) {
|
|
trim = PFont::FONT_TRIM_END;
|
|
}
|
|
|
|
font->draw(_title_bg,
|
|
x + _data->getPad(PAD_LEFT), // X position
|
|
_data->getPad(PAD_UP), // Y position
|
|
(*it)->getVisible(), 0, // Text and max chars
|
|
(*it)->getWidth() - pad_horiz, // Available width
|
|
trim); // Type of trim
|
|
}
|
|
|
|
// move to next tab (or separator if any)
|
|
x += (*it)->getWidth();
|
|
|
|
// draw separator
|
|
if ((_title_list.size() > 1) && (i < (_title_list.size() - 1))) {
|
|
t_sep->render(_title_bg, x, 0, 0, 0);
|
|
x += t_sep->getWidth();
|
|
}
|
|
}
|
|
|
|
_title_wo.clear();
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
PDecor::renderButtons(void)
|
|
{
|
|
list<PDecor::Button*>::iterator it(_button_list.begin());
|
|
for (; it != _button_list.end(); ++it) {
|
|
(*it)->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED);
|
|
}
|
|
}
|
|
|
|
//! @brief Renders the border of the decor.
|
|
void
|
|
PDecor::renderBorder(void)
|
|
{
|
|
if (! _border) {
|
|
return;
|
|
}
|
|
|
|
map<BorderPosition, Pixmap>::iterator it;
|
|
PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler();
|
|
|
|
// Return pixmaps used for the border
|
|
if (_dirty_resized) {
|
|
for (it = _border_pos_map.begin(); it != _border_pos_map.end(); ++it) {
|
|
pm->returnPixmap(it->second);
|
|
it->second = None;
|
|
}
|
|
}
|
|
|
|
PTexture *tex;
|
|
FocusedState state = getFocusedState(false);
|
|
uint width, height;
|
|
|
|
for (it = _border_pos_map.begin(); it != _border_pos_map.end(); ++it) {
|
|
tex = _data->getBorderTexture(state, it->first);
|
|
|
|
// Solid texture, get the color and set as bg, no need to render pixmap
|
|
if (tex->getType() == PTexture::TYPE_SOLID) {
|
|
XSetWindowBackground(_dpy, _border_win[it->first],
|
|
static_cast<PTextureSolid*>(tex)->getColor()->pixel);
|
|
|
|
} else {
|
|
// not a solid texture, get pixmap, render, set as bg
|
|
getBorderSize(it->first, width, height);
|
|
|
|
if ((width > 0) && (height > 0)) {
|
|
if (_dirty_resized) {
|
|
it->second = pm->getPixmap(width, height,
|
|
PScreen::instance()->getDepth());
|
|
XSetWindowBackgroundPixmap(_dpy, _border_win[it->first],
|
|
it->second);
|
|
}
|
|
|
|
tex->render(it->second, 0, 0, width, height);
|
|
}
|
|
}
|
|
|
|
XClearWindow(_dpy, _border_win[it->first]);
|
|
}
|
|
}
|
|
|
|
//! @brief Sets shape on the border windows.
|
|
void
|
|
PDecor::setBorderShape(void)
|
|
{
|
|
#ifdef HAVE_SHAPE
|
|
Pixmap pix;
|
|
bool do_free;
|
|
unsigned int width, height;
|
|
FocusedState state = getFocusedState(false);
|
|
|
|
XRectangle rect = {0, 0, 0, 0};
|
|
|
|
map<BorderPosition, Pixmap>::iterator it(_border_pos_map.begin());
|
|
for (; it != _border_pos_map.end(); ++it) {
|
|
// Get the size of the border at position
|
|
getBorderSize(it->first, width, height);
|
|
|
|
// Get shape pixmap
|
|
pix = _data->getBorderTexture(state, it->first)->getMask(width, height,
|
|
do_free);
|
|
if (pix != None) {
|
|
_need_shape = true;
|
|
XShapeCombineMask(_dpy, _border_win[it->first],
|
|
ShapeBounding, 0, 0, pix, ShapeSet);
|
|
if (do_free) {
|
|
ScreenResources::instance()->getPixmapHandler()->returnPixmap(pix);
|
|
}
|
|
} else {
|
|
rect.width = width;
|
|
rect.height = height;
|
|
XShapeCombineRectangles(_dpy, _border_win[it->first], ShapeBounding,
|
|
0, 0, &rect, 1, ShapeSet, YXBanded);
|
|
}
|
|
}
|
|
#endif // HAVE_SHAPE
|
|
}
|
|
|
|
//! @brief Finds the Head closest to x y from the center of the decor
|
|
uint
|
|
PDecor::getNearestHead(void)
|
|
{
|
|
return PScreen::instance()->getNearestHead(_gm.x + (_gm.width / 2), _gm.y + (_gm.height / 2));
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
PDecor::checkSnap(void)
|
|
{
|
|
if ((Config::instance()->getWOAttract() > 0) ||
|
|
(Config::instance()->getWOResist() > 0)) {
|
|
checkWOSnap();
|
|
}
|
|
if ((Config::instance()->getEdgeAttract() > 0) ||
|
|
(Config::instance()->getEdgeResist() > 0)) {
|
|
checkEdgeSnap();
|
|
}
|
|
}
|
|
|
|
inline bool
|
|
isBetween(int x1, int x2, int t1, int t2)
|
|
{
|
|
if (x1 > t1) {
|
|
if (x1 < t2) {
|
|
return true;
|
|
}
|
|
} else if (x2 > t1) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//! @brief
|
|
//! @todo PDecor/PWinObj doesn't have _skip property
|
|
void
|
|
PDecor::checkWOSnap(void)
|
|
{
|
|
PDecor *decor;
|
|
Geometry gm = _gm;
|
|
|
|
int x = getRX();
|
|
int y = getBY();
|
|
|
|
int attract = Config::instance()->getWOAttract();
|
|
int resist = Config::instance()->getWOResist();
|
|
|
|
bool snapped;
|
|
|
|
vector<PWinObj*>::reverse_iterator it = _wo_list.rbegin();
|
|
for (; it != _wo_list.rend(); ++it) {
|
|
if (((*it) == this) || ! (*it)->isMapped() || ((*it)->getType() != PWinObj::WO_FRAME)) {
|
|
continue;
|
|
}
|
|
|
|
// Skip snapping, only valid on PDecor and up.
|
|
decor = dynamic_cast<PDecor*>(*it);
|
|
if (decor && decor->isSkip(SKIP_SNAP)) {
|
|
continue;
|
|
}
|
|
|
|
snapped = false;
|
|
|
|
// check snap
|
|
if ((x >= ((*it)->getX() - attract)) && (x <= ((*it)->getX() + resist))) {
|
|
if (isBetween(gm.y, y, (*it)->getY(), (*it)->getBY())) {
|
|
_gm.x = (*it)->getX() - gm.width;
|
|
snapped = true;
|
|
}
|
|
} else if ((gm.x >= signed((*it)->getRX() - resist)) &&
|
|
(gm.x <= signed((*it)->getRX() + attract))) {
|
|
if (isBetween(gm.y, y, (*it)->getY(), (*it)->getBY())) {
|
|
_gm.x = (*it)->getRX();
|
|
snapped = true;
|
|
}
|
|
}
|
|
|
|
if (y >= ((*it)->getY() - attract) && (y <= (*it)->getY() + resist)) {
|
|
if (isBetween(gm.x, x, (*it)->getX(), (*it)->getRX())) {
|
|
_gm.y = (*it)->getY() - gm.height;
|
|
if (snapped)
|
|
break;
|
|
}
|
|
} else if ((gm.y >= signed((*it)->getBY() - resist)) &&
|
|
(gm.y <= signed((*it)->getBY() + attract))) {
|
|
if (isBetween(gm.x, x, (*it)->getX(), (*it)->getRX())) {
|
|
_gm.y = (*it)->getBY();
|
|
if (snapped)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//! @brief Snaps decor agains head edges. Only updates _gm, no real move.
|
|
//! @todo Add support for checking for harbour and struts
|
|
void
|
|
PDecor::checkEdgeSnap(void)
|
|
{
|
|
int attract = Config::instance()->getEdgeAttract();
|
|
int resist = Config::instance()->getEdgeResist();
|
|
|
|
Geometry head;
|
|
PScreen::instance()->getHeadInfoWithEdge(PScreen::instance()->getNearestHead(_gm.x, _gm.y), head);
|
|
|
|
if ((_gm.x >= (head.x - resist)) && (_gm.x <= (head.x + attract))) {
|
|
_gm.x = head.x;
|
|
} else if ((_gm.x + _gm.width) >= (head.x + head.width - attract) &&
|
|
((_gm.x + _gm.width) <= (head.x + head.width + resist))) {
|
|
_gm.x = head.x + head.width - _gm.width;
|
|
}
|
|
if ((_gm.y >= (head.y - resist)) && (_gm.y <= (head.y + attract))) {
|
|
_gm.y = head.y;
|
|
} else if (((_gm.y + _gm.height) >= (head.y + head.height - attract)) &&
|
|
((_gm.y + _gm.height) <= (head.y + head.height + resist))) {
|
|
_gm.y = head.y + head.height - _gm.height;
|
|
}
|
|
}
|
|
|
|
|
|
//! @brief Set shapes of decor
|
|
//! @return true if shape is on, else false.
|
|
bool
|
|
PDecor::setShape(void)
|
|
{
|
|
#ifdef HAVE_SHAPE
|
|
if (! _child) {
|
|
return false;
|
|
}
|
|
|
|
int num, t;
|
|
XRectangle *rect;
|
|
|
|
rect = XShapeGetRectangles(_dpy, _child->getWindow(), ShapeBounding,
|
|
&num, &t);
|
|
// If there is more than one Rectangle it has an irregular shape.
|
|
_need_client_shape = (num > 1);
|
|
|
|
// Will set or unset shape depending on _need_shape and _need_client_shape.
|
|
applyBorderShape();
|
|
|
|
XFree(rect);
|
|
|
|
return (num > 1);
|
|
#else // !HAVE_SHAPE
|
|
return false;
|
|
#endif // HAVE_SHAPE
|
|
}
|
|
|
|
/**
|
|
* Move child into position with regards to title and border.
|
|
*/
|
|
void
|
|
PDecor::alignChild(PWinObj *child)
|
|
{
|
|
if (child) {
|
|
XMoveWindow(_dpy, child->getWindow(), borderLeft(), borderTop() + getTitleHeight());
|
|
}
|
|
}
|
|
|
|
//! @brief Draws outline of the decor with geometry gm
|
|
void
|
|
PDecor::drawOutline(const Geometry &gm)
|
|
{
|
|
XDrawRectangle(_dpy, PScreen::instance()->getRoot(),
|
|
_theme->getInvertGC(),
|
|
gm.x, gm.y, gm.width,
|
|
_shaded ? _gm.height : gm.height);
|
|
}
|
|
|
|
//! @brief Places decor buttons
|
|
void
|
|
PDecor::placeButtons(void)
|
|
{
|
|
_titles_left = 0;
|
|
_titles_right = 0;
|
|
|
|
list<PDecor::Button*>::iterator it(_button_list.begin());
|
|
for (; it != _button_list.end(); ++it) {
|
|
if ((*it)->isLeft()) {
|
|
(*it)->move(_titles_left, 0);
|
|
_titles_left += (*it)->getWidth();
|
|
} else {
|
|
_titles_right += (*it)->getWidth();
|
|
(*it)->move(_title_wo.getWidth() - _titles_right, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
//! @brief Places border windows
|
|
void
|
|
PDecor::placeBorder(void)
|
|
{
|
|
// if we have tab min == 0 then we have full width title and place the
|
|
// border ontop, else we put the border under the title
|
|
uint bt_off = (_data->getTitleWidthMin() > 0) ? getTitleHeight() : 0;
|
|
|
|
if (borderTop() > 0) {
|
|
XMoveResizeWindow(_dpy, _border_win[BORDER_TOP],
|
|
borderTopLeft(), bt_off,
|
|
_gm.width - borderTopLeft() - borderTopRight(), borderTop());
|
|
|
|
XMoveResizeWindow(_dpy, _border_win[BORDER_TOP_LEFT],
|
|
0, bt_off,
|
|
borderTopLeft(), borderTopLeftHeight());
|
|
XMoveResizeWindow(_dpy, _border_win[BORDER_TOP_RIGHT],
|
|
_gm.width - borderTopRight(), bt_off,
|
|
borderTopRight(), borderTopRightHeight());
|
|
|
|
if (borderLeft()) {
|
|
XMoveResizeWindow(_dpy, _border_win[BORDER_LEFT],
|
|
0, borderTopLeftHeight() + bt_off,
|
|
borderLeft(), _gm.height - borderTopLeftHeight() - borderBottomLeftHeight());
|
|
}
|
|
|
|
if (borderRight()) {
|
|
XMoveResizeWindow(_dpy, _border_win[BORDER_RIGHT],
|
|
_gm.width - borderRight(), borderTopRightHeight() + bt_off,
|
|
borderRight(), _gm.height - borderTopRightHeight() - borderBottomRightHeight());
|
|
}
|
|
} else {
|
|
if (borderLeft()) {
|
|
XMoveResizeWindow(_dpy, _border_win[BORDER_LEFT],
|
|
0, getTitleHeight(),
|
|
borderLeft(), _gm.height - getTitleHeight() - borderBottom());
|
|
}
|
|
|
|
if (borderRight()) {
|
|
XMoveResizeWindow(_dpy, _border_win[BORDER_RIGHT],
|
|
_gm.width - borderRight(), getTitleHeight(),
|
|
borderRight(), _gm.height - getTitleHeight() - borderBottom());
|
|
}
|
|
}
|
|
|
|
if (borderBottom()) {
|
|
XMoveResizeWindow(_dpy, _border_win[BORDER_BOTTOM],
|
|
borderBottomLeft(), _gm.height - borderBottom(),
|
|
_gm.width - borderBottomLeft() - borderBottomRight(), borderBottom());
|
|
XMoveResizeWindow(_dpy, _border_win[BORDER_BOTTOM_LEFT],
|
|
0, _gm.height - borderBottomLeftHeight(),
|
|
borderBottomLeft(), borderBottomLeftHeight());
|
|
XMoveResizeWindow(_dpy, _border_win[BORDER_BOTTOM_RIGHT],
|
|
_gm.width - borderBottomRight(), _gm.height - borderBottomRightHeight(),
|
|
borderBottomRight(), borderBottomRightHeight());
|
|
}
|
|
|
|
applyBorderShape();
|
|
}
|
|
|
|
/**
|
|
* Apply shaping on the window based on the shape of the client and
|
|
* the borders.
|
|
*/
|
|
void
|
|
PDecor::applyBorderShape(void)
|
|
{
|
|
#ifdef HAVE_SHAPE
|
|
if (_need_shape || _need_client_shape) {
|
|
// if we have tab min == 0 then we have full width title and place the
|
|
// border ontop, else we put the border under the title
|
|
uint bt_off = (_data->getTitleWidthMin() > 0) ? getTitleHeight() : 0;
|
|
|
|
Window shape;
|
|
shape = XCreateSimpleWindow(_dpy, PScreen::instance()->getRoot(),
|
|
0, 0, _gm.width, _gm.height, 0, 0, 0);
|
|
|
|
if (_child) {
|
|
XShapeCombineShape(_dpy, shape, ShapeBounding,
|
|
borderLeft(), borderTop() + getTitleHeight(),
|
|
_child->getWindow(), ShapeBounding, ShapeSet);
|
|
}
|
|
|
|
// Apply border shape. Need to be carefull wheter or not to include it.
|
|
if (_border
|
|
&& ! (_shaded && bt_off) // Shaded in non-full-width mode removes border.
|
|
&& ! (_need_client_shape)) { // Shaped clients should appear bordeless.
|
|
// top
|
|
if (borderTop() > 0) {
|
|
XShapeCombineShape(_dpy, shape, ShapeBounding,
|
|
0, bt_off, _border_win[BORDER_TOP_LEFT],
|
|
ShapeBounding, ShapeUnion);
|
|
|
|
XShapeCombineShape(_dpy, shape, ShapeBounding,
|
|
borderTopLeft(), bt_off, _border_win[BORDER_TOP],
|
|
ShapeBounding, ShapeUnion);
|
|
|
|
XShapeCombineShape(_dpy, shape, ShapeBounding,
|
|
_gm.width - borderTopRight(), bt_off, _border_win[BORDER_TOP_RIGHT],
|
|
ShapeBounding, ShapeUnion);
|
|
}
|
|
|
|
bool use_bt_off = bt_off || borderTop();
|
|
// Left border
|
|
if (borderLeft() > 0) {
|
|
XShapeCombineShape(_dpy, shape, ShapeBounding,
|
|
0, use_bt_off ? bt_off + borderTopLeftHeight() : getTitleHeight(),
|
|
_border_win[BORDER_LEFT],
|
|
ShapeBounding, ShapeUnion);
|
|
}
|
|
|
|
// Right border
|
|
if (borderRight() > 0) {
|
|
XShapeCombineShape(_dpy, shape, ShapeBounding,
|
|
_gm.width - borderRight(), use_bt_off ? bt_off + borderTopRightHeight() : getTitleHeight(),
|
|
_border_win[BORDER_RIGHT],
|
|
ShapeBounding, ShapeUnion);
|
|
}
|
|
|
|
// bottom
|
|
if (borderBottom() > 0) {
|
|
XShapeCombineShape(_dpy, shape, ShapeBounding,
|
|
0, _gm.height - borderBottomLeftHeight(), _border_win[BORDER_BOTTOM_LEFT],
|
|
ShapeBounding, ShapeUnion);
|
|
|
|
XShapeCombineShape(_dpy, shape, ShapeBounding,
|
|
borderBottomLeft(), _gm.height - borderBottom(), _border_win[BORDER_BOTTOM],
|
|
ShapeBounding, ShapeUnion);
|
|
|
|
XShapeCombineShape(_dpy, shape, ShapeBounding,
|
|
_gm.width - borderBottomRight(), _gm.height - borderBottomRightHeight(),
|
|
_border_win[BORDER_BOTTOM_RIGHT],
|
|
ShapeBounding, ShapeUnion);
|
|
}
|
|
}
|
|
if (_titlebar) {
|
|
// apply title shape
|
|
XShapeCombineShape(_dpy, shape, ShapeBounding,
|
|
_title_wo.getX(), _title_wo.getY(), _title_wo.getWindow(),
|
|
ShapeBounding, ShapeUnion);
|
|
}
|
|
|
|
// Apply the shape mask to the window
|
|
XShapeCombineShape(_dpy, _window, ShapeBounding, 0, 0, shape, ShapeBounding, ShapeSet);
|
|
|
|
XDestroyWindow(_dpy, shape);
|
|
} else {
|
|
// No shaping is required, apply a square shape to remove
|
|
// possible stale shaping.
|
|
XRectangle rect_square;
|
|
rect_square.x = 0;
|
|
rect_square.y = 0;
|
|
rect_square.width = _gm.width;
|
|
rect_square.height = _gm.height;
|
|
|
|
XShapeCombineRectangles(_dpy, _window, ShapeBounding, 0, 0, &rect_square, 1, ShapeSet, YXBanded);
|
|
}
|
|
#endif // HAVE_SHAPE
|
|
}
|
|
|
|
//! @brief Restacks child, title and border windows.
|
|
void
|
|
PDecor::restackBorder(void)
|
|
{
|
|
// List of windows, adding two possible for title and child window
|
|
int extra = 0;
|
|
Window windows[BORDER_NO_POS + 2];
|
|
|
|
|
|
// Only put the Child over if not shaded.
|
|
if (_child && ! _shaded) {
|
|
windows[extra++] = _child->getWindow();
|
|
}
|
|
// Add title if any
|
|
if (_titlebar) {
|
|
windows[extra++] = _title_wo.getWindow();
|
|
}
|
|
|
|
// Add border windows
|
|
for (int i = 0; i < BORDER_NO_POS; ++i) {
|
|
windows[i + extra] = _border_win[i];
|
|
}
|
|
|
|
// Raise the top window so actual restacking is done.
|
|
XRaiseWindow(_dpy, windows[0]);
|
|
XRestackWindows(_dpy, windows, BORDER_NO_POS + extra);
|
|
}
|
|
|
|
//! @brief Updates _decor_name to represent decor state
|
|
//! @return true if decor was changed
|
|
bool
|
|
PDecor::updateDecorName(void)
|
|
{
|
|
string name;
|
|
|
|
if (_titlebar && _border) {
|
|
name = PDecor::DEFAULT_DECOR_NAME;
|
|
} else if (_titlebar) {
|
|
name = PDecor::DEFAULT_DECOR_NAME_BORDERLESS;
|
|
} else if (_border) {
|
|
name = PDecor::DEFAULT_DECOR_NAME_TITLEBARLESS;
|
|
}
|
|
|
|
return setDecor(name);
|
|
}
|
|
|
|
/**
|
|
* Get size of border at position.
|
|
*
|
|
* @param pos Position to get size for.
|
|
* @param width Width of border at position.
|
|
* @param height Height of border at position.
|
|
*/
|
|
void
|
|
PDecor::getBorderSize(BorderPosition pos, uint &width, uint &height)
|
|
{
|
|
FocusedState state = getFocusedState(false); // convenience
|
|
|
|
switch (pos) {
|
|
case BORDER_TOP_LEFT:
|
|
case BORDER_TOP_RIGHT:
|
|
case BORDER_BOTTOM_LEFT:
|
|
case BORDER_BOTTOM_RIGHT:
|
|
width = _data->getBorderTexture(state, pos)->getWidth();
|
|
height = _data->getBorderTexture(state, pos)->getHeight();
|
|
break;
|
|
case BORDER_TOP:
|
|
if ((borderTopLeft() + borderTopRight()) < _gm.width) {
|
|
width = _gm.width - borderTopLeft() - borderTopRight();
|
|
} else {
|
|
width = 1;
|
|
}
|
|
height = _data->getBorderTexture(state, pos)->getHeight();
|
|
break;
|
|
case BORDER_BOTTOM:
|
|
if ((borderBottomLeft() + borderBottomRight()) < _gm.width) {
|
|
width = _gm.width - borderBottomLeft() - borderBottomRight();
|
|
} else {
|
|
width = 1;
|
|
}
|
|
height = _data->getBorderTexture(state, pos)->getHeight();
|
|
break;
|
|
case BORDER_LEFT:
|
|
width = _data->getBorderTexture(state, pos)->getWidth();
|
|
if ((borderTopLeftHeight() + borderBottomLeftHeight()) < _gm.height) {
|
|
height = _gm.height - borderTopLeftHeight() - borderBottomLeftHeight();
|
|
} else {
|
|
height = 1;
|
|
}
|
|
break;
|
|
case BORDER_RIGHT:
|
|
width = _data->getBorderTexture(state, pos)->getWidth();
|
|
if ((borderTopRightHeight() + borderBottomRightHeight()) < _gm.height) {
|
|
height = _gm.height - borderTopRightHeight() - borderBottomRightHeight();
|
|
} else {
|
|
height = 1;
|
|
}
|
|
break;
|
|
default:
|
|
#ifdef DEBUG
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "PDecor(" << this << ")::getBorderSize()" << endl << " *** invalid border position" << endl;
|
|
#endif // DEBUG
|
|
width = 1;
|
|
height = 1;
|
|
break;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Calculate width of title, if width min is 0 use the full available
|
|
* size minus borders. Else get width from content.
|
|
*/
|
|
uint
|
|
PDecor::calcTitleWidth(void)
|
|
{
|
|
uint width = 0;
|
|
|
|
if (_data->getTitleWidthMin() == 0) {
|
|
width = _gm.width;
|
|
if (width > (borderTopLeft() + borderTopRight())) {
|
|
width -= borderTopLeft() + borderTopRight();
|
|
}
|
|
|
|
} else {
|
|
uint width_max = 0;
|
|
// FIXME: what about selected tabs?
|
|
PFont *font = getFont(getFocusedState(false));
|
|
|
|
if (_data->isTitleWidthSymetric()) {
|
|
// Symetric mode, get max tab width, multiply with number of tabs
|
|
list<PDecor::TitleItem*>::iterator it(_title_list.begin());
|
|
for (; it != _title_list.end(); ++it) {
|
|
width = font->getWidth((*it)->getVisible());
|
|
if (width > width_max) {
|
|
width_max = width;
|
|
}
|
|
}
|
|
|
|
width = width_max + _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT);
|
|
width *= _title_list.size();
|
|
|
|
} else {
|
|
// Asymetric mode, get individual widths
|
|
list<PDecor::TitleItem*>::iterator it(_title_list.begin());
|
|
for (; it != _title_list.end(); ++it) {
|
|
width += font->getWidth((*it)->getVisible())
|
|
+ _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT);
|
|
}
|
|
}
|
|
|
|
// Add width of separators and buttons
|
|
width += (_title_list.size() - 1)
|
|
* _data->getTextureSeparator(getFocusedState(false))->getWidth();
|
|
width += _titles_left + _titles_right;
|
|
|
|
// Validate sizes, make sure it is not less than width min
|
|
// pixels and more than width max percent.
|
|
if (width < static_cast<uint>(_data->getTitleWidthMin())) {
|
|
width = _data->getTitleWidthMin();
|
|
}
|
|
if (width > (_gm.width * _data->getTitleWidthMax() / 100)) {
|
|
width = _gm.width * _data->getTitleWidthMax() / 100;
|
|
}
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
/**
|
|
* Calculate tab width, wrapper to choose correct algorithm..
|
|
*/
|
|
void
|
|
PDecor::calcTabsWidth(void)
|
|
{
|
|
if (! _title_list.size()) {
|
|
return;
|
|
}
|
|
|
|
if (_data->isTitleWidthSymetric()) {
|
|
calcTabsWidthSymetric();
|
|
} else {
|
|
calcTabsWidthAsymetric();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get available tabs width and average width of a single tab.
|
|
*
|
|
* @param width_avail Number of pixels available in the titlebar.
|
|
* @param tab_width Width in pixels to use for one tab.
|
|
* @param off Number of pixels left with tab_width pixels wide tabs.
|
|
*/
|
|
void
|
|
PDecor::calcTabsGetAvailAndTabWidth(uint &width_avail, uint &tab_width, int &off)
|
|
{
|
|
// Calculate width
|
|
width_avail = _title_wo.getWidth();
|
|
|
|
// Remove spacing if enough space is available
|
|
if (width_avail > (_titles_left + _titles_right)) {
|
|
width_avail -= _titles_left + _titles_right;
|
|
}
|
|
|
|
// Remove separators if enough space is available
|
|
uint sep_width = (_title_list.size() - 1)
|
|
* _data->getTextureSeparator(getFocusedState(false))->getWidth();
|
|
if (width_avail > sep_width) {
|
|
width_avail -= sep_width;
|
|
}
|
|
|
|
tab_width = width_avail / _title_list.size();
|
|
off = width_avail % _title_list.size();
|
|
}
|
|
|
|
//! @brief Calculate tab width symetric, sets up _title_list widths.
|
|
void
|
|
PDecor::calcTabsWidthSymetric(void)
|
|
{
|
|
int off;
|
|
uint width_avail, tab_width;
|
|
calcTabsGetAvailAndTabWidth(width_avail, tab_width, off);
|
|
|
|
// Assign width to elements
|
|
list<PDecor::TitleItem*>::iterator it(_title_list.begin());
|
|
for (; it != _title_list.end(); ++it) {
|
|
(*it)->setWidth(tab_width + ((off-- > 0) ? 1 : 0));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate tab width asymmetrically. This is done with the following
|
|
* priority:
|
|
*
|
|
* 1. Give tabs their required width, if it fits this is complete.
|
|
* 2. Tabs did not fit, calculate average width.
|
|
* 3. Add width of tabs requiring less space to average.
|
|
* 4. Re-assign width equally to tabs using more than average.
|
|
*/
|
|
void
|
|
PDecor::calcTabsWidthAsymetric(void)
|
|
{
|
|
int off;
|
|
uint width, width_avail, tab_width;
|
|
calcTabsGetAvailAndTabWidth(width_avail, tab_width, off);
|
|
|
|
// Convenience
|
|
PFont *font = getFont(getFocusedState(false));
|
|
|
|
// 1. give tabs their required width.
|
|
uint width_total = 0;
|
|
list<PDecor::TitleItem*>::iterator it(_title_list.begin());
|
|
for (; it != _title_list.end(); ++it) {
|
|
// This should set the tab width to be only the size needed
|
|
width = font->getWidth((*it)->getVisible().c_str())
|
|
+ _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT) + ((off-- > 0) ? 1 : 0);
|
|
width_total += width;
|
|
(*it)->setWidth(width);
|
|
}
|
|
|
|
if (width_total > width_avail) {
|
|
calcTabsWidthAsymetricShrink(width_avail, tab_width);
|
|
} else if (width_total < static_cast<uint>(_data->getTitleWidthMin())) {
|
|
calcTabsWidthAsymetricGrow(width_avail, tab_width);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is called to shrink the tabs that are over the tab_width in
|
|
* order to make room for all
|
|
*/
|
|
void
|
|
PDecor::calcTabsWidthAsymetricShrink(uint width_avail, uint tab_width)
|
|
{
|
|
// 2. Tabs did not fit
|
|
uint tabs_left = _title_list.size();
|
|
list<PDecor::TitleItem*>::iterator it(_title_list.begin());
|
|
for (; it != _title_list.end(); ++it) {
|
|
if ((*it)->getWidth() < tab_width) {
|
|
// 3. Add width of tabs requiring less space to the average.
|
|
tabs_left--;
|
|
width_avail -= (*it)->getWidth();
|
|
}
|
|
}
|
|
|
|
// 4. Re-assign width equally to tabs using more than average
|
|
tab_width = width_avail / tabs_left;
|
|
uint off = width_avail % tabs_left;
|
|
for (it = _title_list.begin(); it != _title_list.end(); ++it) {
|
|
if ((*it)->getWidth() >= tab_width) {
|
|
(*it)->setWidth(tab_width + ((off-- > 0) ? 1 : 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is called when tabs in the decor are using less space than
|
|
* width min, grow tabs that are smaller than the average.
|
|
*/
|
|
void
|
|
PDecor::calcTabsWidthAsymetricGrow(uint width_avail, uint tab_width)
|
|
{
|
|
// FIXME: Implement growing of asymetric tabs.
|
|
}
|