mirror of
https://github.com/MidnightCommander/mc
synced 2024-12-23 21:06:52 +03:00
f97cb04b55
* gscreen.c (panel_setup_drag_scroll): Renamed from panel_setup_drag_motion(). (panel_clist_drag_motion): Use gdnd_validate_action(). (panel_clist_motion): Return immediately if the event window is not the clist_window. Otherwise, forward the event to panel_widget_motion(). * gdnd.c (gdnd_find_panel_by_drag_context): New public function to find a panel based on a drag context. This is basically the old find_panel_owning_window() made public. (gdnd_drop_on_directory): Test for the source widget being a tree, and if so, do not use the default behavior for panels. (gdnd_validate_action): New function to compute the final drag action given some conditions. This is now used to make dragging behavior consistent across the desktop and the file panels.
2248 lines
55 KiB
C
2248 lines
55 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 "ext.h"
|
|
#include "file.h"
|
|
#include "fileopctx.h"
|
|
#include "gconf.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"
|
|
|
|
|
|
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 DesktopIconInfo *last_selected_icon;
|
|
|
|
/* Drag and drop sources and targets */
|
|
|
|
static GtkTargetEntry dnd_icon_sources[] = {
|
|
{ TARGET_MC_DESKTOP_ICON_TYPE, 0, TARGET_MC_DESKTOP_ICON },
|
|
{ TARGET_URI_LIST_TYPE, 0, TARGET_URI_LIST },
|
|
{ TARGET_TEXT_PLAIN_TYPE, 0, TARGET_TEXT_PLAIN },
|
|
{ TARGET_URL_TYPE, 0, TARGET_URL }
|
|
};
|
|
|
|
static GtkTargetEntry dnd_icon_targets[] = {
|
|
{ TARGET_MC_DESKTOP_ICON_TYPE, 0, TARGET_MC_DESKTOP_ICON },
|
|
{ TARGET_URI_LIST_TYPE, 0, TARGET_URI_LIST }
|
|
};
|
|
|
|
static GtkTargetEntry dnd_desktop_targets[] = {
|
|
{ TARGET_MC_DESKTOP_ICON_TYPE, 0, TARGET_MC_DESKTOP_ICON },
|
|
{ TARGET_URI_LIST_TYPE, 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 needs to be resolved at button release time. Also, we store the
|
|
* event->state.
|
|
*/
|
|
static int dnd_select_icon_pending;
|
|
static guint dnd_select_icon_pending_state;
|
|
|
|
/* Whether the button release signal on a desktop icon should be stopped due to
|
|
* the icon being selected by clicking on the text item.
|
|
*/
|
|
static int icon_select_on_text;
|
|
|
|
/* 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 DesktopIconInfo *desktop_icon_info_new (char *filename, int auto_pos, int xpos, int ypos);
|
|
|
|
|
|
/* 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 (DesktopIconInfo *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 (DesktopIconInfo *dii, int auto_pos, int xpos, int ypos)
|
|
{
|
|
int u, v;
|
|
char *filename;
|
|
|
|
if (auto_pos) {
|
|
if (desktop_auto_placement || !desktop_snap_icons)
|
|
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);
|
|
}
|
|
|
|
/* Destroys all the current desktop icons */
|
|
static void
|
|
destroy_desktop_icons (void)
|
|
{
|
|
int i;
|
|
GList *l;
|
|
DesktopIconInfo *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_destroy (dii);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Returns a list with all of the icons on the desktop */
|
|
static GList *
|
|
get_all_icons (void)
|
|
{
|
|
GList *l, *res;
|
|
int i;
|
|
|
|
res = NULL;
|
|
|
|
for (i = 0; i < (layout_cols * layout_rows); i++)
|
|
for (l = layout_slots [i].icons; l; l = l->next)
|
|
res = g_list_prepend (res, l->data);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Returns the node in the list of DesktopIconInfo structures that contains the
|
|
* icon for the specified filename. If no icon is found, then returns NULL.
|
|
*/
|
|
static GList *
|
|
icon_exists_in_list (GList *list, char *filename)
|
|
{
|
|
GList *l;
|
|
DesktopIconInfo *dii;
|
|
|
|
for (l = list; l; l = l->next) {
|
|
dii = l->data;
|
|
|
|
if (strcmp (filename, dii->filename) == 0)
|
|
return l;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Reloads the desktop icons efficiently. If there are "new" files for which no
|
|
* icons have been created, then icons for them will be created started at the
|
|
* specified position -- this is used when dragging new icons to the desktop.
|
|
*/
|
|
static void
|
|
reload_desktop_icons (int xpos, int ypos)
|
|
{
|
|
struct dirent *dirent;
|
|
DIR *dir;
|
|
char *full_name;
|
|
int have_pos, x, y;
|
|
DesktopIconInfo *dii;
|
|
GSList *need_position_list, *sl;
|
|
GList *all_icons, *l;
|
|
|
|
|
|
dir = mc_opendir (desktop_directory);
|
|
if (!dir) {
|
|
message (FALSE,
|
|
_("Warning"),
|
|
_("Could not open %s; will not have initial desktop icons"),
|
|
desktop_directory);
|
|
return;
|
|
}
|
|
|
|
gnome_metadata_lock ();
|
|
|
|
/* Read the directory. For each file for which we do have an existing
|
|
* icon, do nothing. Otherwise, if the file has its metadata for icon
|
|
* position set, create an icon for it. Otherwise, store it in a list
|
|
* of new icons for which positioning is pending.
|
|
*/
|
|
|
|
need_position_list = NULL;
|
|
all_icons = get_all_icons ();
|
|
|
|
while ((dirent = mc_readdir (dir)) != NULL) {
|
|
/* Skip . and .. */
|
|
|
|
if ((dirent->d_name[0] == '.' && dirent->d_name[1] == 0)
|
|
|| (dirent->d_name[0] == '.' && dirent->d_name[1] == '.'
|
|
&& dirent->d_name[2] == 0))
|
|
continue;
|
|
|
|
l = icon_exists_in_list (all_icons, dirent->d_name);
|
|
if (l) {
|
|
all_icons = g_list_remove_link (all_icons, l);
|
|
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);
|
|
|
|
/* all_icons now contains a list of all of the icons that were not found
|
|
* in the ~/desktop directory, remove them. To be paranoically tidy,
|
|
* also delete the icon position information -- someone may have deleted
|
|
* a file under us.
|
|
*/
|
|
|
|
for (l = all_icons; l; l = l->next) {
|
|
dii = l->data;
|
|
full_name = g_concat_dir_and_file (desktop_directory, dii->filename);
|
|
gmeta_del_icon_pos (full_name);
|
|
g_free (full_name);
|
|
desktop_icon_info_destroy (dii);
|
|
}
|
|
|
|
g_list_free (all_icons);
|
|
|
|
gnome_metadata_unlock ();
|
|
|
|
/* 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 (sl = need_position_list; sl; sl = sl->next) {
|
|
dii = desktop_icon_info_new (sl->data, TRUE, xpos, ypos);
|
|
gtk_widget_show (dii->dicon);
|
|
g_free (sl->data);
|
|
}
|
|
|
|
g_slist_free (need_position_list);
|
|
|
|
/* Flush events to make the icons paint themselves */
|
|
x_flush_events ();
|
|
}
|
|
|
|
/* Unselects all the desktop icons except the one in exclude */
|
|
static void
|
|
unselect_all (DesktopIconInfo *exclude)
|
|
{
|
|
int i;
|
|
GList *l;
|
|
DesktopIconInfo *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 (DesktopIconInfo *dii, int sel)
|
|
{
|
|
int du, dv, lu, lv;
|
|
int min_u, min_v;
|
|
int max_u, max_v;
|
|
int u, v;
|
|
GList *l;
|
|
DesktopIconInfo *ldii;
|
|
DesktopIconInfo *min_udii, *min_vdii;
|
|
DesktopIconInfo *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;
|
|
|
|
/* Even if the icons are on the same slot, their positions may
|
|
* need adjusting with respect to each other.
|
|
*/
|
|
if (du != lu || last_selected_icon->x < dii->x) {
|
|
min_udii = last_selected_icon;
|
|
max_udii = dii;
|
|
} else {
|
|
min_udii = dii;
|
|
max_udii = last_selected_icon;
|
|
}
|
|
}
|
|
|
|
if (dv < lv) {
|
|
min_v = dv;
|
|
max_v = lv;
|
|
min_vdii = dii;
|
|
max_vdii = last_selected_icon;
|
|
} else {
|
|
min_v = lv;
|
|
max_v = dv;
|
|
|
|
/* Same as above */
|
|
if (dv != lv || last_selected_icon->y < dii->y) {
|
|
min_vdii = last_selected_icon;
|
|
max_vdii = dii;
|
|
} else {
|
|
min_vdii = dii;
|
|
max_vdii = last_selected_icon;
|
|
}
|
|
}
|
|
|
|
/* 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 (DesktopIconInfo *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. */
|
|
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 */
|
|
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)
|
|
{
|
|
DesktopIconInfo *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;
|
|
}
|
|
|
|
/* Sets up the mouse grab for when a desktop icon is being edited */
|
|
static void
|
|
setup_editing_grab (DesktopIconInfo *dii)
|
|
{
|
|
GdkCursor *ibeam;
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
DesktopIconInfo *dii;
|
|
|
|
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);
|
|
|
|
gtk_grab_add (dii->dicon);
|
|
setup_editing_grab (dii);
|
|
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 (DesktopIconInfo *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)
|
|
{
|
|
DesktopIconInfo *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);
|
|
}
|
|
|
|
/* Callback used when the user stops selecting text in a desktop icon. This function
|
|
* restores the mouse grab that we had set up initially (the selection process changes
|
|
* the grab and then removes it, so we need to restore the initial grab).
|
|
*/
|
|
static void
|
|
selection_stopped (GnomeIconTextItem *iti, gpointer data)
|
|
{
|
|
DesktopIconInfo *dii;
|
|
|
|
dii = data;
|
|
setup_editing_grab (dii);
|
|
}
|
|
|
|
/**
|
|
* desktop_icon_info_open:
|
|
* @dii: The desktop icon to open.
|
|
*
|
|
* Opens the specified desktop icon when the user double-clicks on it.
|
|
**/
|
|
void
|
|
desktop_icon_info_open (DesktopIconInfo *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);
|
|
}
|
|
|
|
void
|
|
desktop_icon_info_delete (DesktopIconInfo *dii)
|
|
{
|
|
char *full_name;
|
|
struct stat s;
|
|
FileOpContext *ctx;
|
|
long progress_count = 0;
|
|
double progress_bytes = 0;
|
|
|
|
/* 1. Delete the file */
|
|
ctx = file_op_context_new ();
|
|
file_op_context_create_ui (ctx, OP_DELETE, TRUE);
|
|
x_flush_events ();
|
|
|
|
full_name = g_concat_dir_and_file (desktop_directory, dii->filename);
|
|
|
|
if (lstat (full_name, &s) != -1) {
|
|
if (S_ISLNK (s.st_mode))
|
|
erase_file (ctx, full_name, &progress_count, &progress_bytes, TRUE);
|
|
else {
|
|
if (S_ISDIR (s.st_mode))
|
|
erase_dir (ctx, full_name, &progress_count, &progress_bytes);
|
|
else
|
|
erase_file (ctx, full_name, &progress_count, &progress_bytes, TRUE);
|
|
}
|
|
|
|
gmeta_del_icon_pos (full_name);
|
|
}
|
|
|
|
g_free (full_name);
|
|
file_op_context_destroy (ctx);
|
|
|
|
/* 2. Destroy the dicon */
|
|
desktop_icon_info_destroy (dii);
|
|
}
|
|
|
|
/* Used to execute the popup menu for desktop icons */
|
|
static void
|
|
do_popup_menu (DesktopIconInfo *dii, GdkEventButton *event)
|
|
{
|
|
char *filename;
|
|
|
|
filename = g_concat_dir_and_file (desktop_directory, dii->filename);
|
|
|
|
if (gpopup_do_popup (event, NULL, dii, 0, filename) != -1)
|
|
reload_desktop_icons (0, 0);
|
|
|
|
g_free (filename);
|
|
}
|
|
|
|
/* Idle handler that opens a desktop icon. See below for information on why we
|
|
* do things this way.
|
|
*/
|
|
static gint
|
|
idle_open_icon (gpointer data)
|
|
{
|
|
desktop_icon_info_open (data);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Event handler for desktop icons. Button events are ignored when the icon is
|
|
* being edited; these will be handled either by the icon's text item or the
|
|
* icon_event_after() fallback.
|
|
*/
|
|
static gint
|
|
icon_event (GnomeCanvasItem *item, GdkEvent *event, gpointer data)
|
|
{
|
|
DesktopIconInfo *dii;
|
|
GnomeIconTextItem *iti;
|
|
int on_text;
|
|
int retval;
|
|
|
|
dii = data;
|
|
iti = GNOME_ICON_TEXT_ITEM (DESKTOP_ICON (dii->dicon)->text);
|
|
|
|
on_text = item == GNOME_CANVAS_ITEM (iti);
|
|
retval = FALSE;
|
|
|
|
switch (event->type) {
|
|
case GDK_BUTTON_PRESS:
|
|
if (event->button.button == 1) {
|
|
/* If se are editing, do not handle the event ourselves
|
|
* -- either let the text item handle it, or wait until
|
|
* we fall back to the icon_event_after() callback.
|
|
*/
|
|
if (iti->editing)
|
|
break;
|
|
|
|
/* Save the mouse position for DnD */
|
|
|
|
dnd_press_x = event->button.x;
|
|
dnd_press_y = event->button.y;
|
|
|
|
/* Handle icon selection if we are not on the text item
|
|
* or if the icon is not selected in the first place.
|
|
* Otherwise, if there are modifier keys pressed, handle
|
|
* icon selection instead of starting editing.
|
|
*/
|
|
if (!on_text
|
|
|| !dii->selected
|
|
|| (event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
|
|
/* If click on text, and the icon was not
|
|
* selected in the first place or shift is down,
|
|
* save this flag.
|
|
*/
|
|
if (on_text
|
|
&& (!dii->selected || (event->button.state & GDK_SHIFT_MASK)))
|
|
icon_select_on_text = TRUE;
|
|
|
|
if ((dii->selected
|
|
&& !(event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
|
|
|| ((event->button.state & GDK_CONTROL_MASK)
|
|
&& !(event->button.state & GDK_SHIFT_MASK))) {
|
|
dnd_select_icon_pending = TRUE;
|
|
dnd_select_icon_pending_state = event->button.state;
|
|
} else
|
|
select_icon (dii, event->button.state);
|
|
|
|
retval = TRUE;
|
|
}
|
|
} else if (event->button.button == 3) {
|
|
do_popup_menu (dii, (GdkEventButton *) event);
|
|
retval = TRUE;
|
|
}
|
|
break;
|
|
|
|
case GDK_2BUTTON_PRESS:
|
|
if (event->button.button != 1 || iti->editing)
|
|
break;
|
|
|
|
/* We have an interesting race condition here. If we open the
|
|
* desktop icon here instead of doing it in the idle handler,
|
|
* the icon thinks it must begin editing itself, even when the
|
|
* icon_select_on_text flag tries to prevent it. I have no idea
|
|
* why this happens :-( - Federico
|
|
*/
|
|
gtk_idle_add (idle_open_icon, dii);
|
|
/* desktop_icon_info_open (dii); */
|
|
icon_select_on_text = TRUE;
|
|
retval = TRUE;
|
|
break;
|
|
|
|
case GDK_BUTTON_RELEASE:
|
|
if (event->button.button != 1)
|
|
break;
|
|
|
|
if (on_text && icon_select_on_text) {
|
|
icon_select_on_text = FALSE;
|
|
retval = TRUE;
|
|
}
|
|
|
|
if (dnd_select_icon_pending) {
|
|
select_icon (dii, dnd_select_icon_pending_state);
|
|
dnd_select_icon_pending = FALSE;
|
|
dnd_select_icon_pending_state = 0;
|
|
retval = TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* If the click was on the text and we actually did something, then we
|
|
* need to stop the text item's event handler from executing.
|
|
*/
|
|
if (on_text && retval)
|
|
gtk_signal_emit_stop_by_name (GTK_OBJECT (iti), "event");
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Fallback handler for when the icon is being edited and the user clicks
|
|
* outside the icon's text item. This indicates that editing should be accepted
|
|
* and terminated.
|
|
*/
|
|
static gint
|
|
icon_event_after (GtkWidget *widget, GdkEventButton *event, gpointer data)
|
|
{
|
|
DesktopIconInfo *dii;
|
|
GnomeIconTextItem *iti;
|
|
|
|
dii = data;
|
|
iti = GNOME_ICON_TEXT_ITEM (DESKTOP_ICON (dii->dicon)->text);
|
|
|
|
if (event->type != GDK_BUTTON_PRESS)
|
|
return FALSE;
|
|
|
|
g_return_val_if_fail (iti->editing, FALSE); /* sanity check for dropped events */
|
|
|
|
gnome_icon_text_item_stop_editing (iti, TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
DesktopIconInfo *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, dnd_select_icon_pending_state);
|
|
|
|
dnd_select_icon_pending = FALSE;
|
|
dnd_select_icon_pending_state = 0;
|
|
}
|
|
|
|
/* 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;
|
|
DesktopIconInfo *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)
|
|
{
|
|
DesktopIconInfo *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 (DesktopIconInfo *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);
|
|
}
|
|
|
|
/* Callback used when we get a drag_motion event from a desktop icon. We have
|
|
* to decide which operation to perform based on the type of the data the user
|
|
* is dragging.
|
|
*/
|
|
static gboolean
|
|
icon_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time,
|
|
gpointer data)
|
|
{
|
|
DesktopIconInfo *dii;
|
|
char *filename;
|
|
file_entry *fe;
|
|
GdkDragAction action;
|
|
|
|
dii = data;
|
|
|
|
filename = g_concat_dir_and_file (desktop_directory, dii->filename);
|
|
fe = file_entry_from_file (filename);
|
|
g_free (filename);
|
|
|
|
action = 0; /* be pessimistic by defaulting to nothing */
|
|
|
|
if (dii->selected
|
|
&& gdnd_drag_context_has_target (context, TARGET_MC_DESKTOP_ICON)
|
|
&& (context->actions & GDK_ACTION_MOVE))
|
|
action = GDK_ACTION_MOVE;
|
|
else if (gdnd_drag_context_has_target (context, TARGET_URI_LIST)) {
|
|
if (S_ISDIR (fe->buf.st_mode) || fe->f.link_to_dir)
|
|
action = context->suggested_action;
|
|
else if (is_exe (fe->buf.st_mode)
|
|
&& if_link_is_exe (fe)
|
|
&& (context->actions & GDK_ACTION_COPY))
|
|
action = GDK_ACTION_COPY;
|
|
}
|
|
|
|
gdk_drag_status (context, action, time);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Returns the desktop icon that started the drag from the specified context */
|
|
static DesktopIconInfo *
|
|
find_icon_by_drag_context (GdkDragContext *context)
|
|
{
|
|
GtkWidget *source;
|
|
int i;
|
|
GList *l;
|
|
DesktopIconInfo *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)
|
|
{
|
|
DesktopIconInfo *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);
|
|
}
|
|
|
|
/**
|
|
* drop_on_file_entry
|
|
*/
|
|
static void
|
|
desktop_icon_drop_uri_list (DesktopIconInfo *dii, GdkDragContext *context, GtkSelectionData *data)
|
|
{
|
|
char *filename;
|
|
file_entry *fe;
|
|
int size;
|
|
char *buf, *mime_type;
|
|
|
|
filename = g_concat_dir_and_file (desktop_directory, dii->filename);
|
|
|
|
fe = file_entry_from_file (filename);
|
|
if (!fe)
|
|
return; /* eek */
|
|
|
|
/*
|
|
* 1. If it is directory, drop the files there
|
|
*/
|
|
if (fe->f.link_to_dir){
|
|
gdnd_drop_on_directory (context, data, filename);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* 2. Try to use a metadata-based drop action
|
|
*/
|
|
if (gnome_metadata_get (filename, "drop-action", &size, &buf) == 0){
|
|
/*action_drop (filename, buf, context, data);*/ /* Fixme: i'm undefined */
|
|
g_free (buf);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* 3. Try a drop action from the mime-type
|
|
*/
|
|
mime_type = gnome_mime_type_or_default (filename, NULL);
|
|
if (mime_type){
|
|
char *action;
|
|
|
|
action = gnome_mime_get_value (mime_type, "drop-action");
|
|
|
|
if (action){
|
|
/*action_drop (filename, action, context, data);*/ /* Fixme: i'm undefined */
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 4. Executable. Try metadata keys for "open".
|
|
*/
|
|
if (is_exe (fe->buf.st_mode) && if_link_is_exe (fe)){
|
|
GList *names, *l;
|
|
int len, i;
|
|
char **drops;
|
|
|
|
/* Convert the list of filenames into an array of char */
|
|
names = gnome_uri_list_extract_uris (data->data);
|
|
len = g_list_length (names);
|
|
drops = (char **) g_malloc (sizeof (char *) * (len+1));
|
|
|
|
for (l = names, i = 0; i < len; i++, l = l->next){
|
|
char *text = l->data;
|
|
|
|
if (strncmp (text, "file:", 5) == 0)
|
|
text += 5;
|
|
|
|
drops [i] = text;
|
|
}
|
|
drops [i] = NULL;
|
|
|
|
if (gnome_metadata_get (filename, "open", &size, &buf) == 0){
|
|
exec_extension (filename, buf, drops, NULL, 0);
|
|
goto out2;
|
|
}
|
|
exec_extension (filename, "%f %q", drops, NULL, 0);
|
|
|
|
g_free (drops);
|
|
out2:
|
|
gnome_uri_list_free_strings (names);
|
|
g_free (buf);
|
|
|
|
}
|
|
|
|
out:
|
|
file_entry_free (fe);
|
|
}
|
|
|
|
static void
|
|
icon_drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
|
|
GtkSelectionData *data, guint info, guint time, gpointer user_data)
|
|
{
|
|
DesktopIconInfo *dii;
|
|
|
|
dii = user_data;
|
|
|
|
switch (info) {
|
|
case TARGET_MC_DESKTOP_ICON:
|
|
if (dii->selected)
|
|
drop_desktop_icons (context, data, x + dii->x, y + dii->y);
|
|
else
|
|
printf ("FIXME: what do drop?\n"); /* FIXME */
|
|
|
|
break;
|
|
|
|
case TARGET_URI_LIST:
|
|
printf ("Wheeeeee!\n");
|
|
desktop_icon_drop_uri_list (dii, context, data);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Set up a desktop icon as a DnD destination */
|
|
static void
|
|
setup_icon_dnd_dest (DesktopIconInfo *dii)
|
|
{
|
|
gtk_drag_dest_set (DESKTOP_ICON (dii->dicon)->canvas,
|
|
GTK_DEST_DEFAULT_DROP,
|
|
dnd_icon_targets,
|
|
dnd_icon_ntargets,
|
|
GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
|
|
|
|
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_motion",
|
|
(GtkSignalFunc) (icon_drag_motion),
|
|
dii);
|
|
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_data_received",
|
|
(GtkSignalFunc) 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 DesktopIconInfo *
|
|
desktop_icon_info_new (char *filename, int auto_pos, int xpos, int ypos)
|
|
{
|
|
DesktopIconInfo *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_speed (desktop_directory, fe, FALSE);
|
|
|
|
dii = g_new (DesktopIconInfo, 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. We connect to the image/stipple and
|
|
* text items separately so that the callback can distinguish between
|
|
* them. This is not a hack.
|
|
*/
|
|
|
|
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->icon), "event",
|
|
(GtkSignalFunc) icon_event,
|
|
dii);
|
|
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->stipple), "event",
|
|
(GtkSignalFunc) icon_event,
|
|
dii);
|
|
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "event",
|
|
(GtkSignalFunc) icon_event,
|
|
dii);
|
|
|
|
/* Connect_after to button presses on the icon's window. This is a
|
|
* fallback for when the icon is being edited and a button press is not
|
|
* handled by the icon's text item -- this means the user has clicked
|
|
* outside the text item and wishes to accept and terminate editing.
|
|
*/
|
|
gtk_signal_connect_after (GTK_OBJECT (dii->dicon), "button_press_event",
|
|
(GtkSignalFunc) icon_event_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);
|
|
gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "selection_stopped",
|
|
(GtkSignalFunc) selection_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;
|
|
}
|
|
|
|
/**
|
|
* desktop_icon_info_destroy:
|
|
* @dii: The desktop icon to destroy
|
|
*
|
|
* Destroys the specified desktop icon.
|
|
**/
|
|
void
|
|
desktop_icon_info_destroy (DesktopIconInfo *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);
|
|
}
|
|
|
|
static void
|
|
setup_trashcan (char *desktop_dir)
|
|
{
|
|
char *trashcan_dir;
|
|
char *trash_pix;
|
|
|
|
trashcan_dir = g_concat_dir_and_file (desktop_directory, _("Trashcan"));
|
|
trash_pix = g_concat_dir_and_file (ICONDIR, "trash.xpm");
|
|
|
|
if (!g_file_exists (trashcan_dir)){
|
|
mkdir (trashcan_dir, 0777);
|
|
gnome_metadata_set (
|
|
trashcan_dir, "icon-filename", strlen (trash_pix)+1, trash_pix);
|
|
}
|
|
|
|
g_free (trashcan_dir);
|
|
g_free (trash_pix);
|
|
}
|
|
|
|
/*
|
|
* Check that the user's desktop directory exists, and if not, create
|
|
* the default desktop setup.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/* setup_trashcan (desktop_directory); */
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Callback used when we get a drag_motion event from the desktop. We must
|
|
* decide what kind of operation can be performed with what the user is
|
|
* dragging.
|
|
*/
|
|
static gboolean
|
|
desktop_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time,
|
|
gpointer data)
|
|
{
|
|
GdkDragAction action;
|
|
GtkWidget *source_widget;
|
|
|
|
action = context->suggested_action; /* this is the default */
|
|
|
|
if (gdnd_drag_context_has_target (context, TARGET_MC_DESKTOP_ICON)) {
|
|
if (context->actions & GDK_ACTION_MOVE)
|
|
action = GDK_ACTION_MOVE;
|
|
} else if (gdnd_drag_context_has_target (context, TARGET_URI_LIST)) {
|
|
source_widget = gtk_drag_get_source_widget (context);
|
|
|
|
/* If it comes from ourselves, make move the default unless the
|
|
* user is explicitly asking for ASK.
|
|
*/
|
|
printf ("%s\t%s\t%s\t%s\n",
|
|
(context->actions & GDK_ACTION_COPY) ? "copy" : "",
|
|
(context->actions & GDK_ACTION_MOVE) ? "move" : "",
|
|
(context->actions & GDK_ACTION_LINK) ? "link" : "",
|
|
(context->actions & GDK_ACTION_ASK) ? "ask" : "");
|
|
|
|
if (source_widget
|
|
&& context->suggested_action != GDK_ACTION_ASK
|
|
&& (context->actions & GDK_ACTION_MOVE))
|
|
action = GDK_ACTION_MOVE;
|
|
} else
|
|
action = 0; /* we cannot handle that type of data */
|
|
|
|
gdk_drag_status (context, action, time);
|
|
return TRUE;
|
|
}
|
|
|
|
/* 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 (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 ("There is already a process taking drop windows on the desktop\n");
|
|
|
|
gtk_drag_dest_set (dnd_proxy_window,
|
|
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_motion",
|
|
(GtkSignalFunc) desktop_drag_motion,
|
|
NULL);
|
|
gtk_signal_connect (GTK_OBJECT (dnd_proxy_window), "drag_data_received",
|
|
(GtkSignalFunc) 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;
|
|
DesktopIconInfo *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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* icon_is_in_area:
|
|
* @dii: the desktop icon information
|
|
*
|
|
* Returns TRUE if the specified icon is at least partially inside the specified
|
|
* area, or FALSE otherwise.
|
|
*/
|
|
static int
|
|
icon_is_in_area (DesktopIconInfo *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;
|
|
DesktopIconInfo *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)
|
|
{
|
|
gdnd_init ();
|
|
create_layout_info ();
|
|
create_desktop_dir ();
|
|
reload_desktop_icons (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);
|
|
}
|