mirror of
https://github.com/edeproject/ede.git
synced 2023-08-10 21:13:03 +03:00
04e1c63cb2
eiconman recognize plain files besides .desktop files.
994 lines
23 KiB
C++
994 lines
23 KiB
C++
/*
|
|
* $Id$
|
|
*
|
|
* Eiconman, desktop and icon manager
|
|
* Part of Equinox Desktop Environment (EDE).
|
|
* Copyright (c) 2000-2007 EDE Authors.
|
|
*
|
|
* This program is licensed under terms of the
|
|
* GNU General Public License version 2 or newer.
|
|
* See COPYING for details.
|
|
*/
|
|
|
|
#include "eiconman.h"
|
|
#include "DesktopIcon.h"
|
|
#include "DesktopConfig.h"
|
|
#include "Utils.h"
|
|
|
|
#include <edelib/Nls.h>
|
|
#include <edelib/Debug.h>
|
|
#include <edelib/Config.h>
|
|
#include <edelib/Directory.h>
|
|
#include <edelib/StrUtil.h>
|
|
#include <edelib/File.h>
|
|
#include <edelib/IconTheme.h>
|
|
#include <edelib/Item.h>
|
|
#include <edelib/MimeType.h>
|
|
|
|
#include <fltk/Divider.h>
|
|
#include <fltk/damage.h>
|
|
#include <fltk/Color.h>
|
|
#include <fltk/events.h>
|
|
#include <fltk/run.h>
|
|
#include <fltk/x11.h>
|
|
#include <fltk/SharedImage.h>
|
|
|
|
#include <signal.h>
|
|
#include <X11/Xproto.h> // CARD32
|
|
|
|
#include <stdlib.h> // rand, srand
|
|
#include <time.h> // time
|
|
#include <stdio.h> // snprintf
|
|
|
|
/*
|
|
* NOTE: DO NOT set 'using namespace fltk' here
|
|
* since fltk::Window will collide with Window from X11
|
|
* resulting compilation errors.
|
|
*
|
|
* This is why I hate this namespace shit !
|
|
*/
|
|
|
|
#define CONFIG_NAME "eiconman.conf"
|
|
|
|
#define SELECTION_SINGLE (fltk::event_button() == 1)
|
|
#define SELECTION_MULTI (fltk::event_button() == 1 && \
|
|
(fltk::get_key_state(fltk::LeftShiftKey) ||\
|
|
fltk::get_key_state(fltk::RightShiftKey)))
|
|
|
|
#define MIN(x,y) ((x) < (y) ? (x) : (y))
|
|
#define MAX(x,y) ((x) > (y) ? (x) : (y))
|
|
|
|
/*
|
|
* Added since fltk DAMAGE_OVERLAY value is used in few different contexts
|
|
* and re-using it will do nothing. Yuck!
|
|
*/
|
|
#define EDAMAGE_OVERLAY 2
|
|
|
|
Desktop* Desktop::pinstance = NULL;
|
|
bool running = true;
|
|
|
|
inline unsigned int random_pos(int max) {
|
|
return (rand() % max);
|
|
}
|
|
|
|
inline bool intersects(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) {
|
|
return (MAX(x1, x2) <= MIN(w1, w2) &&
|
|
MAX(y1, y2) <= MIN(h1, h2));
|
|
}
|
|
|
|
inline void dpy_sizes(int& width, int& height) {
|
|
fltk::open_display();
|
|
width = DisplayWidth(fltk::xdisplay, fltk::xscreen);
|
|
height = DisplayHeight(fltk::xdisplay, fltk::xscreen);
|
|
}
|
|
|
|
void exit_signal(int signum) {
|
|
EDEBUG(ESTRLOC ": Exiting (got signal %d)\n", signum);
|
|
running = false;
|
|
}
|
|
|
|
/*
|
|
* It is used to notify desktop when _NET_CURRENT_DESKTOP is triggered.
|
|
* FIXME: _NET_WORKAREA is nice thing that could set here too :)
|
|
*
|
|
* FIXME: XInternAtom should be placed somewhere else
|
|
*/
|
|
int desktop_xmessage_handler(int e, fltk::Window*) {
|
|
Atom nd = XInternAtom(fltk::xdisplay, "_NET_CURRENT_DESKTOP", False);
|
|
|
|
if(fltk::xevent.type == PropertyNotify) {
|
|
if(fltk::xevent.xproperty.atom == nd) {
|
|
EDEBUG(ESTRLOC ": Desktop changed !!!\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void background_cb(fltk::Widget*, void*) {
|
|
DesktopConfig dc;
|
|
dc.run();
|
|
}
|
|
|
|
Desktop::Desktop() : fltk::Window(0, 0, 100, 100, "")
|
|
{
|
|
moving = false;
|
|
|
|
desktops_num = 0;
|
|
curr_desktop = 0;
|
|
|
|
selbox = new SelectionOverlay;
|
|
selbox->x = selbox->y = selbox->w = selbox->h = 0;
|
|
selbox->show = false;
|
|
|
|
dsett = new DesktopSettings;
|
|
dsett->color = 0;
|
|
dsett->wp_use = false;
|
|
dsett->wp_image = NULL;
|
|
|
|
// fallback if update_workarea() fails
|
|
int dw, dh;
|
|
dpy_sizes(dw, dh);
|
|
resize(dw, dh);
|
|
|
|
update_workarea();
|
|
|
|
read_config();
|
|
|
|
begin();
|
|
|
|
pmenu = new fltk::PopupMenu(0, 0, 450, 50);
|
|
pmenu->begin();
|
|
edelib::Item* it = new edelib::Item(_("&New desktop item"));
|
|
it->offset_x(12, 12);
|
|
new fltk::Divider();
|
|
it = new edelib::Item(_("&Line up vertical"));
|
|
it->offset_x(12, 12);
|
|
it = new edelib::Item(_("&Line up horizontal"));
|
|
it->offset_x(12, 12);
|
|
it = new edelib::Item(_("&Arrange by name"));
|
|
it->offset_x(12, 12);
|
|
new fltk::Divider();
|
|
it = new edelib::Item(_("&Icon settings"));
|
|
it->offset_x(12, 12);
|
|
|
|
edelib::Item* itbg = new edelib::Item(_("&Background settings"));
|
|
itbg->offset_x(12, 12);
|
|
itbg->callback(background_cb);
|
|
pmenu->end();
|
|
pmenu->type(fltk::PopupMenu::POPUP3);
|
|
|
|
end();
|
|
|
|
if(dsett->wp_use)
|
|
set_wallpaper(dsett->wp_path.c_str(), false);
|
|
else
|
|
set_bg_color(dsett->color, false);
|
|
}
|
|
|
|
Desktop::~Desktop() {
|
|
EDEBUG(ESTRLOC ": Desktop::~Desktop()\n");
|
|
|
|
save_config();
|
|
|
|
if(selbox)
|
|
delete selbox;
|
|
|
|
/*
|
|
* icons member deleting is not needed, since add_icon will
|
|
* append to icons and to fltk::Group array. Desktop at the end
|
|
* will cleanup fltk::Group array.
|
|
*/
|
|
icons.clear();
|
|
|
|
if(dsett)
|
|
delete dsett;
|
|
}
|
|
|
|
void Desktop::init(void) {
|
|
if(Desktop::pinstance != NULL)
|
|
return;
|
|
Desktop::pinstance = new Desktop();
|
|
}
|
|
|
|
void Desktop::shutdown(void) {
|
|
if(Desktop::pinstance == NULL)
|
|
return;
|
|
|
|
delete Desktop::pinstance;
|
|
Desktop::pinstance = NULL;
|
|
}
|
|
|
|
Desktop* Desktop::instance(void) {
|
|
EASSERT(Desktop::pinstance != NULL && "Desktop::init() should be run first");
|
|
return Desktop::pinstance;
|
|
}
|
|
|
|
void Desktop::update_workarea(void) {
|
|
int X,Y,W,H;
|
|
if(net_get_workarea(X, Y, W, H))
|
|
resize(X,Y,W,H);
|
|
}
|
|
|
|
void Desktop::set_bg_color(unsigned int c, bool do_redraw) {
|
|
EASSERT(dsett != NULL);
|
|
|
|
dsett->color = c;
|
|
color(c);
|
|
|
|
if(do_redraw)
|
|
redraw();
|
|
}
|
|
|
|
void Desktop::set_wallpaper(const char* path, bool do_redraw) {
|
|
EASSERT(path != NULL);
|
|
EASSERT(dsett != NULL);
|
|
/*
|
|
* Prevent cases 'set_wallpaper(dsett->wp_path.c_str())' since assignement
|
|
* will nullify pointers. Very hard to find bug! (believe me, after few hours)
|
|
*/
|
|
if(dsett->wp_path.c_str() != path)
|
|
dsett->wp_path = path;
|
|
|
|
dsett->wp_image = fltk::SharedImage::get(path);
|
|
/*
|
|
* SharedImage::get() will return NULL if is unable to read the image
|
|
* and that is exactly what is wanted here since draw() function will
|
|
* skip drawing image in nulled case. Blame user for this :)
|
|
*/
|
|
image(dsett->wp_image);
|
|
|
|
if(do_redraw)
|
|
redraw();
|
|
}
|
|
|
|
void Desktop::set_wallpaper(fltk::Image* im, bool do_redraw) {
|
|
if(dsett->wp_image == im)
|
|
return;
|
|
|
|
image(dsett->wp_image);
|
|
if(do_redraw)
|
|
redraw();
|
|
}
|
|
|
|
void Desktop::read_config(void) {
|
|
edelib::Config conf;
|
|
if(!conf.load(CONFIG_NAME)) {
|
|
EDEBUG(ESTRLOC ": Can't load %s, using default...\n", CONFIG_NAME);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* TODO:
|
|
* Add IconArea[X,Y,W,H] so icons can live
|
|
* inside that area only (aka margins).
|
|
*/
|
|
|
|
// read Desktop section
|
|
int default_bg_color = fltk::BLUE;
|
|
int default_wp_use = false;
|
|
char wpath[256];
|
|
|
|
conf.get("Desktop", "Color", dsett->color, default_bg_color);
|
|
|
|
if(conf.error() != edelib::CONF_ERR_SECTION) {
|
|
conf.get("Desktop", "WallpaperUse", dsett->wp_use, default_wp_use);
|
|
conf.get("Desktop", "Wallpaper", wpath, sizeof(wpath));
|
|
|
|
dsett->wp_path = wpath;
|
|
|
|
// keep path but disable wallpaper if file does not exists
|
|
if(!edelib::file_exists(wpath)) {
|
|
EDEBUG(ESTRLOC ": %s as wallpaper does not exists\n", wpath);
|
|
dsett->wp_use = false;
|
|
}
|
|
} else {
|
|
// color is already filled
|
|
dsett->wp_use = default_wp_use;
|
|
}
|
|
|
|
// read Icons section
|
|
conf.get("Icons", "Label Background", gisett.label_background, 46848);
|
|
conf.get("Icons", "Label Foreground", gisett.label_foreground, fltk::WHITE);
|
|
conf.get("Icons", "Label Fontsize", gisett.label_fontsize, 12);
|
|
conf.get("Icons", "Label Maxwidth", gisett.label_maxwidth, 75);
|
|
conf.get("Icons", "Label Transparent",gisett.label_transparent, false);
|
|
conf.get("Icons", "Label Visible", gisett.label_draw, true);
|
|
conf.get("Icons", "Gridspacing", gisett.gridspacing, 16);
|
|
conf.get("Icons", "OneClickExec", gisett.one_click_exec, false);
|
|
conf.get("Icons", "AutoArrange", gisett.auto_arr, true);
|
|
|
|
/*
|
|
* Now try to load icons, first looking inside ~/Desktop directory
|
|
* then inside config since config could contain removed entries
|
|
* from $HOME/Desktop
|
|
*
|
|
* FIXME: dir_exists() can't handle '~/Desktop' ???
|
|
*/
|
|
//load_icons("/home/sanel/Desktop", conf);
|
|
edelib::String dd = edelib::dir_home();
|
|
dd += "/Desktop";
|
|
load_icons(dd.c_str(), conf);
|
|
|
|
#if 0
|
|
EDEBUG("----------------------------------------------------------\n");
|
|
EDEBUG("d Color : %i\n", bg_color);
|
|
EDEBUG("d WallpaperUse: %i\n", wp_use);
|
|
EDEBUG("i label bkg : %i\n", gisett.label_background);
|
|
EDEBUG("i label fg : %i\n", gisett.label_foreground);
|
|
EDEBUG("i label fsize : %i\n", gisett.label_fontsize);
|
|
EDEBUG("i label maxw : %i\n", gisett.label_maxwidth);
|
|
EDEBUG("i label trans : %i\n", gisett.label_transparent);
|
|
EDEBUG("i label vis : %i\n", gisett.label_draw);
|
|
EDEBUG("i gridspace : %i\n", gisett.gridspacing);
|
|
EDEBUG("i oneclick : %i\n", gisett.one_click_exec);
|
|
EDEBUG("i auto_arr : %i\n", gisett.auto_arr);
|
|
#endif
|
|
}
|
|
|
|
void Desktop::load_icons(const char* path, edelib::Config& conf) {
|
|
EASSERT(path != NULL);
|
|
|
|
if(!edelib::dir_exists(path)) {
|
|
EDEBUG(ESTRLOC ": %s does not exists\n", path);
|
|
return;
|
|
}
|
|
|
|
edelib::vector<edelib::String> lst;
|
|
|
|
// list without full path, so we can use name as entry in config file
|
|
if(!dir_list(path, lst)) {
|
|
EDEBUG(ESTRLOC ": Can't read %s\n", path);
|
|
return;
|
|
}
|
|
|
|
const char* name = NULL;
|
|
int icon_x = 0;
|
|
int icon_y = 0;
|
|
edelib::String full_path;
|
|
full_path.reserve(256);
|
|
|
|
bool can_add = false;
|
|
|
|
edelib::MimeType mt;
|
|
|
|
unsigned int sz = lst.size();
|
|
for(unsigned int i = 0; i < sz; i++) {
|
|
name = lst[i].c_str();
|
|
|
|
full_path = path;
|
|
full_path += '/';
|
|
full_path += name;
|
|
|
|
can_add = false;
|
|
|
|
IconSettings is;
|
|
|
|
// see is possible .desktop file, icon, name fields are filled from read_desktop_file()
|
|
if(edelib::str_ends(name, ".desktop")) {
|
|
if(read_desktop_file(full_path.c_str(), is))
|
|
can_add = true;
|
|
} else {
|
|
// then try to figure out it's mime; if fails, ignore it
|
|
if(mt.set(full_path.c_str())) {
|
|
/*
|
|
* FIXME: MimeType fails for directories
|
|
* Temp solution untill that is fixed in edelib
|
|
*/
|
|
if(edelib::dir_exists(full_path.c_str()))
|
|
is.icon = "folder";
|
|
else
|
|
is.icon = mt.icon_name();
|
|
|
|
// icon label is name of file
|
|
is.name = name;
|
|
is.type = ICON_FILE;
|
|
|
|
can_add = true;
|
|
} else {
|
|
EDEBUG(ESTRLOC ": Failed mime-type for %s, ignoring...\n", name);
|
|
can_add = false;
|
|
}
|
|
}
|
|
|
|
if(can_add) {
|
|
is.key_name = name;
|
|
// random_pos() is used if X/Y keys are not found
|
|
conf.get(name, "X", icon_x, random_pos(w() - 10));
|
|
conf.get(name, "Y", icon_y, random_pos(h() - 10));
|
|
|
|
EDEBUG(ESTRLOC ": %s found with: %i %i\n", name, icon_x, icon_y);
|
|
is.x = icon_x;
|
|
is.y = icon_y;
|
|
|
|
DesktopIcon* dic = new DesktopIcon(&gisett, &is);
|
|
add_icon(dic);
|
|
}
|
|
}
|
|
}
|
|
|
|
// reads .desktop file content
|
|
bool Desktop::read_desktop_file(const char* path, IconSettings& is) {
|
|
EASSERT(path != NULL);
|
|
|
|
if(!edelib::file_exists(path)) {
|
|
EDEBUG(ESTRLOC ": %s don't exists\n");
|
|
return false;
|
|
}
|
|
|
|
edelib::Config dconf;
|
|
if(!dconf.load(path)) {
|
|
EDEBUG(ESTRLOC ": Can't read %s\n", path);
|
|
return false;
|
|
}
|
|
|
|
char buff[128];
|
|
int buffsz = sizeof(buff);
|
|
|
|
/*
|
|
* First check for 'EmptyIcon' key since via it is determined
|
|
* is icon trash type or not (in case of trash, 'Icon' key is used for full trash).
|
|
* FIXME: any other way to check for trash icons ???
|
|
*/
|
|
if(dconf.get("Desktop Entry", "EmptyIcon", buff, buffsz)) {
|
|
is.type = ICON_TRASH;
|
|
is.icon = buff;
|
|
} else
|
|
is.type = ICON_NORMAL;
|
|
|
|
if(dconf.error() == edelib::CONF_ERR_SECTION) {
|
|
EDEBUG(ESTRLOC ": %s is not valid .desktop file\n");
|
|
return false;
|
|
}
|
|
|
|
dconf.get("Desktop Entry", "Icon", buff, buffsz);
|
|
|
|
if(is.type == ICON_TRASH)
|
|
is.icon2 = buff;
|
|
else {
|
|
is.icon = buff;
|
|
is.type = ICON_NORMAL;
|
|
}
|
|
|
|
EDEBUG(ESTRLOC ": Icon is: %s\n", is.icon.c_str());
|
|
|
|
char name[256];
|
|
// FIXME: UTF-8 safety
|
|
if(dconf.get_localized("Desktop Entry", "Name", name, sizeof(name))) {
|
|
EDEBUG(ESTRLOC ": Name is: %s\n", name);
|
|
is.name = name;
|
|
}
|
|
|
|
/*
|
|
* Specs (desktop entry file) said that Type=Link means there must
|
|
* be somewhere URL key. My thoughts is that in this case Exec key
|
|
* should be ignored, even if exists. Then I will follow my thoughts.
|
|
*
|
|
* FIXME: 'Type' should be seen as test for .desktop file; if key
|
|
* is not present, then file should not be considered as .desktop. This
|
|
* should be checked before all others.
|
|
*/
|
|
if(!dconf.get("Desktop Entry", "Type", buff, buffsz)) {
|
|
EDEBUG(ESTRLOC ": Missing mandatory Type key\n");
|
|
return false;
|
|
}
|
|
|
|
if(strncmp(buff, "Link", 4) == 0) {
|
|
is.cmd_is_url = true;
|
|
if(!dconf.get("Desktop Entry", "URL", buff, buffsz)) {
|
|
EDEBUG(ESTRLOC ": Missing expected URL key\n");
|
|
return false;
|
|
}
|
|
is.cmd = buff;
|
|
} else if(strncmp(buff, "Application", 11) == 0) {
|
|
is.cmd_is_url = false;
|
|
if(!dconf.get("Desktop Entry", "Exec", buff, buffsz)) {
|
|
EDEBUG(ESTRLOC ": Missing expected Exec key\n");
|
|
return false;
|
|
}
|
|
is.cmd = buff;
|
|
} else if(strncmp(buff, "Directory", 11) == 0) {
|
|
EDEBUG(ESTRLOC ": Type = Directory is not implemented yet\n");
|
|
return false;
|
|
} else {
|
|
EDEBUG(ESTRLOC ": Unknown %s type, ignoring...\n", buff);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Desktop::save_config(void) {
|
|
edelib::Config conf;
|
|
|
|
conf.set("Desktop", "Color", dsett->color);
|
|
conf.set("Desktop", "WallpaperUse", dsett->wp_use);
|
|
conf.set("Desktop", "Wallpaper", dsett->wp_path.c_str());
|
|
|
|
conf.set("Icons", "Label Background", gisett.label_background);
|
|
conf.set("Icons", "Label Foreground", gisett.label_foreground);
|
|
conf.set("Icons", "Label Fontsize", gisett.label_fontsize);
|
|
conf.set("Icons", "Label Maxwidth", gisett.label_maxwidth);
|
|
conf.set("Icons", "Label Transparent",gisett.label_transparent);
|
|
conf.set("Icons", "Label Visible", gisett.label_draw);
|
|
conf.set("Icons", "Gridspacing", gisett.gridspacing);
|
|
conf.set("Icons", "OneClickExec", gisett.one_click_exec);
|
|
conf.set("Icons", "AutoArrange", gisett.auto_arr);
|
|
|
|
unsigned int sz = icons.size();
|
|
const IconSettings* is = NULL;
|
|
|
|
for(unsigned int i = 0; i < sz; i++) {
|
|
is = icons[i]->get_settings();
|
|
conf.set(is->key_name.c_str(), "X", icons[i]->x());
|
|
conf.set(is->key_name.c_str(), "Y", icons[i]->y());
|
|
}
|
|
|
|
if(!conf.save(CONFIG_NAME))
|
|
EDEBUG(ESTRLOC ": Unable to save to %s\n", CONFIG_NAME);
|
|
}
|
|
|
|
void Desktop::add_icon(DesktopIcon* ic) {
|
|
EASSERT(ic != NULL);
|
|
|
|
icons.push_back(ic);
|
|
add(ic);
|
|
}
|
|
|
|
void Desktop::move_selection(int x, int y, bool apply) {
|
|
unsigned int sz = selectionbuff.size();
|
|
if(sz == 0)
|
|
return;
|
|
|
|
int prev_x, prev_y, tmp_x, tmp_y;
|
|
|
|
for(unsigned int i = 0; i < sz; i++) {
|
|
prev_x = selectionbuff[i]->drag_icon_x();
|
|
prev_y = selectionbuff[i]->drag_icon_y();
|
|
|
|
tmp_x = x - selection_x;
|
|
tmp_y = y - selection_y;
|
|
|
|
selectionbuff[i]->drag(prev_x+tmp_x, prev_y+tmp_y, apply);
|
|
|
|
// very slow if is not checked
|
|
if(apply == true)
|
|
selectionbuff[i]->redraw();
|
|
}
|
|
|
|
selection_x = x;
|
|
selection_y = y;
|
|
}
|
|
|
|
void Desktop::unfocus_all(void) {
|
|
unsigned int sz = icons.size();
|
|
|
|
for(unsigned int i = 0; i < sz; i++) {
|
|
if(icons[i]->is_focused()) {
|
|
icons[i]->do_unfocus();
|
|
icons[i]->redraw();
|
|
}
|
|
}
|
|
|
|
redraw();
|
|
}
|
|
|
|
bool Desktop::in_selection(const DesktopIcon* ic) {
|
|
EASSERT(ic != NULL);
|
|
|
|
unsigned int sz = selectionbuff.size();
|
|
for(unsigned int i = 0; i < sz; i++) {
|
|
if(ic == selectionbuff[i])
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Desktop::select(DesktopIcon* ic) {
|
|
EASSERT(ic != NULL);
|
|
|
|
if(in_selection(ic))
|
|
return;
|
|
|
|
selectionbuff.push_back(ic);
|
|
|
|
if(!ic->is_focused()) {
|
|
ic->do_focus();
|
|
ic->redraw();
|
|
}
|
|
|
|
redraw();
|
|
}
|
|
|
|
void Desktop::select_noredraw(DesktopIcon* ic) {
|
|
EASSERT(ic != NULL);
|
|
|
|
if(in_selection(ic))
|
|
return;
|
|
selectionbuff.push_back(ic);
|
|
}
|
|
|
|
void Desktop::select_only(DesktopIcon* ic) {
|
|
EASSERT(ic != NULL);
|
|
|
|
unfocus_all();
|
|
|
|
selectionbuff.clear();
|
|
selectionbuff.push_back(ic);
|
|
|
|
ic->do_focus();
|
|
ic->redraw();
|
|
|
|
redraw();
|
|
}
|
|
|
|
void Desktop::select_in_area(void) {
|
|
if(!selbox->show)
|
|
return;
|
|
|
|
int ax = selbox->x;
|
|
int ay = selbox->y;
|
|
int aw = selbox->w;
|
|
int ah = selbox->h;
|
|
|
|
if(aw < 0) {
|
|
ax += aw;
|
|
aw = -aw;
|
|
} else if(!aw)
|
|
aw = 1;
|
|
|
|
if(ah < 0) {
|
|
ay += ah;
|
|
ah = -ah;
|
|
} else if(!ah)
|
|
ah = 1;
|
|
|
|
/*
|
|
* XXX: This function can fail since icon coordinates are absolute (event_x_root)
|
|
* but selbox use relative (event_root). It will work as expected if desktop is at x=0 y=0.
|
|
* This should be checked further.
|
|
*/
|
|
unsigned int sz = icons.size();
|
|
DesktopIcon* ic = NULL;
|
|
|
|
for(unsigned int i = 0; i < sz; i++) {
|
|
ic = icons[i];
|
|
EASSERT(ic != NULL && "Impossible to happen");
|
|
|
|
if(intersects(ax, ay, ax+aw, ay+ah, ic->x(), ic->y(), ic->w()+ic->x(), ic->h()+ic->y())) {
|
|
if(!ic->is_focused()) {
|
|
ic->do_focus();
|
|
ic->redraw();
|
|
}
|
|
} else {
|
|
if(ic->is_focused()) {
|
|
ic->do_unfocus();
|
|
ic->redraw();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Tries to figure out icon below mouse (used for DND)
|
|
* If fails, return NULL
|
|
*/
|
|
DesktopIcon* Desktop::below_mouse(int px, int py) {
|
|
unsigned int sz = icons.size();
|
|
|
|
DesktopIcon* ic = NULL;
|
|
for(unsigned int i = 0; i < sz; i++) {
|
|
ic = icons[i];
|
|
EASSERT(ic != NULL && "Impossible to happen");
|
|
|
|
if(ic->x() < px && ic->y() < py && px < (ic->x() + ic->h()) && py < (ic->y() + ic->h()))
|
|
return ic;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// used to drop dnd context on desktop figuring out what it can be
|
|
void Desktop::drop_source(const char* src, int x, int y) {
|
|
if(!src)
|
|
return;
|
|
IconSettings is;
|
|
is.x = x;
|
|
is.y = y;
|
|
|
|
// absolute path we (for now) see as non-url
|
|
if(src[0] == '/')
|
|
is.cmd_is_url = false;
|
|
else
|
|
is.cmd_is_url = true;
|
|
|
|
is.name = "XXX";
|
|
is.cmd = "(none)";
|
|
is.type = ICON_NORMAL;
|
|
|
|
edelib::MimeType mt;
|
|
if(!mt.set(src)) {
|
|
EDEBUG("MimeType for %s failed, not dropping icon\n", src);
|
|
return;
|
|
}
|
|
|
|
is.icon = mt.icon_name();
|
|
|
|
EDEBUG("---------> %s\n", is.icon.c_str());
|
|
|
|
DesktopIcon* dic = new DesktopIcon(&gisett, &is);
|
|
add_icon(dic);
|
|
}
|
|
|
|
void Desktop::draw(void) {
|
|
#if 0
|
|
if(damage() & fltk::DAMAGE_ALL) {
|
|
fltk::Window::draw();
|
|
}
|
|
#endif
|
|
|
|
if(damage() & fltk::DAMAGE_ALL) {
|
|
clear_flag(fltk::HIGHLIGHT);
|
|
|
|
int nchild = children();
|
|
if(damage() & ~fltk::DAMAGE_CHILD) {
|
|
draw_box();
|
|
draw_label();
|
|
|
|
for(int i = 0; i < nchild; i++) {
|
|
fltk::Widget& ch = *child(i);
|
|
draw_child(ch);
|
|
draw_outside_label(ch);
|
|
}
|
|
} else {
|
|
for(int i = 0; i < nchild; i++) {
|
|
fltk::Widget& ch = *child(i);
|
|
if(ch.damage() & fltk::DAMAGE_CHILD_LABEL) {
|
|
draw_outside_label(ch);
|
|
ch.set_damage(ch.damage() & ~fltk::DAMAGE_CHILD_LABEL);
|
|
}
|
|
update_child(ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(damage() & (fltk::DAMAGE_ALL|EDAMAGE_OVERLAY)) {
|
|
clear_xoverlay();
|
|
|
|
if(selbox->show)
|
|
draw_xoverlay(selbox->x, selbox->y, selbox->w, selbox->h);
|
|
/*
|
|
* now scan all icons and see if they needs redraw, and if do
|
|
* just update their label since it is indicator of selection
|
|
*/
|
|
for(int i = 0; i < children(); i++) {
|
|
if(child(i)->damage() == fltk::DAMAGE_ALL) {
|
|
child(i)->set_damage(fltk::DAMAGE_CHILD_LABEL);
|
|
update_child(*child(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int Desktop::handle(int event) {
|
|
switch(event) {
|
|
case fltk::FOCUS:
|
|
case fltk::UNFOCUS:
|
|
return 1;
|
|
|
|
case fltk::PUSH: {
|
|
/*
|
|
* First check where we clicked. If we do it on desktop
|
|
* unfocus any possible focused childs, and handle
|
|
* specific clicks. Otherwise, do rest for childs.
|
|
*/
|
|
fltk::Widget* clicked = fltk::belowmouse();
|
|
EDEBUG(ESTRLOC ": %i\n", fltk::event_button());
|
|
|
|
if(clicked == this) {
|
|
unfocus_all();
|
|
EDEBUG(ESTRLOC ": DESKTOP CLICK !!!\n");
|
|
|
|
if(!selectionbuff.empty())
|
|
selectionbuff.clear();
|
|
|
|
if(fltk::event_button() == 3)
|
|
pmenu->popup();
|
|
|
|
// track position so moving can be deduced
|
|
if(fltk::event_button() == 1) {
|
|
selbox->x = fltk::event_x();
|
|
selbox->y = fltk::event_y();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// from here, all events are managed for icons
|
|
DesktopIcon* tmp_icon = (DesktopIcon*)clicked;
|
|
|
|
/*
|
|
* do no use assertion on this, since
|
|
* fltk::belowmouse() can miss our icon
|
|
*/
|
|
if(!tmp_icon)
|
|
return 1;
|
|
|
|
if(SELECTION_MULTI) {
|
|
fltk::event_is_click(0);
|
|
select(tmp_icon);
|
|
return 1;
|
|
} else if (SELECTION_SINGLE) {
|
|
|
|
if(!in_selection(tmp_icon))
|
|
select_only(tmp_icon);
|
|
|
|
} else if (fltk::event_button() == 3)
|
|
select_only(tmp_icon);
|
|
|
|
/*
|
|
* Let child handle the rest.
|
|
* Also prevent click on other mouse buttons during move.
|
|
*/
|
|
if(!moving)
|
|
tmp_icon->handle(fltk::PUSH);
|
|
|
|
selection_x = fltk::event_x_root();
|
|
selection_y = fltk::event_y_root();
|
|
EDEBUG(ESTRLOC ": fltk::PUSH from desktop\n");
|
|
|
|
return 1;
|
|
}
|
|
|
|
case fltk::DRAG:
|
|
moving = true;
|
|
|
|
if(!selectionbuff.empty()) {
|
|
EDEBUG(ESTRLOC ": DRAG icon from desktop\n");
|
|
move_selection(fltk::event_x_root(), fltk::event_y_root(), false);
|
|
} else {
|
|
EDEBUG(ESTRLOC ": DRAG from desktop\n");
|
|
|
|
/*
|
|
* Moving is started with pushed button.
|
|
* From this point selection box is created and is rolled until release
|
|
*/
|
|
if(selbox->x != 0 || selbox->y != 0) {
|
|
selbox->w = fltk::event_x() - selbox->x;
|
|
selbox->h = fltk::event_y() - selbox->y;
|
|
|
|
selbox->show = true;
|
|
|
|
// see if there some icons inside selection area
|
|
select_in_area();
|
|
|
|
// redraw selection box
|
|
redraw(EDAMAGE_OVERLAY);
|
|
}
|
|
}
|
|
return 1;
|
|
|
|
case fltk::RELEASE:
|
|
EDEBUG(ESTRLOC ": RELEASE from desktop\n");
|
|
EDEBUG(ESTRLOC ": clicks: %i\n", fltk::event_is_click());
|
|
|
|
if(selbox->show) {
|
|
selbox->x = selbox->y = selbox->w = selbox->h = 0;
|
|
selbox->show = false;
|
|
redraw(EDAMAGE_OVERLAY);
|
|
/*
|
|
* Now pickup those who are in is_focused() state.
|
|
* Here is not used select() since it will fill selectionbuff with
|
|
* redrawing whole window each time. This is not what we want.
|
|
*
|
|
* Possible flickers due overlay will be later removed when is
|
|
* called move_selection(), which will in turn redraw icons again
|
|
* after position them.
|
|
*/
|
|
if(!selectionbuff.empty())
|
|
selectionbuff.clear();
|
|
|
|
for(unsigned int i = 0; i < icons.size(); i++) {
|
|
if(icons[i]->is_focused())
|
|
select_noredraw(icons[i]);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if(!selectionbuff.empty() && moving)
|
|
move_selection(fltk::event_x_root(), fltk::event_y_root(), true);
|
|
|
|
/*
|
|
* Do not send fltk::RELEASE during move
|
|
*
|
|
* TODO: should be alowed fltk::RELEASE to multiple icons? (aka. run
|
|
* command for all selected icons ?
|
|
*/
|
|
if(selectionbuff.size() == 1 && !moving)
|
|
selectionbuff[0]->handle(fltk::RELEASE);
|
|
|
|
moving = false;
|
|
return 1;
|
|
|
|
case fltk::DND_ENTER:
|
|
case fltk::DND_DRAG:
|
|
case fltk::DND_LEAVE:
|
|
return 1;
|
|
|
|
case fltk::DND_RELEASE: {
|
|
// fltk::belowmouse() can't be used within DND context :)
|
|
DesktopIcon* di = below_mouse(fltk::event_x_root(), fltk::event_y_root());
|
|
if(di) {
|
|
di->handle(event);
|
|
} else {
|
|
EDEBUG("DND on DESKTOP\n");
|
|
}
|
|
return 1;
|
|
}
|
|
case fltk::PASTE: {
|
|
DesktopIcon* di = below_mouse(fltk::event_x_root(), fltk::event_y_root());
|
|
if(di) {
|
|
di->handle(event);
|
|
} else {
|
|
EDEBUG("PASTE on desktop with %s\n", fltk::event_text());
|
|
drop_source(fltk::event_text(), fltk::event_x_root(), fltk::event_y_root());
|
|
}
|
|
}
|
|
return 1;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return fltk::Window::handle(event);
|
|
}
|
|
|
|
/*
|
|
* This function is executed before desktop is actually showed
|
|
* but after is internally created so net_make_me_desktop() specific code can
|
|
* be executed correctly.
|
|
*
|
|
* Calling net_make_me_desktop() after show() will confuse many wm's which will
|
|
* in turn partialy register us as desktop type.
|
|
*/
|
|
void Desktop::create(void) {
|
|
fltk::Window::create();
|
|
net_make_me_desktop(this);
|
|
}
|
|
|
|
int main() {
|
|
signal(SIGTERM, exit_signal);
|
|
signal(SIGKILL, exit_signal);
|
|
signal(SIGINT, exit_signal);
|
|
|
|
srand(time(NULL));
|
|
|
|
// TODO: init_locale_support()
|
|
|
|
//edelib::IconTheme::init("crystalsvg");
|
|
edelib::IconTheme::init("edeneu");
|
|
|
|
fltk::register_images();
|
|
|
|
Desktop::init();
|
|
Desktop::instance()->show();
|
|
|
|
/*
|
|
* XSelectInput will redirect PropertyNotify messages, which
|
|
* we are listen for
|
|
*/
|
|
XSelectInput(fltk::xdisplay, RootWindow(fltk::xdisplay, fltk::xscreen), PropertyChangeMask | StructureNotifyMask );
|
|
fltk::add_event_handler(desktop_xmessage_handler);
|
|
|
|
while(running)
|
|
fltk::wait();
|
|
|
|
Desktop::shutdown();
|
|
edelib::IconTheme::shutdown();
|
|
|
|
return 0;
|
|
}
|