mirror of
https://github.com/edeproject/ede.git
synced 2023-08-10 21:13:03 +03:00
623 lines
15 KiB
C++
623 lines
15 KiB
C++
/*
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2012-2014 Sanel Zukan
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <FL/Fl.H>
|
|
#include <X11/Xproto.h>
|
|
|
|
#include <edelib/Debug.h>
|
|
#include <edelib/List.h>
|
|
#include <edelib/WindowXid.h>
|
|
#include <edelib/Util.h>
|
|
#include <edelib/Netwm.h>
|
|
#include <edelib/Directory.h>
|
|
#include <edelib/StrUtil.h>
|
|
|
|
#include "Panel.h"
|
|
#include "Hider.h"
|
|
|
|
/* empty space from left and right panel border */
|
|
#define INITIAL_SPACING 5
|
|
|
|
/* space between each applet */
|
|
#define DEFAULT_SPACING 5
|
|
|
|
/* default panel height */
|
|
#define DEFAULT_PANEL_H 35
|
|
|
|
#define APPLET_EXTENSION ".so"
|
|
|
|
#undef MIN
|
|
#undef MAX
|
|
#define MIN(x,y) ((x) < (y) ? (x) : (y))
|
|
#define MAX(x,y) ((x) > (y) ? (x) : (y))
|
|
|
|
EDELIB_NS_USING_LIST(10, (list,
|
|
Resource,
|
|
String,
|
|
window_xid_create,
|
|
str_trim,
|
|
netwm_window_set_strut,
|
|
netwm_window_remove_strut,
|
|
netwm_window_set_type,
|
|
netwm_workarea_get_size,
|
|
NETWM_WINDOW_TYPE_DOCK))
|
|
|
|
typedef list<Fl_Widget*> WidgetList;
|
|
typedef list<Fl_Widget*>::iterator WidgetListIt;
|
|
|
|
/* used only insid x_events */
|
|
static Panel *gpanel;
|
|
|
|
inline bool intersects(Fl_Widget *o1, Fl_Widget *o2) {
|
|
return (MAX(o1->x(), o2->x()) <= MIN(o1->x() + o1->w(), o2->x() + o2->w()) &&
|
|
MAX(o1->y(), o2->y()) <= MIN(o1->y() + o1->h(), o2->y() + o2->h()));
|
|
}
|
|
|
|
static int xerror_handler(Display *d, XErrorEvent *e) {
|
|
if(e->request_code == X_GetImage)
|
|
return 0;
|
|
|
|
/* keep it static as no multiple panels will be run */
|
|
static char buf[128];
|
|
|
|
/*
|
|
* construct the similar message format like X11 is using by default, but again, little
|
|
* bit different so we knows it comes from here
|
|
*/
|
|
snprintf(buf, sizeof(buf), "%d", e->request_code);
|
|
|
|
XGetErrorDatabaseText(d, "XRequest", buf, "%d", buf, sizeof(buf));
|
|
fprintf(stderr, "%s: ", buf);
|
|
|
|
XGetErrorText(d, e->error_code, buf, sizeof(buf));
|
|
fprintf(stderr, "%s\n", buf);
|
|
|
|
XGetErrorDatabaseText(d, "XlibMessage", "ResourceID", "%d", buf, sizeof(buf));
|
|
fprintf(stderr, " ");
|
|
fprintf(stderr, buf, e->resourceid);
|
|
fprintf(stderr, "\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void make_me_dock(Fl_Window *win) {
|
|
netwm_window_set_type(fl_xid(win), NETWM_WINDOW_TYPE_DOCK);
|
|
}
|
|
|
|
static int x_events(int ev) {
|
|
/*
|
|
* This is quite stupid to do, but hopefully very reliable. When screen is resized
|
|
* root window was resized too and using those sizes, panel is placed. Tracking workarea changes
|
|
* isn't much of use, as setting struts dimensions will affect workarea dimension too.
|
|
*/
|
|
if(fl_xevent->type == ConfigureNotify &&
|
|
(fl_xevent->xconfigure.window == RootWindow(fl_display, fl_screen))) {
|
|
gpanel->update_size_and_pos(false, false,
|
|
fl_xevent->xconfigure.x,
|
|
fl_xevent->xconfigure.y,
|
|
fl_xevent->xconfigure.width,
|
|
fl_xevent->xconfigure.height);
|
|
}
|
|
|
|
/* let others receive the same event */
|
|
return 0;
|
|
}
|
|
|
|
/* horizontaly centers widget in the panel */
|
|
static void fix_widget_h(Fl_Widget *o, Panel *self) {
|
|
int ph, wy, H = self->h() - 10;
|
|
|
|
ph = self->panel_h() / 2;
|
|
wy = ph - (H / 2);
|
|
o->resize(o->x(), wy, o->w(), H);
|
|
}
|
|
|
|
static void add_from_list(WidgetList &lst, Panel *self, int &X, bool inc) {
|
|
WidgetListIt it = lst.begin(), ite = lst.end();
|
|
Fl_Widget *o;
|
|
|
|
while(it != ite) {
|
|
o = *it;
|
|
|
|
/* 'inc == false' means we are going from right to left */
|
|
if(!inc)
|
|
X -= o->w();
|
|
|
|
/* place it correctly */
|
|
o->position(X, o->y());
|
|
|
|
self->add(o);
|
|
|
|
if(inc) {
|
|
X += DEFAULT_SPACING;
|
|
X += o->w();
|
|
} else {
|
|
X -= DEFAULT_SPACING;
|
|
}
|
|
|
|
it = lst.erase(it);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static void move_widget(Panel *self, Fl_Widget *o, int &sx, int &sy) {
|
|
int tx, ty, px, py;
|
|
Fl_Widget *const *a;
|
|
|
|
tx = Fl::event_x() - sx;
|
|
ty = Fl::event_y() - sy;
|
|
|
|
px = o->x() + tx;
|
|
py = o->y() + ty;
|
|
|
|
/* check the bounds */
|
|
if(px < 0)
|
|
px = 0;
|
|
if(px + o->w() > self->panel_w())
|
|
px = self->panel_w() - o->w();
|
|
if(py < 0)
|
|
py = 0;
|
|
if(py + o->h() > self->y() + self->panel_h())
|
|
py = self->y() + self->panel_h() - o->h();
|
|
if(py + o->h() > self->panel_h())
|
|
py = self->panel_h() - o->h();
|
|
|
|
/* o->position(px, py); */
|
|
o->position(px, o->y());
|
|
|
|
/* find possible collision and move others */
|
|
a = self->array();
|
|
for(int i = self->children(); i--; ) {
|
|
if(o == a[i])
|
|
continue;
|
|
|
|
if(intersects(a[i], o)) {
|
|
px = a[i]->x() + tx;
|
|
py = a[i]->y() + ty;
|
|
|
|
/* check the bounds */
|
|
if(px < 0)
|
|
px = 0;
|
|
if(px + o->w() > self->panel_w())
|
|
px = self->panel_w() - o->w();
|
|
if(py < 0)
|
|
py = 0;
|
|
if(py + o->h() > self->y() + self->panel_h())
|
|
py = self->y() + self->panel_h() - o->h();
|
|
if(py + o->h() > self->panel_h())
|
|
py = self->panel_h() - o->h();
|
|
|
|
/* a[i]->position(px, py); */
|
|
a[i]->position(px, a[i]->y());
|
|
}
|
|
}
|
|
|
|
/* update current position */
|
|
sx = Fl::event_x();
|
|
sy = Fl::event_y();
|
|
|
|
o->parent()->redraw();
|
|
}
|
|
#endif
|
|
|
|
Panel::Panel() : PanelWindow(300, 30, "ede-panel") {
|
|
gpanel = this;
|
|
hider = NULL;
|
|
|
|
clicked = 0;
|
|
sx = sy = 0;
|
|
vpos = PANEL_POSITION_BOTTOM;
|
|
screen_x = screen_y = screen_w = screen_h = screen_h_half = 0;
|
|
width_perc = 100;
|
|
can_move_widgets = false;
|
|
can_drag = false;
|
|
|
|
box(FL_UP_BOX);
|
|
read_config();
|
|
}
|
|
|
|
void Panel::read_config(void) {
|
|
Resource r;
|
|
r.load("ede-panel");
|
|
|
|
int tmp;
|
|
if(r.get("Panel", "position", tmp, PANEL_POSITION_BOTTOM)) {
|
|
if((tmp != PANEL_POSITION_TOP) && (tmp != PANEL_POSITION_BOTTOM))
|
|
tmp = PANEL_POSITION_BOTTOM;
|
|
vpos = tmp;
|
|
}
|
|
|
|
if(r.get("Panel", "width", tmp, 100)) {
|
|
if(tmp > 100) tmp = 100;
|
|
else if(tmp < 10) tmp = 10;
|
|
|
|
width_perc = tmp;
|
|
}
|
|
|
|
/* small button on the right edge for panel sliding */
|
|
r.get("Panel", "hider", tmp, 1);
|
|
if(tmp) {
|
|
if(!hider) {
|
|
hider = new Hider();
|
|
add(hider);
|
|
}
|
|
|
|
/* in case was hidden before */
|
|
hider->show();
|
|
} else {
|
|
if(hider) hider->hide();
|
|
}
|
|
|
|
char buf[128];
|
|
if(r.get("Panel", "applets", buf, sizeof(buf)))
|
|
load_applets(buf, &r);
|
|
else
|
|
load_applets(0, &r);
|
|
}
|
|
|
|
void Panel::save_config(void) {
|
|
Resource r;
|
|
/* use whatever was explicitly stored in configuration */
|
|
r.load("ede-panel");
|
|
|
|
r.set("Panel", "position", vpos);
|
|
r.save("ede-panel");
|
|
}
|
|
|
|
void Panel::do_layout(void) {
|
|
E_RETURN_IF_FAIL(children() > 0);
|
|
|
|
Fl_Widget *o;
|
|
unsigned long opts;
|
|
unsigned int lsz;
|
|
int X, W, free_w;
|
|
|
|
WidgetList left, right, center, unmanaged, resizable_h;
|
|
|
|
for(int i = 0; i < children(); i++) {
|
|
o = child(i);
|
|
|
|
/* first resize it to some reasonable height and center it vertically */
|
|
fix_widget_h(o, this);
|
|
|
|
/* manage hider specifically */
|
|
if(hider && o == hider) {
|
|
right.push_back(o);
|
|
continue;
|
|
}
|
|
|
|
/* could be slow, but I'm relaying how number of loaded applets will not be that large */
|
|
if(!mgr.get_applet_options(o, opts)) {
|
|
/* here are put widgets not loaded as applets */
|
|
unmanaged.push_back(o);
|
|
continue;
|
|
}
|
|
|
|
if(opts & EDE_PANEL_APPLET_OPTION_RESIZABLE_H)
|
|
resizable_h.push_back(o);
|
|
|
|
if(opts & EDE_PANEL_APPLET_OPTION_ALIGN_LEFT) {
|
|
/* first item will be most leftest */
|
|
left.push_back(o);
|
|
continue;
|
|
}
|
|
|
|
if(opts & EDE_PANEL_APPLET_OPTION_ALIGN_RIGHT) {
|
|
/* first item will be most rightest */
|
|
right.push_back(o);
|
|
continue;
|
|
}
|
|
|
|
/* rest of them */
|
|
center.push_back(o);
|
|
}
|
|
|
|
/* make sure we at the end have all widgets, so we can overwrite group array */
|
|
lsz = left.size() + center.size() + right.size() + unmanaged.size();
|
|
E_ASSERT(lsz == (unsigned int)children() && "Size of layout lists size not equal to group size");
|
|
|
|
X = INITIAL_SPACING;
|
|
|
|
/*
|
|
* Call add() on each element, processing lists in order. add() will remove element
|
|
* in group array and put it at the end of array. At the end, we should have array ordered by
|
|
* layout flags.
|
|
*/
|
|
add_from_list(left, this, X, true);
|
|
add_from_list(center, this, X, true);
|
|
add_from_list(unmanaged, this, X, true);
|
|
|
|
free_w = X;
|
|
|
|
/* elements right will be put from starting from the right panel border */
|
|
X = w() - INITIAL_SPACING;
|
|
add_from_list(right, this, X, false);
|
|
|
|
/*
|
|
* Code for horizontal streching.
|
|
*
|
|
* FIXME: This code pretty sucks and need better rewrite in the future.
|
|
* To work properly, applets that will be streched must be successive or everything will be
|
|
* messed up. Also, applets that are placed right2left does not work with it; they will be resized to right.
|
|
*/
|
|
if(resizable_h.empty())
|
|
return;
|
|
|
|
/* calculate free space for horizontal alignement, per item in resizable_h list */
|
|
free_w = (X - free_w) / resizable_h.size();
|
|
if(!free_w) free_w = 0;
|
|
|
|
/*
|
|
* since add_from_list() will already reserve some space by current child width and default spacing,
|
|
* those values will be used again or holes will be made
|
|
*/
|
|
WidgetListIt it = resizable_h.begin(), ite = resizable_h.end();
|
|
o = resizable_h.front();
|
|
X = o->x();
|
|
|
|
while(it != ite) {
|
|
o = *it;
|
|
|
|
W = o->w() + free_w;
|
|
o->resize(X, o->y(), W, o->h());
|
|
X += W + DEFAULT_SPACING;
|
|
|
|
it = resizable_h.erase(it);
|
|
}
|
|
}
|
|
|
|
void Panel::set_strut(short state) {
|
|
if(state & PANEL_STRUT_REMOVE)
|
|
netwm_window_remove_strut(fl_xid(this));
|
|
|
|
/* no other flags */
|
|
if(state == PANEL_STRUT_REMOVE) return;
|
|
|
|
int sizes[4] = {0, 0, 0, 0};
|
|
sizes[state & PANEL_STRUT_BOTTOM ? 3 : 2] = h();
|
|
|
|
/* TODO: netwm_window_set_strut_partial() */
|
|
netwm_window_set_strut(fl_xid(this), sizes[0], sizes[1], sizes[2], sizes[3]);
|
|
}
|
|
|
|
void Panel::show(void) {
|
|
if(shown()) return;
|
|
|
|
/*
|
|
* hush known FLTK bug with XGetImage; a lot of errors will be printed when menu icons goes
|
|
* outside screen; this can stuck ede-panel at some point
|
|
*/
|
|
XSetErrorHandler((XErrorHandler) xerror_handler);
|
|
update_size_and_pos(true, true);
|
|
|
|
/* collect messages */
|
|
Fl::add_handler(x_events);
|
|
}
|
|
|
|
void Panel::hide(void) {
|
|
Fl::remove_handler(x_events);
|
|
set_strut(PANEL_STRUT_REMOVE);
|
|
|
|
/* clear loaded widgets */
|
|
mgr.clear(this);
|
|
|
|
/* clear whatever was left out, but was not part of applet manager */
|
|
clear();
|
|
|
|
save_config();
|
|
Fl_Window::hide();
|
|
}
|
|
|
|
void Panel::update_size_and_pos(bool create_xid, bool update_strut) {
|
|
int X, Y, W, H;
|
|
|
|
/* figure out screen dimensions */
|
|
if(!netwm_workarea_get_size(X, Y, W, H))
|
|
Fl::screen_xywh(X, Y, W, H);
|
|
|
|
update_size_and_pos(create_xid, update_strut, X, Y, W, H);
|
|
}
|
|
|
|
void Panel::update_size_and_pos(bool create_xid, bool update_strut, int X, int Y, int W, int H) {
|
|
/* do not update ourself if we screen sizes are the same */
|
|
if(screen_x == X && screen_y == Y && screen_w == W && screen_h == H)
|
|
return;
|
|
|
|
screen_x = X;
|
|
screen_y = Y;
|
|
screen_w = W;
|
|
screen_h = H;
|
|
screen_h_half = screen_h / 2;
|
|
|
|
/* calculate panel percentage width if given */
|
|
if(width_perc < 100) {
|
|
W = (width_perc * screen_w) / 100;
|
|
X = (screen_w / 2) - (W / 2);
|
|
}
|
|
|
|
/* set size as soon as possible, since do_layout() depends on it */
|
|
size(W, DEFAULT_PANEL_H);
|
|
|
|
do_layout();
|
|
|
|
if(create_xid) window_xid_create(this, make_me_dock);
|
|
|
|
/* position it, this is done after XID was created */
|
|
if(vpos == PANEL_POSITION_BOTTOM) {
|
|
position(X, screen_h - h());
|
|
if(width_perc >= 100 && update_strut)
|
|
set_strut(PANEL_STRUT_REMOVE | PANEL_STRUT_BOTTOM);
|
|
} else {
|
|
/*
|
|
* I'm not sure what the hell is going on here; if 'y == 0', X will
|
|
* move panel few pixels down, even if FLTK is reporting y() == 0.
|
|
* Seems like 1 will solve this.
|
|
*/
|
|
position(X, Y == 0 ? 1 : Y);
|
|
if(width_perc >= 100 && update_strut)
|
|
set_strut(PANEL_STRUT_REMOVE | PANEL_STRUT_TOP);
|
|
}
|
|
}
|
|
|
|
int Panel::handle(int e) {
|
|
return Fl_Window::handle(e);
|
|
|
|
#if 0
|
|
switch(e) {
|
|
case FL_PUSH:
|
|
clicked = Fl::belowmouse();
|
|
|
|
if(clicked == this)
|
|
clicked = 0;
|
|
else if(clicked && Fl::event_inside(clicked) && clicked->takesevents())
|
|
clicked->handle(e);
|
|
|
|
/* record push position for possible child drag */
|
|
sx = Fl::event_x();
|
|
sy = Fl::event_y();
|
|
return 1;
|
|
|
|
case FL_DRAG: {
|
|
/* send drag events to children and do not drag panel in the mean time */
|
|
if(clicked && clicked != this && Fl::event_inside(clicked) && clicked->takesevents()) {
|
|
cursor(FL_CURSOR_MOVE);
|
|
return clicked->handle(e);
|
|
} else {
|
|
printf("XXXy %p %p\n", clicked, this);
|
|
if(!can_drag) return 1;
|
|
cursor(FL_CURSOR_MOVE);
|
|
|
|
/* snap it to the top or bottom, depending on pressed mouse location */
|
|
if(Fl::event_y_root() <= screen_h_half && y() > screen_h_half) {
|
|
position(x(), screen_y);
|
|
if(width_perc >= 100)
|
|
set_strut(PANEL_STRUT_TOP);
|
|
vpos = PANEL_POSITION_TOP;
|
|
}
|
|
|
|
if(Fl::event_y_root() > screen_h_half && y() < screen_h_half) {
|
|
position(x(), screen_h - h());
|
|
if(width_perc >= 100)
|
|
set_strut(PANEL_STRUT_BOTTOM);
|
|
vpos = PANEL_POSITION_BOTTOM;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
case FL_RELEASE:
|
|
cursor(FL_CURSOR_DEFAULT);
|
|
|
|
if(clicked && clicked->takesevents()) {
|
|
clicked->handle(e);
|
|
clicked = 0;
|
|
}
|
|
|
|
return 1;
|
|
|
|
case FL_KEYBOARD:
|
|
/* do not quit on Esc key */
|
|
if(Fl::event_key() == FL_Escape)
|
|
return 1;
|
|
/* fallthrough */
|
|
}
|
|
return Fl_Window::handle(e);
|
|
#endif
|
|
}
|
|
|
|
void Panel::load_applets(const char *applets, PanelResource *res) {
|
|
mgr.clear(this);
|
|
|
|
/*
|
|
* Hardcoded order, unless configuration file was found. For easier and uniform parsing
|
|
* (similar string is expected from configuration), fallback is plain string.
|
|
*/
|
|
static const char *fallback =
|
|
#ifndef EDE_PANEL_LOCAL_APPLETS
|
|
"start_menu,"
|
|
"quick_launch,"
|
|
"pager,"
|
|
"clock,"
|
|
"taskbar,"
|
|
"keyboard_layout,"
|
|
"battery_monitor,"
|
|
"cpu_monitor,"
|
|
# ifdef __linux__
|
|
"mem_monitor,"
|
|
# endif
|
|
"system_tray"
|
|
#else /* EDE_PANEL_LOCAL_APPLETS */
|
|
"./applets/start-menu/start_menu,"
|
|
"./applets/quick-launch/quick_launch,"
|
|
"./applets/pager/pager,"
|
|
"./applets/clock/clock,"
|
|
"./applets/taskbar/taskbar,"
|
|
"./applets/keyboard-layout/keyboard_layout,"
|
|
"./applets/battery-monitor/battery_monitor,"
|
|
"./applets/cpu-monitor/cpu_monitor,"
|
|
# ifdef __linux__
|
|
"./applets/mem-monitor/mem_monitor,"
|
|
# endif
|
|
"./applets/system-tray/system_tray"
|
|
#endif /* EDE_PANEL_LOCAL_APPLETS */
|
|
;
|
|
|
|
String dir = Resource::find_data("panel-applets");
|
|
E_RETURN_IF_FAIL(!dir.empty());
|
|
|
|
char *dup = strdup(applets ? applets : fallback);
|
|
E_RETURN_IF_FAIL(dup != NULL);
|
|
|
|
char path[PATH_MAX];
|
|
for(char *tok = strtok(dup, ","); tok; tok = strtok(NULL, ",")) {
|
|
tok = str_trim(tok);
|
|
|
|
#ifndef EDE_PANEL_LOCAL_APPLETS
|
|
snprintf(path, sizeof(path), "%s" E_DIR_SEPARATOR_STR "%s" APPLET_EXTENSION, dir.c_str(), tok);
|
|
#else
|
|
/* only for testing, so path separator is hardcoded */
|
|
snprintf(path, sizeof(path), "%s" APPLET_EXTENSION, tok);
|
|
#endif
|
|
mgr.load(path);
|
|
}
|
|
|
|
free(dup);
|
|
mgr.fill_group(this, res);
|
|
}
|
|
|
|
/* TODO: can be better */
|
|
void Panel::apply_struts(bool apply) {
|
|
if(!(width_perc >= 100)) return;
|
|
|
|
int bottom = 0, top = 0;
|
|
|
|
if(vpos == PANEL_POSITION_TOP)
|
|
top = !apply ? 1 : h();
|
|
else
|
|
bottom = !apply ? 1 : h();
|
|
|
|
netwm_window_set_strut(fl_xid(this), 0, 0, top, bottom);
|
|
}
|