/* Desktop management for the Midnight Commander * * Copyright (C) 1998 The Free Software Foundation * * Authors: Federico Mena * Miguel de Icaza */ #include #include "global.h" #include #include #include #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" #include "main.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 }, { TARGET_URL_TYPE, 0, TARGET_URL } }; static GtkTargetEntry dnd_desktop_targets[] = { { TARGET_MC_DESKTOP_ICON_TYPE, 0, TARGET_MC_DESKTOP_ICON }, { TARGET_URI_LIST_TYPE, 0, TARGET_URI_LIST }, { TARGET_URL_TYPE, 0, TARGET_URL } }; 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 and clicks on the desktop */ static GtkWidget *proxy_invisible; /* 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; /* 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, char *url, char *caption, int xpos, int ypos); /* Convenience function to figure out the slot corresponding to an (x, y) position */ static void get_slot_from_pos (int x, int y, int *u, int *v) { *u = (x + DESKTOP_SNAP_X / 2) / DESKTOP_SNAP_X; *v = (y + DESKTOP_SNAP_Y / 2) / DESKTOP_SNAP_Y; } /* 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. Returns the number of * icons in the sought spot (ideally 0). */ static int auto_pos (int start_slot, int end_slot, int *slot) { int min, min_slot; int i; int val; min = layout_slots[start_slot].num_icons; min_slot = start_slot; for (i = start_slot; i < end_slot; i++) { val = layout_slots[i].num_icons; if (val < min || val == 0) { min = val; min_slot = i; if (val == 0) break; } } *slot = min_slot; return min; } /* Looks for free space in the icon grid, scanning it in column-wise order */ static void get_icon_auto_pos (int *x, int *y) { int start, end; int u, v; int val1, val2; int slot1, slot2; int slot; get_slot_from_pos (*x, *y, &u, &v); start = u * layout_rows + v; end = layout_rows * layout_cols; /* Look forwards until the end of the grid. If we could not find an * empty spot, find the second best. */ val1 = auto_pos (start, end, &slot1); if (val1 == 0) slot = slot1; else { val2 = auto_pos (0, start, &slot2); if (val2 < val1) slot = slot2; } *x = (slot / layout_rows) * DESKTOP_SNAP_X; *y = (slot % layout_rows) * 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); dii->slot = -1; dii->x = 0; dii->y = 0; } /* Places a desktop icon on the specified position */ static void desktop_icon_info_place (DesktopIconInfo *dii, int xpos, int ypos) { int u, v; char *filename; remove_from_slot (dii); 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 */ get_slot_from_pos (xpos, ypos, &u, &v); 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; } typedef struct { char *filename; char *url; char *caption; } file_and_url_t; /* 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 if user_pos is TRUE. If it is FALSE, the icons will be * auto-placed. */ static void reload_desktop_icons (int user_pos, int xpos, int ypos) { struct dirent *dirent; DIR *dir; char *full_name; int have_pos, x, y, size; DesktopIconInfo *dii; GSList *need_position_list, *sl; GList *all_icons, *l; char *desktop_url, *caption; int orig_xpos, orig_ypos; dir = mc_opendir (desktop_directory); if (!dir) { message (FALSE, _("Warning"), _("Could not open %s; will not have 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) { GdkImlibImage *im; file_entry *fe; /* Reload the icon for this file, as it may have changed */ full_name = g_concat_dir_and_file (desktop_directory, dirent->d_name); fe = file_entry_from_file (full_name); im = gicon_get_icon_for_file_speed (desktop_directory, fe, FALSE); file_entry_free (fe); g_free (full_name); dii = l->data; desktop_icon_set_icon (DESKTOP_ICON (dii->dicon), im); /* Leave the icon in the desktop by removing it from the list */ 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 (gnome_metadata_get (full_name, "desktop-url", &size, &desktop_url) != 0) desktop_url = NULL; caption = NULL; gnome_metadata_get (full_name, "icon-caption", &size, &caption); if (have_pos) { dii = desktop_icon_info_new (dirent->d_name, desktop_url, caption, x, y); gtk_widget_show (dii->dicon); g_free (full_name); } else { file_and_url_t *fau; fau = g_new0 (file_and_url_t, 1); fau->filename = g_strdup (dirent->d_name); if (desktop_url) fau->url = g_strdup (desktop_url); if (caption) fau->caption = g_strdup (caption); need_position_list = g_slist_prepend (need_position_list, fau); } if (desktop_url) g_free (desktop_url); if (caption) g_free (caption); } 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); /* 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); orig_xpos = orig_ypos = 0; for (sl = need_position_list; sl; sl = sl->next) { file_and_url_t *fau = sl->data; if (user_pos && sl == need_position_list) { /* If we are on the first icon, place it "by hand". * Else, use automatic placement based on the position * of the first icon of the series. */ if (desktop_auto_placement) { xpos = ypos = 0; get_icon_auto_pos (&xpos, &ypos); } else if (desktop_snap_icons) get_icon_snap_pos (&xpos, &ypos); orig_xpos = xpos; orig_ypos = ypos; } else { xpos = orig_xpos; ypos = orig_ypos; get_icon_auto_pos (&xpos, &ypos); } dii = desktop_icon_info_new (fau->filename, fau->url, fau->caption, xpos, ypos); gtk_widget_show (dii->dicon); if (fau->url) g_free (fau->url); if (fau->caption) g_free (fau->caption); g_free (fau->filename); g_free (fau); } g_slist_free (need_position_list); gnome_metadata_unlock (); /* Flush events to make the icons paint themselves */ x_flush_events (); } /* Perform automatic arrangement of the desktop icons */ static void arrange_desktop_icons (void) { GList *icons, *l; int xpos, ypos; icons = g_list_reverse (get_all_icons ()); for (l = icons; l; l = l->next) remove_from_slot (l->data); for (l = icons; l; l = l->next) { get_icon_auto_pos (&xpos, &ypos); desktop_icon_info_place (l->data, xpos, ypos); } g_list_free (icons); } /* 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; /* 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; } else { min_u = lu; max_u = du; } if (dv < lv) { min_v = dv; max_v = lv; } else { min_v = lv; max_v = dv; } /* 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; 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; } /* * Callback when an icon's text changes and the icon reprensents an * URL */ static int text_changed_url (GnomeIconTextItem *iti, gpointer data) { DesktopIconInfo *dii = data; char *fullname; char *new_text; fullname = g_concat_dir_and_file (desktop_directory, dii->filename); new_text = gnome_icon_text_item_get_text (iti); gnome_metadata_set (fullname, "icon-caption", strlen (new_text) + 1, new_text); return TRUE; } /* 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; if (dii->url){ gnome_url_show (dii->url); return; } 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 { if (is_exe (fe->buf.st_mode) && if_link_is_exe (fe)) my_system (EXECUTE_AS_SHELL, shell, filename); else gmc_open_filename (filename, NULL); } file_entry_free (fe); g_free (filename); } 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 (FALSE, 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: if (dii->url) gtk_selection_data_set (selection_data, selection_data->target, 8, dii->url, strlen (dii->url)); else { 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); } /* Callback used when a drag from the desktop is finished. We need to reload * the desktop. */ static void drag_end (GtkWidget *widget, GdkDragContext *context, gpointer data) { reload_desktop_icons (FALSE, 0, 0); } /* 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); gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->canvas), "drag_end", GTK_SIGNAL_FUNC (drag_end), 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; GtkWidget *source_widget; int is_desktop_icon; dii = data; filename = g_concat_dir_and_file (desktop_directory, dii->filename); fe = file_entry_from_file (filename); g_free (filename); if (!fe) return 0; /* eeek */ gdnd_find_panel_by_drag_context (context, &source_widget); is_desktop_icon = gdnd_drag_context_has_target (context, TARGET_MC_DESKTOP_ICON); action = gdnd_validate_action (context, TRUE, source_widget != NULL, source_widget && is_desktop_icon, fe, dii->selected); 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; /* 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. FIXME: handle auto-placement by reinserting the * icons in the proper place. */ if (!desktop_auto_placement) for (sl = sel_icons; sl; sl = sl->next) { dii = sl->data; desktop_icon_info_place (dii, dii->x + dx, dii->y + dy); } /* Clean up */ g_slist_free (sel_icons); } /* Handler for drag_data_received for desktop icons */ 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; if (gdnd_drag_context_has_target (context, TARGET_MC_DESKTOP_ICON) && dii->selected) drop_desktop_icons (context, data, x + dii->x, y + dii->y); else { char *full_name; file_entry *fe; full_name = g_concat_dir_and_file (desktop_directory, dii->filename); fe = file_entry_from_file (full_name); if (!fe) return; /* eeeek */ if (gdnd_perform_drop (context, data, fe, full_name)) reload_desktop_icons (FALSE, 0, 0); file_entry_free (fe); } } /* 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. It does not show the icon. */ static DesktopIconInfo * desktop_icon_info_new (char *filename, char *url, char *caption, int xpos, int ypos) { DesktopIconInfo *dii; file_entry *fe; char *full_name; GdkImlibImage *icon_im; GtkSignalFunc text_changed_func; /* Create the icon structure */ full_name = g_concat_dir_and_file (desktop_directory, filename); fe = file_entry_from_file (full_name); dii = g_new (DesktopIconInfo, 1); dii->x = 0; dii->y = 0; dii->slot = -1; if (url) { dii->url = g_strdup (url); if (!caption) caption = url; } else { dii->url = NULL; caption = filename; } icon_im = gicon_get_icon_for_file_speed (desktop_directory, fe, FALSE); dii->dicon = desktop_icon_new (icon_im, caption); 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 */ if (dii->url) text_changed_func = (GtkSignalFunc) text_changed_url; else text_changed_func = (GtkSignalFunc) text_changed; gtk_signal_connect (GTK_OBJECT (DESKTOP_ICON (dii->dicon)->text), "text_changed", text_changed_func, 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, xpos, ypos); return dii; } /** * desktop_icon_update_url: * @dii: the desktop icon * * Loads from the metadata updated versions of the caption * and the url */ void desktop_icon_update_url (DesktopIconInfo *dii) { char *fullname = g_concat_dir_and_file (desktop_directory, dii->filename); char *caption = NULL; char *url = NULL; int size; gnome_metadata_get (fullname, "icon-caption", &size, &caption); if (caption){ desktop_icon_set_text (DESKTOP_ICON (dii->dicon), caption); g_free (caption); } gnome_metadata_get (fullname, "desktop-url", &size, &url); if (url){ if (dii->url) g_free (dii->url); dii->url = url; } g_free (fullname); } /** * 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); gnome_metadata_delete (dii->filename); g_free (dii->url); 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; if (getenv ("GNOME_DESKTOP_DIR") != NULL) desktop_directory = g_strdup (getenv ("GNOME_DESKTOP_DIR")); else 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); */ } /* Property placed on target windows */ typedef struct { guint8 byte_order; guint8 protocol_version; guint8 protocol_style; guint8 pad; guint32 proxy_window; guint16 num_drop_sites; guint16 padding; guint32 total_size; } MotifDragReceiverInfo; /* 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; } /* Sets up a window as a Motif DnD proxy */ static void setup_motif_dnd_proxy (guint32 xid, GdkWindow *proxy_window) { guint32 proxy_xid; MotifDragReceiverInfo info; Atom receiver_info_atom; guint32 myint; myint = 0x01020304; proxy_xid = GDK_WINDOW_XWINDOW (proxy_window); receiver_info_atom = gdk_atom_intern ("_MOTIF_DRAG_RECEIVER_INFO", FALSE); info.byte_order = (*((gchar *) &myint) == 1) ? 'B' : 'l'; info.protocol_version = 0; info.protocol_style = 5; /* XmDRAG_DYNAMIC */ info.proxy_window = proxy_xid; info.num_drop_sites = 0; info.total_size = sizeof(info); XChangeProperty (gdk_display, xid, receiver_info_atom, receiver_info_atom, 8, PropModeReplace, (guchar *)&info, sizeof (info)); } /* 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; int is_desktop_icon; gdnd_find_panel_by_drag_context (context, &source_widget); is_desktop_icon = gdnd_drag_context_has_target (context, TARGET_MC_DESKTOP_ICON); action = gdnd_validate_action (context, TRUE, source_widget != NULL, source_widget && is_desktop_icon, NULL, FALSE); 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) { gint dx, dy; /* Fix the proxy window offsets */ gdk_window_get_position (widget->window, &dx, &dy); x += dx; y += dy; if (gdnd_drag_context_has_target (context, TARGET_MC_DESKTOP_ICON)) drop_desktop_icons (context, data, x, y); else { file_entry *desktop_fe; desktop_fe = file_entry_from_file (desktop_directory); if (!desktop_fe) return; /* eeek */ if (gdnd_perform_drop (context, data, desktop_fe, desktop_directory)) reload_desktop_icons (TRUE, x, y); file_entry_free (desktop_fe); } } /* Sets up drag and drop to the desktop root window */ static void setup_desktop_dnd (void) { if (!setup_xdnd_proxy (GDK_ROOT_WINDOW (), proxy_invisible->window)) g_warning ("There is already a process taking drops on the desktop!\n"); setup_motif_dnd_proxy (GDK_ROOT_WINDOW (), proxy_invisible->window); gtk_drag_dest_set (proxy_invisible, 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 (proxy_invisible), "drag_motion", (GtkSignalFunc) desktop_drag_motion, NULL); gtk_signal_connect (GTK_OBJECT (proxy_invisible), "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; } /* Callback for arranging the icons on the desktop */ static void handle_arrange_icons (GtkWidget *widget, gpointer data) { arrange_desktop_icons (); } /* Callback for rescanning the desktop directory */ static void handle_rescan_desktop (GtkWidget *widget, gpointer data) { reload_desktop_icons (FALSE, 0, 0); } /* The popup menu for the desktop */ static GnomeUIInfo desktop_popup_items[] = { GNOMEUIINFO_ITEM_NONE (N_("Arrange Icons"), NULL, handle_arrange_icons), GNOMEUIINFO_ITEM_NONE (N_("Rescan Desktop"), NULL, handle_rescan_desktop), GNOMEUIINFO_END }; /* Executes the popup menu for the desktop */ static void desktop_popup (GdkEventButton *event) { GtkWidget *popup; popup = gnome_popup_menu_new (desktop_popup_items); gnome_popup_menu_do_popup_modal (popup, NULL, NULL, event, NULL); gtk_widget_destroy (popup); } /* 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); 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 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, 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; /* Make the root window send events to the invisible proxy widget */ gdk_window_set_user_data (GDK_ROOT_PARENT (), 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 (proxy_invisible), "button_press_event", (GtkSignalFunc) click_proxy_button_press, NULL); gtk_signal_connect (GTK_OBJECT (proxy_invisible), "button_release_event", (GtkSignalFunc) click_proxy_button_release, NULL); gtk_signal_connect (GTK_OBJECT (proxy_invisible), "motion_notify_event", (GtkSignalFunc) click_proxy_motion, NULL); gtk_signal_connect (GTK_OBJECT (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 (); gicon_init (); create_layout_info (); create_desktop_dir (); reload_desktop_icons (FALSE, 0, 0); /* Create the proxy window and initialize all proxying stuff */ proxy_invisible = gtk_invisible_new (); gtk_widget_show (proxy_invisible); 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 click-on-desktop crap */ gdk_window_unref (click_proxy_gdk_window); /* Remove DnD crap */ gtk_widget_destroy (proxy_invisible); XDeleteProperty (GDK_DISPLAY (), GDK_ROOT_WINDOW (), gdk_atom_intern ("XdndProxy", FALSE)); }