mirror of
https://github.com/edeproject/ede.git
synced 2023-08-10 21:13:03 +03:00
629 lines
14 KiB
C++
629 lines
14 KiB
C++
//
|
|
// CmdDialog.hh for pekwm
|
|
// Copyright © 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 <list>
|
|
#include <cwchar>
|
|
#include <cwctype>
|
|
|
|
#include "InputDialog.hh"
|
|
#include "KeyGrabber.hh"
|
|
#include "PScreen.hh"
|
|
#include "PixmapHandler.hh"
|
|
#include "ScreenResources.hh"
|
|
#include "Workspaces.hh"
|
|
|
|
extern "C" {
|
|
#include <X11/Xutil.h>
|
|
|
|
#ifdef __minix
|
|
# define XK_MISCELLANY
|
|
# include <X11/keysymdef.h>
|
|
#endif
|
|
}
|
|
|
|
using std::list;
|
|
using std::map;
|
|
using std::wstring;
|
|
using std::iswprint;
|
|
|
|
map<KeySym, wchar_t> InputDialog::_keysym_map;
|
|
|
|
/**
|
|
* InputDialog constructor.
|
|
*/
|
|
InputDialog::InputDialog(Theme *theme, const std::wstring &title)
|
|
: PDecor(theme, "INPUTDIALOG"), PWinObjReference(0),
|
|
_data(theme->getCmdDialogData()),
|
|
_pixmap_bg(None), _pos(0), _buf_off(0), _buf_chars(0)
|
|
{
|
|
// PWinObj attributes
|
|
setLayer(LAYER_NONE); // hack, goes over LAYER_MENU
|
|
_hidden = true; // don't care about it when changing worskpace etc
|
|
|
|
if (! _keysym_map.size()) {
|
|
reloadKeysymMap();
|
|
}
|
|
|
|
// Add action to list, going to be used from close and exec
|
|
::Action action;
|
|
_ae.action_list.push_back(action);
|
|
|
|
titleAdd(&_title);
|
|
titleSetActive(0);
|
|
setTitle(title);
|
|
|
|
_text_wo = new PWinObj;
|
|
XSetWindowAttributes attr;
|
|
attr.override_redirect = false;
|
|
attr.event_mask = ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|
|
|
FocusChangeMask|KeyPressMask|KeyReleaseMask;
|
|
_text_wo->setWindow(XCreateWindow(_dpy, _window,
|
|
0, 0, 1, 1, 0,
|
|
CopyFromParent, InputOutput, CopyFromParent,
|
|
CWOverrideRedirect|CWEventMask, &attr));
|
|
|
|
addChild(_text_wo);
|
|
addChildWindow(_text_wo->getWindow());
|
|
activateChild(_text_wo);
|
|
_text_wo->mapWindow();
|
|
|
|
// setup texture, size etc
|
|
loadTheme();
|
|
|
|
Workspaces::instance()->insert(this);
|
|
woListAdd(this);
|
|
_wo_map[_window] = this;
|
|
}
|
|
|
|
/**
|
|
* InputDialog destructor.
|
|
*/
|
|
InputDialog::~InputDialog(void)
|
|
{
|
|
Workspaces::instance()->remove(this);
|
|
_wo_map.erase(_window);
|
|
woListRemove(this);
|
|
|
|
// Free resources
|
|
if (_text_wo) {
|
|
_child_list.remove(_text_wo);
|
|
removeChildWindow(_text_wo->getWindow());
|
|
XDestroyWindow(_dpy, _text_wo->getWindow());
|
|
delete _text_wo;
|
|
}
|
|
|
|
unloadTheme();
|
|
}
|
|
|
|
void
|
|
InputDialog::reloadKeysymMap(void)
|
|
{
|
|
_keysym_map.clear();
|
|
|
|
addKeysymToKeysymMap(XK_KP_0, L'0');
|
|
addKeysymToKeysymMap(XK_KP_1, L'1');
|
|
addKeysymToKeysymMap(XK_KP_2, L'2');
|
|
addKeysymToKeysymMap(XK_KP_3, L'3');
|
|
addKeysymToKeysymMap(XK_KP_4, L'4');
|
|
addKeysymToKeysymMap(XK_KP_5, L'5');
|
|
addKeysymToKeysymMap(XK_KP_6, L'6');
|
|
addKeysymToKeysymMap(XK_KP_7, L'7');
|
|
addKeysymToKeysymMap(XK_KP_8, L'8');
|
|
addKeysymToKeysymMap(XK_KP_9, L'9');
|
|
}
|
|
|
|
void
|
|
InputDialog::addKeysymToKeysymMap(KeySym keysym, wchar_t chr)
|
|
{
|
|
Display *dpy = PScreen::instance()->getDpy();
|
|
|
|
int keysyms_per_keycode;
|
|
KeyCode keycode = XKeysymToKeycode(dpy, keysym);
|
|
KeySym *keysyms = XGetKeyboardMapping(dpy, keycode, 1, &keysyms_per_keycode);
|
|
|
|
for (int i = 0; i < keysyms_per_keycode; i++) {
|
|
if (keysyms[i] != NoSymbol) {
|
|
_keysym_map[keysyms[i]] = chr;
|
|
}
|
|
}
|
|
|
|
XFree(keysyms);
|
|
}
|
|
|
|
/**
|
|
* Handles ButtonPress, moving the text cursor
|
|
*/
|
|
ActionEvent*
|
|
InputDialog::handleButtonPress(XButtonEvent *ev)
|
|
{
|
|
if (*_text_wo == ev->window) {
|
|
// FIXME: move cursor
|
|
return 0;
|
|
} else {
|
|
return PDecor::handleButtonPress(ev);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles KeyPress, editing the buffer
|
|
*/
|
|
ActionEvent*
|
|
InputDialog::handleKeyPress(XKeyEvent *ev)
|
|
{
|
|
ActionEvent *c_ae, *ae = 0;
|
|
|
|
if ( (c_ae = KeyGrabber::instance()->findAction(ev, _type)) ) {
|
|
list<Action>::iterator it(c_ae->action_list.begin());
|
|
for (; it != c_ae->action_list.end(); ++it) {
|
|
switch (it->getAction()) {
|
|
case INPUT_INSERT:
|
|
bufAdd(ev);
|
|
completeReset();
|
|
break;
|
|
case INPUT_REMOVE:
|
|
bufRemove();
|
|
break;
|
|
case INPUT_CLEAR:
|
|
bufClear();
|
|
completeReset();
|
|
break;
|
|
case INPUT_CLEARFROMCURSOR:
|
|
bufKill();
|
|
completeReset();
|
|
break;
|
|
case INPUT_EXEC:
|
|
ae = exec();
|
|
break;
|
|
case INPUT_CLOSE:
|
|
ae = close();
|
|
break;
|
|
case INPUT_COMPLETE:
|
|
complete();
|
|
break;
|
|
case INPUT_COMPLETE_ABORT:
|
|
completeAbort();
|
|
break;
|
|
case INPUT_CURS_NEXT:
|
|
bufChangePos(1);
|
|
completeReset();
|
|
break;
|
|
case INPUT_CURS_PREV:
|
|
bufChangePos(-1);
|
|
completeReset();
|
|
break;
|
|
case INPUT_CURS_BEGIN:
|
|
_pos = 0;
|
|
completeReset();
|
|
break;
|
|
case INPUT_CURS_END:
|
|
_pos = _buf.size();
|
|
completeReset();
|
|
break;
|
|
case INPUT_HIST_NEXT:
|
|
histNext();
|
|
completeReset();
|
|
break;
|
|
case INPUT_HIST_PREV:
|
|
histPrev();
|
|
completeReset();
|
|
break;
|
|
case INPUT_NO_ACTION:
|
|
default:
|
|
// do nothing, shouldn't happen
|
|
break;
|
|
};
|
|
}
|
|
|
|
// something ( most likely ) changed, redraw the window
|
|
if (! ae) {
|
|
bufChanged();
|
|
render();
|
|
}
|
|
}
|
|
|
|
return ae;
|
|
}
|
|
|
|
/**
|
|
* Handles ExposeEvent, redraw when ev->count == 0
|
|
*/
|
|
ActionEvent*
|
|
InputDialog::handleExposeEvent(XExposeEvent *ev)
|
|
{
|
|
if (ev->count > 0) {
|
|
return 0;
|
|
}
|
|
render();
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Maps the InputDialog center on the PWinObj it executes actions on.
|
|
*
|
|
* @param buf Buffer content.
|
|
* @param focus Give input focus if true.
|
|
* @param wo_ref PWinObj reference, defaults to 0 which does not update.
|
|
*/
|
|
void
|
|
InputDialog::mapCentered(const std::string &buf, bool focus, PWinObj *wo_ref)
|
|
{
|
|
// Setup data
|
|
_hist_it = _hist_list.end();
|
|
|
|
_buf = Util::to_wide_str(buf);
|
|
_pos = _buf.size();
|
|
bufChanged();
|
|
|
|
// Update position
|
|
moveCentered(wo_ref);
|
|
|
|
// Map and render
|
|
PDecor::mapWindowRaised();
|
|
render();
|
|
|
|
// Give input focus if requested
|
|
if (focus) {
|
|
giveInputFocus();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Moves to center of wo.
|
|
*
|
|
* @param wo PWinObj to center on.
|
|
*/
|
|
void
|
|
InputDialog::moveCentered(PWinObj *wo)
|
|
{
|
|
// Fallback wo on root.
|
|
if (! wo) {
|
|
wo = PWinObj::getRootPWinObj();
|
|
}
|
|
|
|
// Make sure position is inside head.
|
|
Geometry head;
|
|
uint head_nr = PScreen::instance()->getNearestHead(wo->getX() + (wo->getWidth() / 2),
|
|
wo->getY() + (wo->getHeight() / 2));
|
|
PScreen::instance()->getHeadInfo(head_nr, head);
|
|
|
|
// Make sure X is inside head.
|
|
int new_x = wo->getX() + (static_cast<int>(wo->getWidth()) - static_cast<int>(_gm.width)) / 2;
|
|
if (new_x < head.x) {
|
|
new_x = head.x;
|
|
} else if ((new_x + _gm.width) > (head.x + head.width)) {
|
|
new_x = head.x + head.width - _gm.width;
|
|
}
|
|
|
|
// Make sure Y is inside head.
|
|
int new_y = wo->getY() + (static_cast<int>(wo->getHeight()) - static_cast<int>(_gm.height)) / 2;
|
|
if (new_y < head.y) {
|
|
new_y = head.y;
|
|
} else if ((new_y + _gm.height) > (head.y + head.height)) {
|
|
new_y = head.y + head.height - _gm.height;
|
|
}
|
|
|
|
// Update position.
|
|
move(new_x, new_y);
|
|
}
|
|
|
|
/**
|
|
* Sets title of decor
|
|
*/
|
|
void
|
|
InputDialog::setTitle(const std::wstring &title)
|
|
{
|
|
_title.setReal(title);
|
|
}
|
|
|
|
/**
|
|
* Maps window, overloaded to refresh content of window after mapping.
|
|
*/
|
|
void
|
|
InputDialog::mapWindow(void)
|
|
{
|
|
if (! _mapped) {
|
|
// Correct size for current head before mapping
|
|
updateSize();
|
|
|
|
PDecor::mapWindow();
|
|
render();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets background and size
|
|
*/
|
|
void
|
|
InputDialog::loadTheme(void)
|
|
{
|
|
_data = _theme->getCmdDialogData();
|
|
updateSize();
|
|
updatePixmapSize();
|
|
}
|
|
|
|
/**
|
|
* Frees resources
|
|
*/
|
|
void
|
|
InputDialog::unloadTheme(void)
|
|
{
|
|
ScreenResources::instance()->getPixmapHandler()->returnPixmap(_pixmap_bg);
|
|
}
|
|
|
|
/**
|
|
* Renders _buf onto _text_wo
|
|
*/
|
|
void
|
|
InputDialog::render(void)
|
|
{
|
|
_text_wo->clear();
|
|
|
|
// draw buf content
|
|
_data->getFont()->setColor(_data->getColor());
|
|
|
|
_data->getFont()->draw(_text_wo->getWindow(), _data->getPad(PAD_LEFT), _data->getPad(PAD_UP),
|
|
_buf.c_str() + _buf_off, _buf_chars);
|
|
|
|
// draw cursor
|
|
uint pos = _data->getPad(PAD_LEFT);
|
|
if (_pos > 0) {
|
|
pos = _data->getFont()->getWidth(_buf.c_str() + _buf_off, _pos - _buf_off) + 1;
|
|
}
|
|
|
|
_data->getFont()->draw(_text_wo->getWindow(), pos, _data->getPad(PAD_UP), L"|");
|
|
}
|
|
|
|
/**
|
|
* Generates ACTION_CLOSE closing dialog.
|
|
*
|
|
* @return Pointer to ActionEvent.
|
|
*/
|
|
ActionEvent*
|
|
InputDialog::close(void)
|
|
{
|
|
_ae.action_list.back().setAction(ACTION_NO);
|
|
return &_ae;
|
|
}
|
|
|
|
/**
|
|
* Default implementation, fills in _buf_on_complete to safely run
|
|
* completeAbort.
|
|
*/
|
|
void
|
|
InputDialog::complete(void)
|
|
{
|
|
_buf_on_complete = _buf;
|
|
_pos_on_complete = _pos;
|
|
}
|
|
|
|
/**
|
|
* Restore buffer and clear completion buffers.
|
|
*/
|
|
void
|
|
InputDialog::completeAbort(void)
|
|
{
|
|
if (_buf_on_complete.size()) {
|
|
_buf = _buf_on_complete;
|
|
_pos = _pos_on_complete;
|
|
}
|
|
|
|
completeReset();
|
|
}
|
|
|
|
/**
|
|
* Clear the completion buffer.
|
|
*/
|
|
void
|
|
InputDialog::completeReset(void)
|
|
{
|
|
// Old gcc doesn't know about .clear()
|
|
_buf_on_complete = _buf_on_complete_result = L"";
|
|
_pos_on_complete = 0;
|
|
}
|
|
|
|
/**
|
|
* Adds char to buffer
|
|
*/
|
|
void
|
|
InputDialog::bufAdd(XKeyEvent *ev)
|
|
{
|
|
KeySym keysym;
|
|
char c_return[64];
|
|
memset(c_return, '\0', sizeof(c_return));
|
|
|
|
XLookupString(ev, c_return, sizeof(c_return), &keysym, 0);
|
|
if (_keysym_map.count(keysym)) {
|
|
_buf.insert(_buf.begin() + _pos++, _keysym_map[keysym]);
|
|
} else {
|
|
// Add wide string to buffer counting position
|
|
wstring buf_ret(Util::to_wide_str(c_return));
|
|
for (unsigned int i = 0; i < buf_ret.size(); ++i) {
|
|
if (iswprint(buf_ret[i])) {
|
|
_buf.insert(_buf.begin() + _pos++, buf_ret[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes char from buffer
|
|
*/
|
|
void
|
|
InputDialog::bufRemove(void)
|
|
{
|
|
if ((_pos > _buf.size()) || (_pos == 0) || (_buf.size() == 0)) {
|
|
return;
|
|
}
|
|
|
|
_buf.erase(_buf.begin() + --_pos);
|
|
}
|
|
|
|
/**
|
|
* Clears the buffer, resets status
|
|
*/
|
|
void
|
|
InputDialog::bufClear(void)
|
|
{
|
|
_buf = _buf_on_complete = _buf_on_complete_result = L""; // old gcc doesn't know about .clear()
|
|
_pos = _pos_on_complete = _buf_off = _buf_chars = 0;
|
|
}
|
|
|
|
/**
|
|
* Removes buffer content after cursor position
|
|
*/
|
|
void
|
|
InputDialog::bufKill(void)
|
|
{
|
|
_buf.resize(_pos);
|
|
}
|
|
|
|
/**
|
|
* Moves the marker
|
|
*/
|
|
void
|
|
InputDialog::bufChangePos(int off)
|
|
{
|
|
if ((signed(_pos) + off) < 0) {
|
|
_pos = 0;
|
|
} else if (unsigned(_pos + off) > _buf.size()) {
|
|
_pos = _buf.size();
|
|
} else {
|
|
_pos += off;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recalculates, _buf_off and _buf_chars
|
|
*/
|
|
void
|
|
InputDialog::bufChanged(void)
|
|
{
|
|
PFont *font = _data->getFont(); // convenience
|
|
|
|
// complete string doesn't fit in the window OR
|
|
// we don't fit in the first set
|
|
if ((_pos > 0)
|
|
&& (font->getWidth(_buf.c_str()) > _text_wo->getWidth())
|
|
&& (font->getWidth(_buf.c_str(), _pos) > _text_wo->getWidth())) {
|
|
|
|
// increase position until it all fits
|
|
for (_buf_off = 0; _buf_off < _pos; ++_buf_off) {
|
|
if (font->getWidth(_buf.c_str() + _buf_off, _buf.size() - _buf_off)
|
|
< _text_wo->getWidth()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
_buf_chars = _buf.size() - _buf_off;
|
|
} else {
|
|
_buf_off = 0;
|
|
_buf_chars = _buf.size();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the buffer to the next item in the history.
|
|
*/
|
|
void
|
|
InputDialog::histNext(void)
|
|
{
|
|
if (_hist_it == _hist_list.end()) {
|
|
return; // nothing to do
|
|
}
|
|
|
|
// get next item, if at the end, restore the edit buffer
|
|
++_hist_it;
|
|
if (_hist_it == _hist_list.end()) {
|
|
_buf = _hist_new;
|
|
} else {
|
|
_buf = *_hist_it;
|
|
}
|
|
|
|
// move cursor to the end of line
|
|
_pos = _buf.size();
|
|
}
|
|
|
|
/**
|
|
* Sets the buffer to the previous item in the history.
|
|
*/
|
|
void
|
|
InputDialog::histPrev(void)
|
|
{
|
|
if (_hist_it == _hist_list.begin()) {
|
|
return; // nothing to do
|
|
}
|
|
|
|
// save item so we can restore the edit buffer later
|
|
if (_hist_it == _hist_list.end()) {
|
|
_hist_new = _buf;
|
|
}
|
|
|
|
// get prev item
|
|
_buf = *(--_hist_it);
|
|
|
|
// move cursor to the end of line
|
|
_pos = _buf.size();
|
|
}
|
|
|
|
/**
|
|
* Update command dialog size for view on current head.
|
|
*/
|
|
void
|
|
InputDialog::updateSize(void)
|
|
{
|
|
// Resize the child window and update the size depending.
|
|
uint old_width = _gm.width;
|
|
|
|
unsigned int width, height;
|
|
getInputSize(width, height);
|
|
resizeChild(width, height);
|
|
|
|
// If size was updated, replace the texture and recalculate display
|
|
// buffer.
|
|
if (old_width != _gm.width) {
|
|
updatePixmapSize();
|
|
bufChanged();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update background pixmap size and redraw.
|
|
*/
|
|
void
|
|
InputDialog::updatePixmapSize(void)
|
|
{
|
|
// Get new pixmap and render texture
|
|
PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler();
|
|
pm->returnPixmap(_pixmap_bg);
|
|
_pixmap_bg = pm->getPixmap(_text_wo->getWidth(), _text_wo->getHeight(), PScreen::instance()->getDepth());
|
|
|
|
_data->getTexture()->render(_pixmap_bg, 0, 0, _text_wo->getWidth(), _text_wo->getHeight());
|
|
_text_wo->setBackgroundPixmap(_pixmap_bg);
|
|
_text_wo->clear();
|
|
}
|
|
|
|
/**
|
|
* Get size of the text input widget.
|
|
*
|
|
* @param width Fill in width.
|
|
* @param height Fill in height.
|
|
*/
|
|
void
|
|
InputDialog::getInputSize(unsigned int &width, unsigned int &height)
|
|
{
|
|
Geometry head;
|
|
PScreen::instance()->getHeadInfo(PScreen::instance()->getNearestHead(_gm.x, _gm.y), head);
|
|
|
|
width = head.width / 3;
|
|
height = _data->getFont()->getHeight() + _data->getPad(PAD_UP) + _data->getPad(PAD_DOWN);
|
|
}
|