mc/gnome/gdesktop.c
Owen Taylor bb4317f961 1998-12-11 Owen Taylor <otaylor@redhat.com>
* gscreen.c gdesktop.c gdnd.h: enable dragging locale files
	into Netscape.
1998-12-11 18:12:21 +00:00

1942 lines
47 KiB
C

/* Desktop management for the Midnight Commander
*
* Copyright (C) 1998 The Free Software Foundation
*
* Authors: Federico Mena <federico@nuclecu.unam.mx>
* Miguel de Icaza <miguel@nuclecu.unam.mx>
*/
/*
* TO-DO list for the desktop;
*
* - Put an InputOnly window over icons to be able to select them even if the user clicks on
* the transparent area.
*
* - DnD from file windows to icons.
*
* - DnD from icons to desktop (move icon).
*
* - DnD from icons to windows.
*
* - DnD from icons to icons (file->directory or file->executable).
*
* - Popup menus for icons.
*
* - Select icons with rubberband on the root window.
*
*/
#include <config.h>
#include "fs.h"
#include <gdk/gdkx.h>
#include <gtk/gtkinvisible.h>
#include <gnome.h>
#include "dialog.h"
#define DIR_H_INCLUDE_HANDLE_DIRENT /* bleah */
#include "dir.h"
#include "gdesktop.h"
#include "gdesktop-icon.h"
#include "gicon.h"
#include "gmain.h"
#include "gmetadata.h"
#include "gdnd.h"
#include "gpopup.h"
#include "../vfs/vfs.h"
/* Name of the user's desktop directory (i.e. ~/desktop) */
#define DESKTOP_DIR_NAME "desktop"
/* This structure defines the information carried by a desktop icon */
struct desktop_icon_info {
GtkWidget *dicon; /* The desktop icon widget */
int x, y; /* Position in the desktop */
int slot; /* Index of the slot the icon is in, or -1 for none */
char *filename; /* The file this icon refers to (relative to the desktop_directory) */
int selected : 1; /* Is the icon selected? */
int tmp_selected : 1; /* Temp storage for original selection while rubberbanding */
int finishing_selection : 1; /* Flag set while we are releasing
* button after selecting in the text
*/
};
struct layout_slot {
int num_icons; /* Number of icons in this slot */
GList *icons; /* The list of icons in this slot */
};
/* Configuration options for the desktop */
int desktop_use_shaped_icons = TRUE;
int desktop_auto_placement = FALSE;
int desktop_snap_icons = FALSE;
/* The computed name of the user's desktop directory */
static char *desktop_directory;
/* Layout information: number of rows/columns for the layout slots, and the array of slots. Each
* slot is an integer that specifies the number of icons that belong to that slot.
*/
static int layout_screen_width;
static int layout_screen_height;
static int layout_cols;
static int layout_rows;
static struct layout_slot *layout_slots;
#define l_slots(u, v) (layout_slots[(u) * layout_rows + (v)])
/* The last icon to be selected */
static struct desktop_icon_info *last_selected_icon;
/* Drag and drop sources and targets */
static GtkTargetEntry dnd_icon_sources[] = {
{ "application/x-mc-desktop-icon", 0, TARGET_MC_DESKTOP_ICON },
{ "text/uri-list", 0, TARGET_URI_LIST },
{ "text/plain", 0, TARGET_TEXT_PLAIN },
{ "_NETSCAPE_URL", 0, TARGET_URL }
};
static GtkTargetEntry dnd_icon_targets[] = {
{ "text/uri-list", 0, TARGET_URI_LIST }
};
static GtkTargetEntry dnd_desktop_targets[] = {
{ "application/x-mc-desktop-icon", 0, TARGET_MC_DESKTOP_ICON },
{ "text/uri-list", 0, TARGET_URI_LIST }
};
static int dnd_icon_nsources = sizeof (dnd_icon_sources) / sizeof (dnd_icon_sources[0]);
static int dnd_icon_ntargets = sizeof (dnd_icon_targets) / sizeof (dnd_icon_targets[0]);
static int dnd_desktop_ntargets = sizeof (dnd_desktop_targets) / sizeof (dnd_desktop_targets[0]);
/* Proxy window for DnD on the root window */
static GtkWidget *dnd_proxy_window;
/* Offsets for the DnD cursor hotspot */
static int dnd_press_x, dnd_press_y;
/* Whether a call to select_icon() is pending because the initial click on an
* icon had the GDK_CONTROL_MASK in it. */
static int dnd_select_icon_pending;
/* Proxy window for clicks on the root window */
static GdkWindow *click_proxy_gdk_window;
static GtkWidget *click_proxy_invisible;
/* GC for drawing the rubberband rectangle */
static GdkGC *click_gc;
/* Starting click position and event state for rubberbanding on the desktop */
static int click_start_x;
static int click_start_y;
static int click_start_state;
/* Current mouse position for rubberbanding on the desktop */
static int click_current_x;
static int click_current_y;
static int click_dragging;
static struct desktop_icon_info *desktop_icon_info_new (char *filename, int auto_pos, int xpos, int ypos);
static void desktop_icon_info_free (struct desktop_icon_info *dii);
/* Looks for a free slot in the layout_slots array and returns the coordinates that coorespond to
* it. "Free" means it either has zero icons in it, or it has the minimum number of icons of all
* the slots.
*/
static void
get_icon_auto_pos (int *x, int *y)
{
int min, min_x, min_y;
int u, v;
int val;
min = l_slots (0, 0).num_icons;
min_x = min_y = 0;
for (u = 0; u < layout_cols; u++)
for (v = 0; v < layout_rows; v++) {
val = l_slots (u, v).num_icons;
if (val == 0) {
/* Optimization: if it is zero, return immediately */
*x = u * DESKTOP_SNAP_X;
*y = v * DESKTOP_SNAP_Y;
return;
} else if (val < min) {
min = val;
min_x = u;
min_y = v;
}
}
*x = min_x * DESKTOP_SNAP_X;
*y = min_y * DESKTOP_SNAP_Y;
}
/* Snaps the specified position to the icon grid. It looks for the closest free spot on the grid,
* or the closest one that has the least number of icons in it.
*/
static void
get_icon_snap_pos (int *x, int *y)
{
int min, min_x, min_y;
int min_dist;
int u, v;
int val, dist;
int dx, dy;
min = l_slots (0, 0).num_icons;
min_x = min_y = 0;
min_dist = INT_MAX;
for (u = 0; u < layout_cols; u++)
for (v = 0; v < layout_rows; v++) {
val = l_slots (u, v).num_icons;
dx = *x - u * DESKTOP_SNAP_X;
dy = *y - v * DESKTOP_SNAP_Y;
dist = dx * dx + dy * dy;
if ((val == min && dist < min_dist) || (val < min)) {
min = val;
min_dist = dist;
min_x = u;
min_y = v;
}
}
*x = min_x * DESKTOP_SNAP_X;
*y = min_y * DESKTOP_SNAP_Y;
}
/* Removes an icon from the slot it is in, if any */
static void
remove_from_slot (struct desktop_icon_info *dii)
{
if (dii->slot == -1)
return;
g_assert (layout_slots[dii->slot].num_icons >= 1);
g_assert (layout_slots[dii->slot].icons != NULL);
layout_slots[dii->slot].num_icons--;
layout_slots[dii->slot].icons = g_list_remove (layout_slots[dii->slot].icons, dii);
}
/* Places a desktop icon. If auto_pos is true, then the function will look for a place to position
* the icon automatically, else it will use the specified coordinates, snapped to the grid if the
* global desktop_snap_icons flag is set.
*/
static void
desktop_icon_info_place (struct desktop_icon_info *dii, int auto_pos, int xpos, int ypos)
{
int u, v;
char *filename;
if (auto_pos) {
if (desktop_auto_placement)
get_icon_auto_pos (&xpos, &ypos);
else if (desktop_snap_icons)
get_icon_snap_pos (&xpos, &ypos);
}
if (xpos < 0)
xpos = 0;
else if (xpos > layout_screen_width)
xpos = layout_screen_width - DESKTOP_SNAP_X;
if (ypos < 0)
ypos = 0;
else if (ypos > layout_screen_height)
ypos = layout_screen_height - DESKTOP_SNAP_Y;
/* Increase the number of icons in the corresponding slot */
remove_from_slot (dii);
u = xpos / DESKTOP_SNAP_X;
v = ypos / DESKTOP_SNAP_Y;
dii->slot = u * layout_rows + v;
layout_slots[dii->slot].num_icons++;
layout_slots[dii->slot].icons = g_list_append (layout_slots[dii->slot].icons, dii);
/* Move the icon */
dii->x = xpos;
dii->y = ypos;
gtk_widget_set_uposition (dii->dicon, xpos, ypos);
/* Save the information */
filename = g_concat_dir_and_file (desktop_directory, dii->filename);
gmeta_set_icon_pos (filename, dii->x, dii->y);
g_free (filename);
}
/* Returns TRUE if there is already an icon in the desktop for the specified filename, FALSE otherwise. */
static int
icon_exists (char *filename)
{
int i;
GList *l;
struct desktop_icon_info *dii;
for (i = 0; i < (layout_cols * layout_rows); i++)
for (l = layout_slots[i].icons; l; l = l->next) {
dii = l->data;
if (strcmp (filename, dii->filename) == 0)
return TRUE;
}
return FALSE;
}
/* Reads the ~/Desktop directory and creates the desktop icons. If incremental is TRUE, then an
* icon will not be created for a file if there is already an icon for it, and icons will be created
* starting at the specified position.
*/
static void
load_desktop_icons (int incremental, int xpos, int ypos)
{
struct dirent *dirent;
DIR *dir;
char *full_name;
int have_pos, x, y;
struct desktop_icon_info *dii;
GSList *need_position_list, *l;
dir = mc_opendir (desktop_directory);
if (!dir) {
message (FALSE,
_("Warning"),
_("Could not open %s; will not have initial desktop icons"),
desktop_directory);
return;
}
/* First create the icons for all the files that do have their icon position set. Build a
* list of the icons that do not have their position set.
*/
need_position_list = NULL;
while ((dirent = mc_readdir (dir)) != NULL) {
if (((dirent->d_name[0] == '.') && (dirent->d_name[1] == 0))
|| ((dirent->d_name[0] == '.') && (dirent->d_name[1] == '.') && (dirent->d_name[2] == 0)))
continue;
if (incremental && icon_exists (dirent->d_name))
continue;
full_name = g_concat_dir_and_file (desktop_directory, dirent->d_name);
have_pos = gmeta_get_icon_pos (full_name, &x, &y);
if (have_pos) {
dii = desktop_icon_info_new (dirent->d_name, FALSE, x, y);
gtk_widget_show (dii->dicon);
g_free (full_name);
} else
need_position_list = g_slist_prepend (need_position_list, g_strdup (dirent->d_name));
}
mc_closedir (dir);
/* Now create the icons for all the files that did not have their position set. This makes
* auto-placement work correctly without overlapping icons.
*/
need_position_list = g_slist_reverse (need_position_list);
for (l = need_position_list; l; l = l->next) {
dii = desktop_icon_info_new (l->data, TRUE, xpos, ypos);
gtk_widget_show (dii->dicon);
g_free (l->data);
}
g_slist_free (need_position_list);
}
/* Destroys all the current desktop icons */
static void
destroy_desktop_icons (void)
{
int i;
GList *l;
struct desktop_icon_info *dii;
for (i = 0; i < (layout_cols * layout_rows); i++) {
l = layout_slots[i].icons;
while (l) {
dii = l->data;
l = l->next;
desktop_icon_info_free (dii);
}
}
}
/* Reloads the desktop icons. If incremental is TRUE, then the existing icons will not be destroyed
* first, and the new icons will be put at the specified position.
*/
static void
reload_desktop_icons (int incremental, int x, int y)
{
if (!incremental)
destroy_desktop_icons ();
load_desktop_icons (incremental, x, y);
}
/* Unselects all the desktop icons except the one in exclude */
static void
unselect_all (struct desktop_icon_info *exclude)
{
int i;
GList *l;
struct desktop_icon_info *dii;
for (i = 0; i < (layout_cols * layout_rows); i++)
for (l = layout_slots[i].icons; l; l = l->next) {
dii = l->data;
if (dii->selected && dii != exclude) {
desktop_icon_select (DESKTOP_ICON (dii->dicon), FALSE);
dii->selected = FALSE;
}
}
}
/* Sets the selection state of a range to the specified value. The range starts at the
* last_selected_icon and ends at the specified icon.
*/
static void
select_range (struct desktop_icon_info *dii, int sel)
{
int du, dv, lu, lv;
int min_u, min_v;
int max_u, max_v;
int u, v;
GList *l;
struct desktop_icon_info *ldii;
struct desktop_icon_info *min_udii, *min_vdii;
struct desktop_icon_info *max_udii, *max_vdii;
/* Find out the selection range */
if (!last_selected_icon)
last_selected_icon = dii;
du = dii->slot / layout_rows;
dv = dii->slot % layout_rows;
lu = last_selected_icon->slot / layout_rows;
lv = last_selected_icon->slot % layout_rows;
if (du < lu) {
min_u = du;
max_u = lu;
min_udii = dii;
max_udii = last_selected_icon;
} else {
min_u = lu;
max_u = du;
min_udii = last_selected_icon;
max_udii = dii;
}
if (dv < lv) {
min_v = dv;
max_v = lv;
min_vdii = dii;
max_vdii = last_selected_icon;
} else {
min_v = lv;
max_v = dv;
min_vdii = last_selected_icon;
max_vdii = dii;
}
/* Select all the icons in the rectangle */
for (u = min_u; u <= max_u; u++)
for (v = min_v; v <= max_v; v++)
for (l = l_slots (u, v).icons; l; l = l->next) {
ldii = l->data;
if ((u == min_u && ldii->x < min_udii->x)
|| (v == min_v && ldii->y < min_vdii->y)
|| (u == max_u && ldii->x > max_udii->x)
|| (v == max_v && ldii->y > max_vdii->y))
continue;
desktop_icon_select (DESKTOP_ICON (ldii->dicon), sel);
ldii->selected = sel;
}
}
/* Handles icon selection and unselection due to button presses. The
* event_state is the state field of the event.
*/
static void
select_icon (struct desktop_icon_info *dii, int event_state)
{
int range;
int additive;
range = ((event_state & GDK_SHIFT_MASK) != 0);
additive = ((event_state & GDK_CONTROL_MASK) != 0);
if (!additive)
unselect_all (NULL);
if (!range) {
if (additive) {
desktop_icon_select (DESKTOP_ICON (dii->dicon), !dii->selected);
dii->selected = !dii->selected;
} else if (!dii->selected) {
desktop_icon_select (DESKTOP_ICON (dii->dicon), TRUE);
dii->selected = TRUE;
}
last_selected_icon = dii;
if (dii->selected)
gdk_window_raise (dii->dicon->window);
} else
select_range (dii, TRUE);
}
/* Creates a file entry structure and fills it with information appropriate to the specified file. */
static file_entry *
file_entry_from_file (char *filename)
{
file_entry *fe;
struct stat s;
if (mc_lstat (filename, &s) == -1) {
g_warning ("Could not stat %s, bad things will happen", filename);
return NULL;
}
fe = g_new (file_entry, 1);
fe->fname = g_strdup (x_basename (filename));
fe->fnamelen = strlen (fe->fname);
fe->buf = s;
fe->f.marked = FALSE;
fe->f.link_to_dir = FALSE;
fe->f.stalled_link = FALSE;
if (S_ISLNK (s.st_mode)) {
struct stat s2;
if (mc_stat (filename, &s2) == 0)
fe->f.link_to_dir = S_ISDIR (s2.st_mode) != 0;
else
fe->f.stalled_link = TRUE;
}
return fe;
}
/* Frees a file entry structure */
static void
file_entry_free (file_entry *fe)
{
if (fe->fname)
g_free (fe->fname);
g_free (fe);
}
/* Callback used when an icon's text changes. We must validate the rename and return the
* appropriate value. The desktop icon info structure is passed in the user data.
*/
static int
text_changed (GnomeIconTextItem *iti, gpointer data)
{
struct desktop_icon_info *dii;
char *new_name;
char *source;
char *dest;
int retval;
dii = data;
source = g_concat_dir_and_file (desktop_directory, dii->filename);
new_name = gnome_icon_text_item_get_text (iti);
dest = g_concat_dir_and_file (desktop_directory, new_name);
if (mc_rename (source, dest) == 0) {
g_free (dii->filename);
dii->filename = g_strdup (new_name);
retval = TRUE;
} else
retval = FALSE; /* FIXME: maybe pop up a warning/query dialog? */
g_free (source);
g_free (dest);
return retval;
}
/* Callback used when the user begins editing the icon text item in a desktop icon. It installs the
* mouse and keyboard grabs that are required while an icon is being edited.
*/
static void
editing_started (GnomeIconTextItem *iti, gpointer data)
{
struct desktop_icon_info *dii;
GdkCursor *ibeam;
dii = data;
/* Disable drags from this icon until editing is finished */
gtk_drag_source_unset (DESKTOP_ICON (dii->dicon)->canvas);
/* Unselect all icons but this one */
unselect_all (dii);
ibeam = gdk_cursor_new (GDK_XTERM);
gdk_pointer_grab (dii->dicon->window,
TRUE,
(GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
| GDK_ENTER_NOTIFY_MASK
| GDK_LEAVE_NOTIFY_MASK),
NULL,
ibeam,
GDK_CURRENT_TIME);
gtk_grab_add (dii->dicon);
gdk_cursor_destroy (ibeam);
gdk_keyboard_grab (GTK_LAYOUT (DESKTOP_ICON (dii->dicon)->canvas)->bin_window, FALSE, GDK_CURRENT_TIME);
}
/* Sets up the specified icon as a drag source, but does not connect the signals */
static void
setup_icon_dnd_actions (struct desktop_icon_info *dii)
{
gtk_drag_source_set (DESKTOP_ICON (dii->dicon)->canvas,
GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
dnd_icon_sources,
dnd_icon_nsources,
GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
}
/* Callback used when the user finishes editing the icon text item in a desktop icon. It removes
* the mouse and keyboard grabs.
*/
static void
editing_stopped (GnomeIconTextItem *iti, gpointer data)
{
struct desktop_icon_info *dii;
dii = data;
gtk_grab_remove (dii->dicon);
gdk_pointer_ungrab (GDK_CURRENT_TIME);
gdk_keyboard_ungrab (GDK_CURRENT_TIME);
/* Re-enable drags from this icon */
setup_icon_dnd_actions (dii);
}
/* Used to open a desktop icon when the user double-clicks on it */
static void
open_desktop_icon (struct desktop_icon_info *dii)
{
char *filename;
file_entry *fe;
filename = g_concat_dir_and_file (desktop_directory, dii->filename);
fe = file_entry_from_file (filename);
if (S_ISDIR (fe->buf.st_mode) || link_isdir (fe))
new_panel_at (filename);
else
do_enter_on_file_entry (fe);
file_entry_free (fe);
}
/* Used to execute the popup menu for desktop icons */
static void
do_popup_menu (struct desktop_icon_info *dii, GdkEventButton *event)
{
char *filename;
filename = g_concat_dir_and_file (desktop_directory, dii->filename);
if (gpopup_do_popup (event, NULL, 0, filename) != -1)
reload_desktop_icons (FALSE, 0, 0); /* bleah */
g_free (filename);
}
/* Callback activated when a button is redirected from the desktop to
* the icon during a grab.
*/
static gint
window_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
struct desktop_icon_info *dii;
GtkWidget *parent;
dii = data;
/* We should only get this while editing. But check anyways -
* we ignore events in a child of our a event widget
*/
parent = gtk_get_event_widget ((GdkEvent *)event);
if (parent)
parent = parent->parent;
while (parent) {
if (widget == parent)
return FALSE;
parent = parent->parent;
}
if (GNOME_ICON_TEXT_ITEM (DESKTOP_ICON (dii->dicon)->text)->editing) {
gnome_icon_text_item_stop_editing (
GNOME_ICON_TEXT_ITEM (
DESKTOP_ICON (dii->dicon)->text), TRUE);
return TRUE;
}
return FALSE;
}
/* Callback used when a button is pressed on a desktop icon */
static gint
icon_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
struct desktop_icon_info *dii;
int retval;
dii = data;
/* If the text is being edited, do not handle clicks by ourselves */
if (GNOME_ICON_TEXT_ITEM (DESKTOP_ICON (dii->dicon)->text)->editing)
return FALSE;
/* Save the mouse position for DnD */
dnd_press_x = event->x;
dnd_press_y = event->y;
/* Process the event */
retval = FALSE;
switch (event->type) {
case GDK_BUTTON_PRESS:
if (event->button == 1) {
/* If (only) the Control key is down, then we have to delay the icon selection */
dnd_select_icon_pending = ((event->state & GDK_CONTROL_MASK)
&& !((event->state & GDK_CONTROL_MASK)
&& (event->state & GDK_SHIFT_MASK)));
if (!dnd_select_icon_pending) {
select_icon (dii, event->state);
retval = TRUE;
}
} else if (event->button == 3) {
do_popup_menu (dii, event);
retval = TRUE;
}
break;
case GDK_2BUTTON_PRESS:
if (event->button != 1)
break;
open_desktop_icon (dii);
retval = TRUE;
break;
default:
break;
}
/* Keep the canvas items from getting the signal */
#if 0
if (retval)
gtk_signal_emit_stop_by_name (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "button_press_event");
#endif
return retval;
}
/* Handler for button releases on desktop icons. If there was a pending
* selection on the icon, then the function performs the selection.
*/
static gint
icon_button_release (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
struct desktop_icon_info *dii;
dii = data;
if (dnd_select_icon_pending) {
select_icon (dii, GDK_CONTROL_MASK);
dnd_select_icon_pending = FALSE;
return TRUE;
} else if (GNOME_ICON_TEXT_ITEM (DESKTOP_ICON (dii->dicon)->text)->selecting) {
dii->finishing_selection = TRUE;
}
return FALSE;
}
/* Handler for button releases on desktop icons. If there was a pending
* selection on the icon, then the function performs the selection.
*/
static gint
icon_button_release_after (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
struct desktop_icon_info *dii;
dii = data;
if (dii->finishing_selection) {
/* Restore the pointer grab here because the icon item just
* called gdk_pointer_ungrab()
*/
GdkCursor *ibeam = gdk_cursor_new (GDK_XTERM);
gdk_pointer_grab (dii->dicon->window,
TRUE,
(GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
| GDK_ENTER_NOTIFY_MASK
| GDK_LEAVE_NOTIFY_MASK),
NULL,
ibeam,
GDK_CURRENT_TIME);
gdk_cursor_destroy (ibeam);
dii->finishing_selection = FALSE;
}
return FALSE;
}
/* Callback used when a drag from the desktop icons is started. We set the drag icon to the proper
* pixmap.
*/
static void
drag_begin (GtkWidget *widget, GdkDragContext *context, gpointer data)
{
struct desktop_icon_info *dii;
DesktopIcon *dicon;
GtkArg args[3];
GdkImlibImage *im;
GdkPixmap *pixmap;
GdkBitmap *mask;
int x, y;
dii = data;
dicon = DESKTOP_ICON (dii->dicon);
/* See if the icon was pending to be selected */
if (dnd_select_icon_pending) {
if (!dii->selected)
select_icon (dii, GDK_CONTROL_MASK);
dnd_select_icon_pending = FALSE;
}
/* FIXME: see if it is more than one icon and if so, use a multiple-files icon. */
args[0].name = "image";
args[1].name = "x";
args[2].name = "y";
gtk_object_getv (GTK_OBJECT (dicon->icon), 3, args);
im = GTK_VALUE_BOXED (args[0]);
x = GTK_VALUE_DOUBLE (args[1]);
y = GTK_VALUE_DOUBLE (args[2]);
gdk_imlib_render (im, im->rgb_width, im->rgb_height);
pixmap = gdk_imlib_copy_image (im);
mask = gdk_imlib_copy_mask (im);
gtk_drag_set_icon_pixmap (context,
gtk_widget_get_colormap (dicon->canvas),
pixmap,
mask,
dnd_press_x - x,
dnd_press_y - y);
gdk_pixmap_unref (pixmap);
gdk_bitmap_unref (mask);
}
/* Builds a string with the URI-list of the selected desktop icons */
static char *
build_selected_icons_uri_list (int *len)
{
int i;
GList *l;
struct desktop_icon_info *dii;
char *filelist, *p;
int desktop_dir_len;
/* First, count the number of selected icons and add up their filename lengths */
*len = 0;
desktop_dir_len = strlen (desktop_directory);
for (i = 0; i < (layout_cols * layout_rows); i++)
for (l = layout_slots[i].icons; l; l = l->next) {
dii = l->data;
/* "file:" + desktop_directory + "/" + dii->filename + "\r\n" */
if (dii->selected)
*len += 5 + desktop_dir_len + 1 + strlen (dii->filename) + 2;
}
/* Second, create the file list string */
filelist = g_new (char, *len + 1); /* plus 1 for null terminator */
p = filelist;
for (i = 0; i < (layout_cols * layout_rows); i++)
for (l = layout_slots[i].icons; l; l = l->next) {
dii = l->data;
if (dii->selected) {
strcpy (p, "file:");
p += 5;
strcpy (p, desktop_directory);
p += desktop_dir_len;
*p++ = '/';
strcpy (p, dii->filename);
p += strlen (dii->filename);
strcpy (p, "\r\n");
p += 2;
}
}
*p = 0;
return filelist;
}
/* Callback used to get the drag data from the desktop icons */
static void
drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
guint info, guint32 time, gpointer data)
{
struct desktop_icon_info *dii;
char *filelist;
int len;
GList *files;
dii = data;
filelist = build_selected_icons_uri_list (&len);
switch (info) {
case TARGET_MC_DESKTOP_ICON:
case TARGET_URI_LIST:
case TARGET_TEXT_PLAIN:
gtk_selection_data_set (selection_data,
selection_data->target,
8,
filelist,
len);
break;
case TARGET_URL:
files = gnome_uri_list_extract_uris (filelist);
if (files) {
gtk_selection_data_set (selection_data,
selection_data->target,
8,
files->data,
strlen (files->data));
}
gnome_uri_list_free_strings (files);
break;
default:
g_assert_not_reached ();
}
g_free (filelist);
}
/* Set up a desktop icon as a DnD source */
static void
setup_icon_dnd_source (struct desktop_icon_info *dii)
{
setup_icon_dnd_actions (dii);
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_begin",
(GtkSignalFunc) drag_begin,
dii);
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_data_get",
GTK_SIGNAL_FUNC (drag_data_get),
dii);
}
static void
icon_drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
GtkSelectionData *data, guint info, guint time, gpointer user_data)
{
struct desktop_icon_info *dii;
char *filename;
file_entry *fe;
dii = user_data;
filename = g_concat_dir_and_file (desktop_directory, dii->filename);
fe = file_entry_from_file (filename);
if (!fe)
return; /* eek */
switch (info) {
case TARGET_URI_LIST:
if (fe->f.link_to_dir)
gdnd_drop_on_directory (context, data, filename);
else if (is_exe (fe->buf.st_mode) && if_link_is_exe (fe))
printf ("Implement execution of desktop icons!\n"); /* FIXME: launch with the dropped files */
else
g_assert_not_reached ();
break;
default:
break;
}
}
/* Set up a desktop icon as a DnD destination */
static void
setup_icon_dnd_dest (struct desktop_icon_info *dii)
{
char *filename;
file_entry *fe;
int actions;
filename = g_concat_dir_and_file (desktop_directory, dii->filename);
fe = file_entry_from_file (filename);
g_free (filename);
if (!fe)
return; /* eek */
/* See what actions are appropriate for this icon */
if (fe->f.link_to_dir)
actions = GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK;
else if (is_exe (fe->buf.st_mode) && if_link_is_exe (fe))
actions = GDK_ACTION_COPY;
else
actions = 0;
file_entry_free (fe);
if (!actions)
return;
/* Connect the drop signals */
gtk_drag_dest_set (DESKTOP_ICON (dii->dicon)->canvas,
GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
dnd_icon_targets,
dnd_icon_ntargets,
actions);
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_data_received",
GTK_SIGNAL_FUNC (icon_drag_data_received),
dii);
}
/* Creates a new desktop icon. The filename is the pruned filename inside the desktop directory.
* If auto_pos is false, it will use the specified coordinates for the icon. Else, it will use
* auto- positioning trying to start at the specified coordinates. It does not show the icon.
*/
static struct desktop_icon_info *
desktop_icon_info_new (char *filename, int auto_pos, int xpos, int ypos)
{
struct desktop_icon_info *dii;
file_entry *fe;
char *full_name;
GdkImlibImage *icon_im;
/* Create the icon structure */
full_name = g_concat_dir_and_file (desktop_directory, filename);
fe = file_entry_from_file (full_name);
icon_im = gicon_get_icon_for_file (fe);
dii = g_new (struct desktop_icon_info, 1);
dii->dicon = desktop_icon_new (icon_im, filename);
dii->x = 0;
dii->y = 0;
dii->slot = -1;
dii->filename = g_strdup (filename);
dii->selected = FALSE;
file_entry_free (fe);
g_free (full_name);
/* Connect to the icon's signals */
gtk_signal_connect_after (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "button_press_event",
(GtkSignalFunc) icon_button_press,
dii);
gtk_signal_connect (GTK_OBJECT (dii->dicon), "button_press_event",
(GtkSignalFunc) window_button_press,
dii);
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "button_release_event",
(GtkSignalFunc) icon_button_release,
dii);
gtk_signal_connect_after (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "button_release_event",
(GtkSignalFunc) icon_button_release_after,
dii);
/* Connect to the text item's signals */
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "text_changed",
(GtkSignalFunc) text_changed,
dii);
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "editing_started",
(GtkSignalFunc) editing_started,
dii);
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "editing_stopped",
(GtkSignalFunc) editing_stopped,
dii);
/* Prepare the DnD functionality for this icon */
setup_icon_dnd_source (dii);
setup_icon_dnd_dest (dii);
/* Place the icon and append it to the list */
desktop_icon_info_place (dii, auto_pos, xpos, ypos);
return dii;
}
/* Frees a desktop icon information structure, and destroy the icon widget. Does not remove the
* structure from the desktop_icons list!
*/
static void
desktop_icon_info_free (struct desktop_icon_info *dii)
{
gtk_widget_destroy (dii->dicon);
remove_from_slot (dii);
g_free (dii->filename);
g_free (dii);
}
/* Creates the layout information array */
static void
create_layout_info (void)
{
layout_screen_width = gdk_screen_width ();
layout_screen_height = gdk_screen_height ();
layout_cols = (layout_screen_width + DESKTOP_SNAP_X - 1) / DESKTOP_SNAP_X;
layout_rows = (layout_screen_height + DESKTOP_SNAP_Y - 1) / DESKTOP_SNAP_Y;
layout_slots = g_new0 (struct layout_slot, layout_cols * layout_rows);
}
/* Check that the user's desktop directory exists, and if not, create it with a symlink to the
* user's home directory so that an icon will be displayed.
*/
static void
create_desktop_dir (void)
{
char *home_link_name;
desktop_directory = g_concat_dir_and_file (gnome_user_home_dir, DESKTOP_DIR_NAME);
if (!g_file_exists (desktop_directory)) {
/* Create the directory */
mkdir (desktop_directory, 0777);
/* Create the link to the user's home directory so that he will have an icon */
home_link_name = g_concat_dir_and_file (desktop_directory, _("Home directory"));
if (mc_symlink (gnome_user_home_dir, home_link_name) != 0) {
message (FALSE,
_("Warning"),
_("Could not symlink %s to %s; will not have initial desktop icons."),
gnome_user_home_dir, home_link_name);
g_free (home_link_name);
return;
}
g_free (home_link_name);
}
}
/* Sets up a proxy window for DnD on the specified X window. Courtesy of Owen Taylor */
static gboolean
setup_xdnd_proxy (guint32 xid, GdkWindow *proxy_window)
{
GdkAtom xdnd_proxy_atom;
guint32 proxy_xid;
Atom type;
int format;
unsigned long nitems, after;
Window *proxy_data;
Window proxy;
guint32 old_warnings;
XGrabServer (GDK_DISPLAY ());
xdnd_proxy_atom = gdk_atom_intern ("XdndProxy", FALSE);
proxy_xid = GDK_WINDOW_XWINDOW (proxy_window);
type = None;
proxy = None;
old_warnings = gdk_error_warnings;
gdk_error_code = 0;
gdk_error_warnings = 0;
/* Check if somebody else already owns drops on the root window */
XGetWindowProperty (GDK_DISPLAY (), xid,
xdnd_proxy_atom, 0,
1, False, AnyPropertyType,
&type, &format, &nitems, &after,
(guchar **) &proxy_data);
if (type != None) {
if (format == 32 && nitems == 1)
proxy = *proxy_data;
XFree (proxy_data);
}
/* The property was set, now check if the window it points to exists and
* has a XdndProxy property pointing to itself.
*/
if (proxy) {
XGetWindowProperty (GDK_DISPLAY (), proxy,
xdnd_proxy_atom, 0,
1, False, AnyPropertyType,
&type, &format, &nitems, &after,
(guchar **) &proxy_data);
if (!gdk_error_code && type != None) {
if (format == 32 && nitems == 1)
if (*proxy_data != proxy)
proxy = GDK_NONE;
XFree (proxy_data);
} else
proxy = GDK_NONE;
}
if (!proxy) {
/* OK, we can set the property to point to us */
XChangeProperty (GDK_DISPLAY (), xid,
xdnd_proxy_atom, gdk_atom_intern ("WINDOW", FALSE),
32, PropModeReplace,
(guchar *) &proxy_xid, 1);
}
gdk_error_code = 0;
gdk_error_warnings = old_warnings;
XUngrabServer (GDK_DISPLAY ());
gdk_flush ();
if (!proxy) {
/* Mark our window as a valid proxy window with a XdndProxy
* property pointing recursively;
*/
XChangeProperty (GDK_DISPLAY (), proxy_xid,
xdnd_proxy_atom, gdk_atom_intern ("WINDOW", FALSE),
32, PropModeReplace,
(guchar *) &proxy_xid, 1);
return TRUE;
} else
return FALSE;
}
/* Returns the desktop icon that started the drag from the specified context */
struct desktop_icon_info *
find_icon_by_drag_context (GdkDragContext *context)
{
GtkWidget *source;
int i;
GList *l;
struct desktop_icon_info *dii;
source = gtk_drag_get_source_widget (context);
if (!source)
return NULL;
source = gtk_widget_get_toplevel (source);
for (i = 0; i < (layout_cols * layout_rows); i++)
for (l = layout_slots[i].icons; l; l = l->next) {
dii = l->data;
if (dii->dicon == source)
return dii;
}
return NULL;
}
/* Performs a drop of desktop icons onto the desktop. It basically moves the icons from their
* original position to the new coordinates.
*/
static void
drop_desktop_icons (GdkDragContext *context, GtkSelectionData *data, int x, int y)
{
struct desktop_icon_info *source_dii, *dii;
int dx, dy;
int i;
GList *l;
GSList *sel_icons, *sl;
/* FIXME: this needs to do the right thing (what Windows does) when desktop_auto_placement
* is enabled.
*/
/* Find the icon that the user is dragging */
source_dii = find_icon_by_drag_context (context);
if (!source_dii) {
g_warning ("Eeeeek, could not find the icon that started the drag!");
return;
}
/* Compute the distance to move icons */
if (desktop_snap_icons)
get_icon_snap_pos (&x, &y);
dx = x - source_dii->x - dnd_press_x;
dy = y - source_dii->y - dnd_press_y;
/* Build a list of selected icons */
sel_icons = NULL;
for (i = 0; i < (layout_cols * layout_rows); i++)
for (l = layout_slots[i].icons; l; l = l->next) {
dii = l->data;
if (dii->selected)
sel_icons = g_slist_prepend (sel_icons, l->data);
}
/* Move the icons */
for (sl = sel_icons; sl; sl = sl->next) {
dii = sl->data;
desktop_icon_info_place (dii, FALSE, dii->x + dx, dii->y + dy);
}
/* Clean up */
g_slist_free (sel_icons);
}
/* Callback used when the root window receives a drop */
static void
desktop_drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
GtkSelectionData *data, guint info, guint time, gpointer user_data)
{
int retval;
gint dx, dy;
/* Fix the proxy window offsets */
gdk_window_get_position (widget->window, &dx, &dy);
x += dx;
y += dy;
switch (info) {
case TARGET_MC_DESKTOP_ICON:
drop_desktop_icons (context, data, x, y);
break;
case TARGET_URI_LIST:
retval = gdnd_drop_on_directory (context, data, desktop_directory);
if (retval)
reload_desktop_icons (TRUE, x, y);
break;
default:
break;
}
}
/* Sets up drag and drop to the desktop root window */
static void
setup_desktop_dnd (void)
{
dnd_proxy_window = gtk_invisible_new ();
gtk_widget_show (dnd_proxy_window);
if (!setup_xdnd_proxy (GDK_ROOT_WINDOW (), dnd_proxy_window->window))
g_warning ("Eeeeek, some moron is already taking drops on the root window!");
gtk_drag_dest_set (dnd_proxy_window,
GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
dnd_desktop_targets,
dnd_desktop_ntargets,
GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
gtk_signal_connect (GTK_OBJECT (dnd_proxy_window), "drag_data_received",
GTK_SIGNAL_FUNC (desktop_drag_data_received),
NULL);
}
/* Looks for the proxy window to get root window clicks from the window manager */
static GdkWindow *
find_click_proxy_window (void)
{
GdkAtom click_proxy_atom;
Atom type;
int format;
unsigned long nitems, after;
Window *proxy_data;
Window proxy;
guint32 old_warnings;
GdkWindow *proxy_gdk_window;
XGrabServer (GDK_DISPLAY ());
click_proxy_atom = gdk_atom_intern ("_WIN_DESKTOP_BUTTON_PROXY", FALSE);
type = None;
proxy = None;
old_warnings = gdk_error_warnings;
gdk_error_code = 0;
gdk_error_warnings = 0;
/* Check if the proxy window exists */
XGetWindowProperty (GDK_DISPLAY (), GDK_ROOT_WINDOW (),
click_proxy_atom, 0,
1, False, AnyPropertyType,
&type, &format, &nitems, &after,
(guchar **) &proxy_data);
if (type != None) {
if (format == 32 && nitems == 1)
proxy = *proxy_data;
XFree (proxy_data);
}
/* The property was set, now check if the window it points to exists and
* has a _WIN_DESKTOP_BUTTON_PROXY property pointing to itself.
*/
if (proxy) {
XGetWindowProperty (GDK_DISPLAY (), proxy,
click_proxy_atom, 0,
1, False, AnyPropertyType,
&type, &format, &nitems, &after,
(guchar **) &proxy_data);
if (!gdk_error_code && type != None) {
if (format == 32 && nitems == 1)
if (*proxy_data != proxy)
proxy = GDK_NONE;
XFree (proxy_data);
} else
proxy = GDK_NONE;
}
gdk_error_code = 0;
gdk_error_warnings = old_warnings;
XUngrabServer (GDK_DISPLAY ());
gdk_flush ();
if (proxy)
proxy_gdk_window = gdk_window_foreign_new (proxy);
else
proxy_gdk_window = NULL;
return proxy_gdk_window;
}
/* Executes the popup menu for the desktop */
static void
desktop_popup (GdkEventButton *event)
{
printf ("FIXME: display desktop popup menu\n");
}
/* Draws the rubberband rectangle for selecting icons on the desktop */
static void
draw_rubberband (int x, int y)
{
int x1, y1, x2, y2;
if (click_start_x < x) {
x1 = click_start_x;
x2 = x;
} else {
x1 = x;
x2 = click_start_x;
}
if (click_start_y < y) {
y1 = click_start_y;
y2 = y;
} else {
y1 = y;
y2 = click_start_y;
}
gdk_draw_rectangle (GDK_ROOT_PARENT (), click_gc, FALSE, x1, y1, x2 - x1, y2 - y1);
}
/* Stores dii->selected into dii->tmp_selected to keep the original selection
* around while the user is rubberbanding.
*/
static void
store_temp_selection (void)
{
int i;
GList *l;
struct desktop_icon_info *dii;
for (i = 0; i < (layout_cols * layout_rows); i++)
for (l = layout_slots[i].icons; l; l = l->next) {
dii = l->data;
dii->tmp_selected = dii->selected;
}
}
/* Returns TRUE if the specified icon is at least partially inside the specified
* area, or FALSE otherwise.
*/
static int
icon_is_in_area (struct desktop_icon_info *dii, int x1, int y1, int x2, int y2)
{
DesktopIcon *dicon;
dicon = DESKTOP_ICON (dii->dicon);
/* FIXME: this only intersects the rectangle with the icon image's
* bounds. Doing the "hard" intersection with the actual shape of the
* image is left as an exercise to the reader.
*/
x1 -= dii->x;
y1 -= dii->y;
x2 -= dii->x;
y2 -= dii->y;
if (x1 < dicon->icon_x + dicon->icon_w - 1
&& x2 > dicon->icon_x
&& y1 < dicon->icon_y + dicon->icon_h - 1
&& y2 > dicon->icon_y)
return TRUE;
if (x1 < dicon->text_x + dicon->text_w - 1
&& x2 > dicon->text_x
&& y1 < dicon->text_y + dicon->text_h - 1
&& y2 > dicon->text_y)
return TRUE;
return FALSE;
}
/* Update the selection being rubberbanded. It selects or unselects the icons
* as appropriate.
*/
static void
update_drag_selection (int x, int y)
{
int x1, y1, x2, y2;
int i;
GList *l;
struct desktop_icon_info *dii;
int additive, invert, in_area;
if (click_start_x < x) {
x1 = click_start_x;
x2 = x;
} else {
x1 = x;
x2 = click_start_x;
}
if (click_start_y < y) {
y1 = click_start_y;
y2 = y;
} else {
y1 = y;
y2 = click_start_y;
}
/* Select or unselect icons as appropriate */
additive = click_start_state & GDK_SHIFT_MASK;
invert = click_start_state & GDK_CONTROL_MASK;
for (i = 0; i < (layout_cols * layout_rows); i++)
for (l = layout_slots[i].icons; l; l = l->next) {
dii = l->data;
in_area = icon_is_in_area (dii, x1, y1, x2, y2);
if (in_area) {
if (invert) {
if (dii->selected == dii->tmp_selected) {
desktop_icon_select (DESKTOP_ICON (dii->dicon), !dii->selected);
dii->selected = !dii->selected;
}
} else if (additive) {
if (!dii->selected) {
desktop_icon_select (DESKTOP_ICON (dii->dicon), TRUE);
dii->selected = TRUE;
}
} else {
if (!dii->selected) {
desktop_icon_select (DESKTOP_ICON (dii->dicon), TRUE);
dii->selected = TRUE;
}
}
} else if (dii->selected != dii->tmp_selected) {
desktop_icon_select (DESKTOP_ICON (dii->dicon), dii->tmp_selected);
dii->selected = dii->tmp_selected;
}
}
}
/* Handles button presses on the root window via the click_proxy_gdk_window */
static gint
click_proxy_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
GdkCursor *cursor;
if (event->button == 1) {
click_start_x = event->x;
click_start_y = event->y;
click_start_state = event->state;
XGrabServer (GDK_DISPLAY ());
cursor = gdk_cursor_new (GDK_TOP_LEFT_ARROW);
gdk_pointer_grab (GDK_ROOT_PARENT (),
FALSE,
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
NULL,
cursor,
event->time);
gdk_cursor_destroy (cursor);
/* If no modifiers are pressed, we unselect all the icons */
if ((click_start_state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == 0)
unselect_all (NULL);
store_temp_selection (); /* Save the original selection */
draw_rubberband (event->x, event->y);
click_current_x = event->x;
click_current_y = event->y;
click_dragging = TRUE;
return TRUE;
} else if (event->button == 3) {
desktop_popup (event);
return TRUE;
}
return FALSE;
}
/* Handles button releases on the root window via the click_proxy_gdk_window */
static gint
click_proxy_button_release (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
if (!click_dragging || event->button != 1)
return FALSE;
draw_rubberband (click_current_x, click_current_y);
gdk_pointer_ungrab (event->time);
click_dragging = FALSE;
update_drag_selection (event->x, event->y);
XUngrabServer (GDK_DISPLAY ());
return TRUE;
}
/* Handles motion events when dragging the icon-selection rubberband on the desktop */
static gint
click_proxy_motion (GtkWidget *widget, GdkEventMotion *event, gpointer data)
{
if (!click_dragging)
return FALSE;
draw_rubberband (click_current_x, click_current_y);
draw_rubberband (event->x, event->y);
update_drag_selection (event->x, event->y);
click_current_x = event->x;
click_current_y = event->y;
return TRUE;
}
/* Filter that translates proxied events from virtual root windows into normal
* Gdk events for the click_proxy_invisible widget.
*/
static GdkFilterReturn
click_proxy_filter (GdkXEvent *xevent, GdkEvent *event, gpointer data)
{
XEvent *xev;
xev = xevent;
switch (xev->type) {
case ButtonPress:
case ButtonRelease:
/* Translate button events into events that come from the proxy
* window, so that we can catch them as a signal from the
* invisible widget.
*/
if (xev->type == ButtonPress)
event->button.type = GDK_BUTTON_PRESS;
else
event->button.type = GDK_BUTTON_RELEASE;
gdk_window_ref (click_proxy_gdk_window);
event->button.window = click_proxy_gdk_window;
event->button.send_event = xev->xbutton.send_event;
event->button.time = xev->xbutton.time;
event->button.x = xev->xbutton.x;
event->button.y = xev->xbutton.y;
event->button.state = xev->xbutton.state;
event->button.button = xev->xbutton.button;
return GDK_FILTER_TRANSLATE;
case DestroyNotify:
/* The proxy window was destroyed (i.e. the window manager
* died), so we have to cope with it
*/
if (((GdkEventAny *) event)->window == click_proxy_gdk_window) {
gdk_window_destroy_notify (click_proxy_gdk_window);
click_proxy_gdk_window = NULL;
}
return GDK_FILTER_REMOVE;
default:
break;
}
return GDK_FILTER_CONTINUE;
}
/* Creates a proxy window to receive clicks from the root window and sets up the
* necessary event filters.
*/
static void
setup_desktop_click_proxy_window (void)
{
click_proxy_gdk_window = find_click_proxy_window ();
if (!click_proxy_gdk_window) {
g_warning ("Root window clicks will not work as no GNOME-compliant window manager could be found!");
return;
}
/* Make the proxy window send events to the invisible proxy widget */
gdk_window_set_user_data (click_proxy_gdk_window, click_proxy_invisible);
/* Add our filter to get events */
gdk_window_add_filter (click_proxy_gdk_window, click_proxy_filter, NULL);
/* The proxy window for clicks sends us button press events with
* SubstructureNotifyMask. We need StructureNotifyMask to receive
* DestroyNotify events, too.
*/
XSelectInput (GDK_DISPLAY (), GDK_WINDOW_XWINDOW (click_proxy_gdk_window),
SubstructureNotifyMask | StructureNotifyMask);
}
/* Handler for PropertyNotify events from the root window; it must change the
* proxy window to a new one.
*/
static gint
click_proxy_property_notify (GtkWidget *widget, GdkEventProperty *event, gpointer data)
{
if (event->window != GDK_ROOT_PARENT ())
return FALSE;
if (event->atom != gdk_atom_intern ("_WIN_DESKTOP_BUTTON_PROXY", FALSE))
return FALSE;
/* If there already is a proxy window, destroy it */
click_proxy_gdk_window = NULL;
/* Get the new proxy window */
setup_desktop_click_proxy_window ();
return TRUE;
}
#define gray50_width 2
#define gray50_height 2
static char gray50_bits[] = {
0x02, 0x01, };
/* Sets up the window manager proxy window to receive clicks on the desktop root window */
static void
setup_desktop_clicks (void)
{
GdkColormap *cmap;
GdkColor color;
GdkBitmap *stipple;
click_proxy_invisible = gtk_invisible_new ();
gtk_widget_show (click_proxy_invisible);
/* Make the root window send events to the invisible proxy widget */
gdk_window_set_user_data (GDK_ROOT_PARENT (), click_proxy_invisible);
/* Add our filter to get button press/release events (they are sent by
* the WM * with the window set to the root). Our filter will translate
* them to a GdkEvent with the proxy window as its window field.
*/
gdk_window_add_filter (GDK_ROOT_PARENT (), click_proxy_filter, NULL);
/* Select for PropertyNotify events from the root window */
XSelectInput (GDK_DISPLAY (), GDK_ROOT_WINDOW (), PropertyChangeMask);
/* Create the proxy window for clicks on the root window */
setup_desktop_click_proxy_window ();
/* Connect the signals */
gtk_signal_connect (GTK_OBJECT (click_proxy_invisible), "button_press_event",
(GtkSignalFunc) click_proxy_button_press,
NULL);
gtk_signal_connect (GTK_OBJECT (click_proxy_invisible), "button_release_event",
(GtkSignalFunc) click_proxy_button_release,
NULL);
gtk_signal_connect (GTK_OBJECT (click_proxy_invisible), "motion_notify_event",
(GtkSignalFunc) click_proxy_motion,
NULL);
gtk_signal_connect (GTK_OBJECT (click_proxy_invisible), "property_notify_event",
(GtkSignalFunc) click_proxy_property_notify,
NULL);
/* Create the GC to paint the rubberband rectangle */
click_gc = gdk_gc_new (GDK_ROOT_PARENT ());
cmap = gdk_window_get_colormap (GDK_ROOT_PARENT ());
gdk_color_white (cmap, &color);
if (color.pixel == 0)
gdk_color_black (cmap, &color);
gdk_gc_set_foreground (click_gc, &color);
gdk_gc_set_function (click_gc, GDK_XOR);
gdk_gc_set_fill (click_gc, GDK_STIPPLED);
stipple = gdk_bitmap_create_from_data (NULL, gray50_bits, gray50_width, gray50_height);
gdk_gc_set_stipple (click_gc, stipple);
gdk_bitmap_unref (stipple);
}
/**
* desktop_init
*
* Initializes the desktop by setting up the default icons (if necessary), setting up drag and drop,
* and other miscellaneous tasks.
*/
void
desktop_init (void)
{
create_layout_info ();
create_desktop_dir ();
load_desktop_icons (FALSE, 0, 0);
setup_desktop_dnd ();
setup_desktop_clicks ();
}
/**
* desktop_destroy
*
* Shuts the desktop down by destroying the desktop icons.
*/
void
desktop_destroy (void)
{
/* Destroy the desktop icons */
destroy_desktop_icons ();
/* Cleanup */
g_free (layout_slots);
layout_slots = NULL;
layout_cols = 0;
layout_rows = 0;
g_free (desktop_directory);
desktop_directory = NULL;
/* Remove DnD crap */
gtk_widget_destroy (dnd_proxy_window);
XDeleteProperty (GDK_DISPLAY (), GDK_ROOT_WINDOW (), gdk_atom_intern ("XdndProxy", FALSE));
/* Remove click-on-desktop crap */
gdk_window_unref (click_proxy_gdk_window);
gtk_widget_destroy (click_proxy_invisible);
}