/* * desktop.c * * Copyright 2010 Hong Jen Yee (PCMan) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "desktop.h" #include "pcmanfm.h" #include "app-config.h" #include #include #include #include #include #include "pref.h" #include "main-win.h" #include "gseal-gtk-compat.h" #define SPACING 2 #define PADDING 6 #define MARGIN 2 struct _FmDesktopItem { GtkTreeIter it; FmFileInfo* fi; GdkPixbuf* icon; int x; /* position of the item on the desktop */ int y; GdkRectangle icon_rect; GdkRectangle text_rect; gboolean is_special : 1; /* is this a special item like "My Computer", mounted volume, or "Trash" */ gboolean is_mount : 1; /* is this a mounted volume*/ gboolean is_selected : 1; gboolean is_prelight : 1; gboolean custom_pos : 1; }; /* static void fm_desktop_finalize (GObject *object); */ static void fm_desktop_destroy (GtkObject *object); static FmDesktopItem* hit_test(FmDesktop* self, int x, int y); static FmDesktopItem* get_nearest_item(FmDesktop* desktop, FmDesktopItem* item, GtkDirectionType dir); static void calc_item_size(FmDesktop* desktop, FmDesktopItem* item); static void layout_items(FmDesktop* self); static void queue_layout_items(FmDesktop* desktop); static void paint_item(FmDesktop* self, FmDesktopItem* item, cairo_t* cr, GdkRectangle* expose_area); static void redraw_item(FmDesktop* desktop, FmDesktopItem* item); static void calc_rubber_banding_rect(FmDesktop* self, int x, int y, GdkRectangle* rect); static void update_rubberbanding(FmDesktop* self, int newx, int newy ); static void paint_rubber_banding_rect(FmDesktop* self, cairo_t* cr, GdkRectangle* expose_area); static void update_background(FmDesktop* desktop); static void update_working_area(FmDesktop* desktop); static GList* get_selected_items(FmDesktop* desktop, int* n_items); static void activate_selected_items(FmDesktop* desktop); static void set_focused_item(FmDesktop* desktop, FmDesktopItem* item); static void select_all(FmDesktop* desktop); static void deselect_all(FmDesktop* desktop); static FmDesktopItem* desktop_item_new(GtkTreeIter* it); static void desktop_item_free(FmDesktopItem* item); static gboolean on_expose( GtkWidget* w, GdkEventExpose* evt ); static void on_size_allocate( GtkWidget* w, GtkAllocation* alloc ); static void on_size_request( GtkWidget* w, GtkRequisition* req ); static gboolean on_button_press( GtkWidget* w, GdkEventButton* evt ); static gboolean on_button_release( GtkWidget* w, GdkEventButton* evt ); static gboolean on_motion_notify( GtkWidget* w, GdkEventMotion* evt ); static gboolean on_leave_notify( GtkWidget* w, GdkEventCrossing* evt ); static gboolean on_key_press( GtkWidget* w, GdkEventKey* evt ); static void on_style_set( GtkWidget* w, GtkStyle* prev ); static void on_direction_changed( GtkWidget* w, GtkTextDirection prev ); static void on_realize( GtkWidget* w ); static gboolean on_focus_in( GtkWidget* w, GdkEventFocus* evt ); static gboolean on_focus_out( GtkWidget* w, GdkEventFocus* evt ); static void on_drag_leave(GtkWidget* w, GdkDragContext* drag_ctx, guint time); static void on_wallpaper_changed(FmConfig* cfg, gpointer user_data); static void on_desktop_text_changed(FmConfig* cfg, gpointer user_data); static void on_desktop_font_changed(FmConfig* cfg, gpointer user_data); static void on_big_icon_size_changed(FmConfig* cfg, gpointer user_data); static void on_icon_theme_changed(GtkIconTheme* theme, gpointer user_data); static void on_row_inserted(GtkTreeModel* mod, GtkTreePath* tp, GtkTreeIter* it, FmDesktop* desktop); static void on_row_deleted(GtkTreeModel* mod, GtkTreePath* tp, FmDesktop* desktop); static void on_row_changed(GtkTreeModel* mod, GtkTreePath* tp, GtkTreeIter* it, FmDesktop* desktop); static void on_rows_reordered(GtkTreeModel* mod, GtkTreePath* parent_tp, GtkTreeIter* parent_it, gpointer arg3, FmDesktop* desktop); static void on_dnd_src_data_get(FmDndSrc* ds, FmDesktop* desktop); static gboolean on_dnd_dest_query_info(FmDndDest* dd, int x, int y, GdkDragAction* action, FmDesktop* desktop); static void on_dnd_dest_files_dropped(FmDndDest* dd, GdkDragAction action, int info_type, FmList* files, FmDesktop* desktop); static GdkFilterReturn on_root_event(GdkXEvent *xevent, GdkEvent *event, gpointer data); static void on_screen_size_changed(GdkScreen* screen, FmDesktop* desktop); /* popup menus */ static void on_paste(GtkAction* act, gpointer user_data); static void on_select_all(GtkAction* act, gpointer user_data); static void on_invert_select(GtkAction* act, gpointer user_data); static void on_create_new(GtkAction* act, gpointer user_data); static void on_sort_type(GtkAction* act, GtkRadioAction *cur, gpointer user_data); static void on_sort_by(GtkAction* act, GtkRadioAction *cur, gpointer user_data); static void on_open_in_new_tab(GtkAction* act, gpointer user_data); static void on_open_in_new_win(GtkAction* act, gpointer user_data); static void on_open_folder_in_terminal(GtkAction* act, gpointer user_data); /* for desktop menu provided by window manager */ static void forward_event_to_rootwin( GdkScreen *gscreen, GdkEvent *event ); G_DEFINE_TYPE(FmDesktop, fm_desktop, GTK_TYPE_WINDOW); static GtkWindowGroup* win_group = NULL; static GtkWidget **desktops = NULL; static gint n_screens = 0; static guint wallpaper_changed = 0; static guint desktop_text_changed = 0; static guint desktop_font_changed = 0; static guint big_icon_size_changed = 0; static guint icon_theme_changed = 0; static GtkAccelGroup* acc_grp = NULL; static PangoFontDescription* font_desc = NULL; static FmFolderModel* model = NULL; static Atom XA_NET_WORKAREA = 0; static Atom XA_NET_NUMBER_OF_DESKTOPS = 0; static Atom XA_NET_CURRENT_DESKTOP = 0; static Atom XA_XROOTMAP_ID= 0; static int desktop_sort_by = COL_FILE_MTIME; static int desktop_sort_type = GTK_SORT_ASCENDING; static GdkCursor* hand_cursor = NULL; enum { FM_DND_DEST_DESKTOP_ITEM = N_FM_DND_DEST_DEFAULT_TARGETS + 1 }; GtkTargetEntry dnd_targets[] = { {"application/x-desktop-item", GTK_TARGET_SAME_WIDGET, FM_DND_DEST_DESKTOP_ITEM} }; static GtkWidget* desktop_popup = NULL; /* insert GtkUIManager XML definitions */ #include "desktop-ui.c" static void fm_desktop_class_init(FmDesktopClass *klass) { GtkObjectClass *gtk_object_class; GtkWidgetClass* wc; typedef gboolean (*DeleteEvtHandler) (GtkWidget*, GdkEvent*); const char* atom_names[] = {"_NET_WORKAREA", "_NET_NUMBER_OF_DESKTOPS", "_NET_CURRENT_DESKTOP", "_XROOTMAP_ID"}; Atom atoms[G_N_ELEMENTS(atom_names)] = {0}; /* g_object_class = G_OBJECT_CLASS(klass); g_object_class->finalize = fm_desktop_finalize; */ gtk_object_class = GTK_OBJECT_CLASS(klass); gtk_object_class->destroy = fm_desktop_destroy; wc = GTK_WIDGET_CLASS(klass); wc->expose_event = on_expose; wc->size_allocate = on_size_allocate; wc->size_request = on_size_request; wc->button_press_event = on_button_press; wc->button_release_event = on_button_release; wc->motion_notify_event = on_motion_notify; wc->leave_notify_event = on_leave_notify; wc->key_press_event = on_key_press; wc->style_set = on_style_set; wc->direction_changed = on_direction_changed; wc->realize = on_realize; wc->focus_in_event = on_focus_in; wc->focus_out_event = on_focus_out; /* wc->scroll_event = on_scroll; */ wc->delete_event = (DeleteEvtHandler)gtk_true; wc->drag_leave = on_drag_leave; if(XInternAtoms(GDK_DISPLAY(), atom_names, G_N_ELEMENTS(atom_names), False, atoms)) { XA_NET_WORKAREA = atoms[0]; XA_NET_NUMBER_OF_DESKTOPS = atoms[1]; XA_NET_CURRENT_DESKTOP = atoms[2]; XA_XROOTMAP_ID= atoms[3]; } } static void desktop_item_free(FmDesktopItem* item) { if(item->icon) g_object_unref(item->icon); g_slice_free(FmDesktopItem, item); } static void fm_desktop_destroy(GtkObject *object) { FmDesktop *self; GdkScreen* screen; self = FM_DESKTOP(object); screen = gtk_widget_get_screen((GtkWidget*)self); gdk_window_remove_filter(gdk_screen_get_root_window(screen), on_root_event, self); g_list_foreach(self->items, (GFunc)desktop_item_free, NULL); g_list_free(self->items); self->items = NULL; g_object_unref(self->icon_render); g_object_unref(self->pl); g_signal_handlers_disconnect_by_func(model, on_row_inserted, self); g_signal_handlers_disconnect_by_func(model, on_row_deleted, self); g_signal_handlers_disconnect_by_func(model, on_row_changed, self); g_signal_handlers_disconnect_by_func(model, on_rows_reordered, self); if(self->single_click_timeout_handler) g_source_remove(self->single_click_timeout_handler); if(self->idle_layout) g_source_remove(self->idle_layout); G_OBJECT_CLASS(fm_desktop_parent_class)->dispose(object); } static void fm_desktop_init(FmDesktop *self) { GdkScreen* screen = gtk_widget_get_screen((GtkWidget*)self); GdkWindow* root; PangoContext* pc; GtkTreeIter it; GtkTargetList* targets; gtk_window_set_default_size((GtkWindow*)self, gdk_screen_get_width(screen), gdk_screen_get_height(screen)); gtk_window_move(GTK_WINDOW(self), 0, 0); gtk_widget_set_app_paintable((GtkWidget*)self, TRUE); gtk_window_set_type_hint(GTK_WINDOW(self), GDK_WINDOW_TYPE_HINT_DESKTOP); gtk_widget_add_events((GtkWidget*)self, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK| GDK_PROPERTY_CHANGE_MASK); self->icon_render = fm_cell_renderer_pixbuf_new(); g_object_set( self->icon_render, "follow-state", TRUE, NULL); g_object_ref_sink(self->icon_render); fm_cell_renderer_pixbuf_set_fixed_size(FM_CELL_RENDERER_PIXBUF(self->icon_render), fm_config->big_icon_size, fm_config->big_icon_size); /* FIXME: call pango_layout_context_changed() on the layout in response to the * "style-set" and "direction-changed" signals for the widget. */ pc = gtk_widget_get_pango_context( (GtkWidget*)self ); self->pl = gtk_widget_create_pango_layout( (GtkWidget*)self, NULL ); pango_layout_set_alignment( self->pl, PANGO_ALIGN_CENTER ); pango_layout_set_ellipsize( self->pl, PANGO_ELLIPSIZE_END ); pango_layout_set_wrap(self->pl, PANGO_WRAP_WORD_CHAR); g_signal_connect(model, "row-inserted", G_CALLBACK(on_row_inserted), self); g_signal_connect(model, "row-deleted", G_CALLBACK(on_row_deleted), self); g_signal_connect(model, "row-changed", G_CALLBACK(on_row_changed), self); g_signal_connect(model, "rows-reordered", G_CALLBACK(on_rows_reordered), self); root = gdk_screen_get_root_window(screen); gdk_window_set_events(root, gdk_window_get_events(root)|GDK_PROPERTY_CHANGE_MASK); gdk_window_add_filter(root, on_root_event, self); g_signal_connect(screen, "size-changed", G_CALLBACK(on_screen_size_changed), self); /* init dnd support */ gtk_drag_source_set(self, 0, fm_default_dnd_dest_targets, N_FM_DND_DEST_DEFAULT_TARGETS, GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK|GDK_ACTION_ASK); targets = gtk_drag_source_get_target_list((GtkWidget*)self); /* add our own targets */ gtk_target_list_add_table(targets, dnd_targets, G_N_ELEMENTS(dnd_targets)); self->dnd_src = fm_dnd_src_new((GtkWidget*)self); g_signal_connect(self->dnd_src, "data-get", G_CALLBACK(on_dnd_src_data_get), self); gtk_drag_dest_set(self, 0, NULL, 0, GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK|GDK_ACTION_ASK); gtk_drag_dest_set_target_list(GTK_WIDGET(self), targets); self->dnd_dest = fm_dnd_dest_new((GtkWidget*)self); g_signal_connect(self->dnd_dest, "query-info", G_CALLBACK(on_dnd_dest_query_info), self); g_signal_connect(self->dnd_dest, "files_dropped", G_CALLBACK(on_dnd_dest_files_dropped), self); /* add items */ if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &it)) { do{ FmDesktopItem* item = desktop_item_new(&it); self->items = g_list_prepend(self->items, item); }while(gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &it)); self->items = g_list_reverse(self->items); } } GtkWidget *fm_desktop_new(void) { return (GtkWidget*)g_object_new(FM_TYPE_DESKTOP, NULL); } void fm_desktop_manager_init() { GdkDisplay * gdpy; gint i; GtkUIManager* ui; GtkActionGroup* act_grp; if( ! win_group ) win_group = gtk_window_group_new(); if(!model) { FmFolder* folder = fm_folder_get_for_path(fm_path_get_desktop()); if(folder) { model = fm_folder_model_new(folder, FALSE); if(model) fm_folder_model_set_icon_size(model, fm_config->big_icon_size); } } if(app_config->desktop_font) font_desc = pango_font_description_from_string(app_config->desktop_font); gdpy = gdk_display_get_default(); n_screens = gdk_display_get_n_screens(gdpy); desktops = g_new(GtkWidget*, n_screens); for( i = 0; i < n_screens; i++ ) { GtkWidget* desktop = fm_desktop_new(); desktops[i] = desktop; gtk_widget_realize(desktop); /* without this, setting wallpaper won't work */ gtk_widget_show_all(desktop); gdk_window_lower(gtk_widget_get_window(desktop)); gtk_window_group_add_window( GTK_WINDOW_GROUP(win_group), desktop ); } wallpaper_changed = g_signal_connect(app_config, "changed::wallpaper", G_CALLBACK(on_wallpaper_changed), NULL); desktop_text_changed = g_signal_connect(app_config, "changed::desktop_text", G_CALLBACK(on_desktop_text_changed), NULL); desktop_font_changed = g_signal_connect(app_config, "changed::desktop_font", G_CALLBACK(on_desktop_font_changed), NULL); big_icon_size_changed = g_signal_connect(app_config, "changed::big_icon_size", G_CALLBACK(on_big_icon_size_changed), NULL); icon_theme_changed = g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(on_icon_theme_changed), NULL); /* popup menu */ ui = gtk_ui_manager_new(); act_grp = gtk_action_group_new("Desktop"); gtk_action_group_set_translation_domain(act_grp, NULL); gtk_action_group_add_actions(act_grp, desktop_actions, G_N_ELEMENTS(desktop_actions), NULL); gtk_action_group_add_radio_actions(act_grp, desktop_sort_type_actions, G_N_ELEMENTS(desktop_sort_type_actions), GTK_SORT_ASCENDING, on_sort_type, NULL); gtk_action_group_add_radio_actions(act_grp, desktop_sort_by_actions, G_N_ELEMENTS(desktop_sort_by_actions), 0, on_sort_by, NULL); gtk_ui_manager_insert_action_group(ui, act_grp, 0); gtk_ui_manager_add_ui_from_string(ui, desktop_menu_xml, -1, NULL); acc_grp = gtk_ui_manager_get_accel_group(ui); for( i = 0; i < n_screens; i++ ) gtk_window_add_accel_group(GTK_WINDOW(desktops[i]), acc_grp); desktop_popup = (GtkWidget*)g_object_ref(gtk_ui_manager_get_widget(ui, "/popup")); hand_cursor = gdk_cursor_new(GDK_HAND2); g_object_unref(act_grp); g_object_unref(ui); pcmanfm_ref(); } void fm_desktop_manager_finalize() { int i; for( i = 0; i < n_screens; i++ ) { gtk_widget_destroy(desktops[i]); } g_free(desktops); g_object_unref(win_group); win_group = NULL; if(model) { g_object_unref(model); model = NULL; } if(font_desc) { pango_font_description_free(font_desc); font_desc = NULL; } g_signal_handler_disconnect(app_config, wallpaper_changed); g_signal_handler_disconnect(app_config, desktop_text_changed); g_signal_handler_disconnect(app_config, desktop_font_changed); g_signal_handler_disconnect(app_config, big_icon_size_changed); g_signal_handler_disconnect(gtk_icon_theme_get_default(), icon_theme_changed); gtk_widget_destroy(desktop_popup); desktop_popup = NULL; g_object_unref(acc_grp); acc_grp = NULL; if(hand_cursor) { gdk_cursor_unref(hand_cursor); hand_cursor = NULL; } pcmanfm_unref(); } void activate_selected_items(FmDesktop* desktop) { int n_sels; GList* items = get_selected_items(desktop, &n_sels); GList* l; if(!items) return; for(l=items;l;l=l->next) { FmDesktopItem* item = (FmDesktopItem*)l->data; l->data = item->fi; } fm_launch_files_simple(GTK_WINDOW(desktop), NULL, items, pcmanfm_open_folder, NULL); g_list_free(items); } void set_focused_item(FmDesktop* desktop, FmDesktopItem* item) { if(item != desktop->focus) { FmDesktopItem* old_focus = desktop->focus; desktop->focus = item; if(old_focus) redraw_item(desktop, old_focus); if(item) redraw_item(desktop, item); } } static void select_all(FmDesktop* desktop) { GList* l; for(l=desktop->items;l;l=l->next) { FmDesktopItem* item = (FmDesktopItem*)l->data; item->is_selected = TRUE; redraw_item(desktop, item); } } static void deselect_all(FmDesktop* desktop) { GList* l; for( l = desktop->items; l ;l = l->next ) { FmDesktopItem* item = (FmDesktopItem*) l->data; if( item->is_selected ) { item->is_selected = FALSE; redraw_item( desktop, item ); } } } gboolean on_button_press( GtkWidget* w, GdkEventButton* evt ) { FmDesktop* self = (FmDesktop*)w; FmDesktopItem *item = NULL, *clicked_item = NULL; clicked_item = hit_test( FM_DESKTOP(w), (int)evt->x, (int)evt->y ); if( evt->type == GDK_BUTTON_PRESS ) { if( evt->button == 1 ) /* left button */ { self->button_pressed = TRUE; /* store button state for drag & drop */ self->drag_start_x = evt->x; self->drag_start_y = evt->y; } /* if ctrl / shift is not pressed, deselect all. */ if( evt->button != 3 && ! (evt->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ) { /* don't cancel selection if clicking on selected items */ if( !( (evt->button == 1 || evt->button == 3) && clicked_item && clicked_item->is_selected) ) deselect_all(self); } if( clicked_item ) { if( evt->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK) ) clicked_item->is_selected = ! clicked_item->is_selected; else clicked_item->is_selected = TRUE; if( self->focus && self->focus != item ) { FmDesktopItem* old_focus = self->focus; self->focus = NULL; if( old_focus ) redraw_item( FM_DESKTOP(w), old_focus ); } self->focus = clicked_item; redraw_item( self, clicked_item ); if( evt->button == 3 ) /* right click, context menu */ { FmFileMenu* menu; GtkMenu* popup; FmFileInfo* fi; FmFileInfoList* files; /* int n_sels; GList* items = get_selected_items(self, &n_sels); if( items ) { GList* sel; } */ files = fm_desktop_get_selected_files(self); fi = (FmFileInfo*)fm_list_peek_head(files); menu = fm_file_menu_new_for_files(files, fm_path_get_desktop(), TRUE); fm_file_menu_set_folder_func(menu, pcmanfm_open_folder, NULL); fm_list_unref(files); /* merge some specific menu items for folders */ if(fm_file_menu_is_single_file_type(menu) && fm_file_info_is_dir(fi)) { GtkUIManager* ui = fm_file_menu_get_ui(menu); GtkActionGroup* act_grp = fm_file_menu_get_action_group(menu); gtk_action_group_add_actions(act_grp, folder_menu_actions, G_N_ELEMENTS(folder_menu_actions), menu); gtk_ui_manager_add_ui_from_string(ui, folder_menu_xml, -1, NULL); } popup = fm_file_menu_get_menu(menu); gtk_menu_popup(popup, NULL, NULL, NULL, fi, 3, gtk_get_current_event_time()); } goto out; } else /* no item is clicked */ { if( evt->button == 3 ) /* right click on the blank area => desktop popup menu */ { if(! app_config->show_wm_menu) gtk_menu_popup(GTK_MENU(desktop_popup), NULL, NULL, NULL, NULL, 3, gtk_get_current_event_time()); } else if( evt->button == 1 ) { self->rubber_bending = TRUE; /* FIXME: if you foward the event here, this will break rubber bending... */ /* forward the event to root window */ /* forward_event_to_rootwin( gtk_widget_get_screen(w), evt ); */ gtk_grab_add( w ); self->rubber_bending_x = evt->x; self->rubber_bending_y = evt->y; goto out; } } } else if( evt->type == GDK_2BUTTON_PRESS ) /* activate items */ { if( clicked_item && evt->button == 1) /* left double click */ { fm_launch_file_simple(GTK_WINDOW(w), NULL, clicked_item->fi, pcmanfm_open_folder, NULL); goto out; } } /* forward the event to root window */ forward_event_to_rootwin( gtk_widget_get_screen(w), evt ); out: if( ! gtk_widget_has_focus(w) ) { /* g_debug( "we don't have the focus, grab it!" ); */ gtk_widget_grab_focus( w ); } return TRUE; } gboolean on_button_release( GtkWidget* w, GdkEventButton* evt ) { FmDesktop* self = (FmDesktop*)w; FmDesktopItem* clicked_item = hit_test( self, evt->x, evt->y ); self->button_pressed = FALSE; if( self->rubber_bending ) { update_rubberbanding( self, evt->x, evt->y ); gtk_grab_remove( w ); self->rubber_bending = FALSE; } else if( self->dragging ) { self->dragging = FALSE; } else if( fm_config->single_click && evt->button == 1 ) { if( clicked_item ) { /* left single click */ fm_launch_file_simple(GTK_WINDOW(w), NULL, clicked_item->fi, pcmanfm_open_folder, NULL); return TRUE; } } /* forward the event to root window */ if( ! clicked_item ) forward_event_to_rootwin( gtk_widget_get_screen(w), evt ); return TRUE; } static gboolean on_single_click_timeout( FmDesktop* self ) { GtkWidget* w = (GtkWidget*)self; GdkEventButton evt; GdkWindow* window; int x, y; window = gtk_widget_get_window(w); /* generate a fake button press */ /* FIXME: will this cause any problem? */ evt.type = GDK_BUTTON_PRESS; evt.window = window; gdk_window_get_pointer( window, &x, &y, &evt.state ); evt.x = x; evt.y = y; evt.state |= GDK_BUTTON_PRESS_MASK; evt.state &= ~GDK_BUTTON_MOTION_MASK; on_button_press( GTK_WIDGET(self), &evt ); evt.type = GDK_BUTTON_RELEASE; evt.state &= ~GDK_BUTTON_PRESS_MASK; evt.state |= ~GDK_BUTTON_RELEASE_MASK; on_button_release( GTK_WIDGET(self), &evt ); self->single_click_timeout_handler = 0; return FALSE; } gboolean on_motion_notify( GtkWidget* w, GdkEventMotion* evt ) { FmDesktop* self = (FmDesktop*)w; if( ! self->button_pressed ) { if( fm_config->single_click ) { FmDesktopItem* item = hit_test( self, evt->x, evt->y ); GdkWindow* window = gtk_widget_get_window(w); if( item != self->hover_item ) { if( 0 != self->single_click_timeout_handler ) { g_source_remove( self->single_click_timeout_handler ); self->single_click_timeout_handler = 0; } } if( item ) { gdk_window_set_cursor( window, hand_cursor ); /* FIXME: timeout should be customizable */ if( self->single_click_timeout_handler == 0) self->single_click_timeout_handler = g_timeout_add( 400, on_single_click_timeout, self ); //400 ms /* Making a loop to aviod the selection of the item */ /* on_single_click_timeout( self ); */ } else { gdk_window_set_cursor( window, NULL ); } self->hover_item = item; } return TRUE; } if( self->dragging ) { } else if( self->rubber_bending ) { update_rubberbanding( self, evt->x, evt->y ); } else { if ( gtk_drag_check_threshold( w, self->drag_start_x, self->drag_start_y, evt->x, evt->y)) { FmFileInfoList* files = fm_desktop_get_selected_files(self); GtkTargetList* target_list; if(files) { self->dragging = TRUE; target_list = gtk_drag_source_get_target_list(w); gtk_drag_begin( w, target_list, GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK, 1, evt ); } } } return TRUE; } gboolean on_leave_notify( GtkWidget* w, GdkEventCrossing *evt ) { FmDesktop* self = (FmDesktop*)w; if(self->single_click_timeout_handler) { g_source_remove(self->single_click_timeout_handler); self->single_click_timeout_handler = 0; } return TRUE; } gboolean on_key_press( GtkWidget* w, GdkEventKey* evt ) { FmDesktop* desktop = (FmDesktop*)w; FmDesktopItem* item; int modifier = ( evt->state & ( GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK ) ); FmPathList* sels; switch ( evt->keyval ) { case GDK_Left: item = get_nearest_item(desktop, desktop->focus, GTK_DIR_LEFT); if(item) { if(0 == modifier) { deselect_all(desktop); item->is_selected = TRUE; } set_focused_item(desktop, item); } return TRUE; break; case GDK_Right: item = get_nearest_item(desktop, desktop->focus, GTK_DIR_RIGHT); if(item) { if(0 == modifier) { deselect_all(desktop); item->is_selected = TRUE; } set_focused_item(desktop, item); } return TRUE; break; case GDK_Up: item = get_nearest_item(desktop, desktop->focus, GTK_DIR_UP); if(item) { if(0 == modifier) { deselect_all(desktop); item->is_selected = TRUE; } set_focused_item(desktop, item); } return TRUE; break; case GDK_Down: item = get_nearest_item(desktop, desktop->focus, GTK_DIR_DOWN); if(item) { if(0 == modifier) { deselect_all(desktop); item->is_selected = TRUE; } set_focused_item(desktop, item); } return TRUE; break; case GDK_space: if(modifier & GDK_CONTROL_MASK) { if(desktop->focus) { desktop->focus->is_selected = !desktop->focus->is_selected; redraw_item(desktop, desktop->focus); } } else activate_selected_items(desktop); return TRUE; break; case GDK_Return: if(modifier & GDK_MOD1_MASK) { FmFileInfoList* infos = fm_desktop_get_selected_files(desktop); if(infos) { fm_show_file_properties(infos); fm_list_unref(infos); return TRUE; } } else { activate_selected_items(desktop); return TRUE; } break; case GDK_x: if(modifier & GDK_CONTROL_MASK) { sels = fm_desktop_get_selected_paths(desktop); fm_clipboard_cut_files(desktop, sels); fm_list_unref(sels); } break; case GDK_c: if(modifier & GDK_CONTROL_MASK) { sels = fm_desktop_get_selected_paths(desktop); fm_clipboard_copy_files(desktop, sels); fm_list_unref(sels); } break; case GDK_v: if(modifier & GDK_CONTROL_MASK) fm_clipboard_paste_files(GTK_WIDGET(desktop), fm_path_get_desktop()); break; case GDK_F2: sels = fm_desktop_get_selected_paths(desktop); if(sels) { fm_rename_file(fm_list_peek_head(sels)); fm_list_unref(sels); } break; case GDK_Delete: sels = fm_desktop_get_selected_paths(desktop); if(sels) { if(modifier & GDK_SHIFT_MASK) fm_delete_files(sels); else fm_trash_or_delete_files(sels); fm_list_unref(sels); } break; } return GTK_WIDGET_CLASS(fm_desktop_parent_class)->key_press_event(w, evt); } void on_style_set( GtkWidget* w, GtkStyle* prev ) { FmDesktop* self = (FmDesktop*)w; PangoContext* pc = gtk_widget_get_pango_context(w); if(font_desc) pango_context_set_font_description(pc, font_desc); pango_layout_context_changed(self->pl); } void on_direction_changed( GtkWidget* w, GtkTextDirection prev ) { FmDesktop* self = (FmDesktop*)w; PangoContext* pc = gtk_widget_get_pango_context(w); pango_layout_context_changed(self->pl); queue_layout_items(self); } void on_realize( GtkWidget* w ) { FmDesktop* self = (FmDesktop*)w; GTK_WIDGET_CLASS(fm_desktop_parent_class)->realize( w ); gtk_window_set_skip_pager_hint( GTK_WINDOW(w), TRUE ); gtk_window_set_skip_taskbar_hint( GTK_WINDOW(w), TRUE ); gtk_window_set_resizable( (GtkWindow*)w, FALSE ); if( ! self->gc ) self->gc = gdk_gc_new( gtk_widget_get_window(w) ); update_background(self); } gboolean on_focus_in( GtkWidget* w, GdkEventFocus* evt ) { FmDesktop* self = (FmDesktop*) w; GTK_WIDGET_SET_FLAGS( w, GTK_HAS_FOCUS ); if( !self->focus && self->items) self->focus = (FmDesktopItem*)self->items->data; if( self->focus ) redraw_item( self, self->focus ); return FALSE; } gboolean on_focus_out( GtkWidget* w, GdkEventFocus* evt ) { FmDesktop* self = (FmDesktop*) w; if( self->focus ) { GTK_WIDGET_UNSET_FLAGS( w, GTK_HAS_FOCUS ); redraw_item( self, self->focus ); } return FALSE; } void on_drag_leave(GtkWidget* w, GdkDragContext* drag_ctx, guint time) { FmDesktop* desktop = (FmDesktop*)w; if(desktop->drop_hilight) { FmDesktopItem* old_drop = desktop->drop_hilight; desktop->drop_hilight = NULL; redraw_item(desktop, old_drop); } } gboolean on_expose( GtkWidget* w, GdkEventExpose* evt ) { FmDesktop* self = (FmDesktop*)w; GList* l; cairo_t* cr; if( G_UNLIKELY( ! gtk_widget_get_visible (w) || ! gtk_widget_get_mapped (w) ) ) return TRUE; cr = gdk_cairo_create( gtk_widget_get_window(w) ); if( self->rubber_bending ) paint_rubber_banding_rect( self, cr, &evt->area ); for( l = self->items; l; l = l->next ) { FmDesktopItem* item = (FmDesktopItem*)l->data; GdkRectangle* intersect, tmp, tmp2; if(gdk_rectangle_intersect( &evt->area, &item->icon_rect, &tmp )) intersect = &tmp; else intersect = NULL; if(gdk_rectangle_intersect( &evt->area, &item->text_rect, &tmp2 )) { if(intersect) gdk_rectangle_union(intersect, &tmp2, intersect); else intersect = &tmp2; } if(intersect) paint_item( self, item, cr, intersect ); } cairo_destroy(cr); return TRUE; } void on_size_allocate( GtkWidget* w, GtkAllocation* alloc ) { FmDesktop* self = (FmDesktop*)w; /* calculate item size */ PangoContext* pc; PangoFontMetrics *metrics; int font_h; pc = gtk_widget_get_pango_context( (GtkWidget*)self ); metrics = pango_context_get_metrics( pc, NULL, NULL); font_h = pango_font_metrics_get_ascent(metrics) + pango_font_metrics_get_descent (metrics); font_h /= PANGO_SCALE; self->spacing = SPACING; self->xpad = self->ypad = PADDING; self->xmargin = self->ymargin = MARGIN; self->text_h = font_h * 2; self->text_w = 100; self->pango_text_h = self->text_h * PANGO_SCALE; self->pango_text_w = self->text_w * PANGO_SCALE; self->text_h += 4; self->text_w += 4; /* 4 is for drawing border */ self->cell_h = fm_config->big_icon_size + self->spacing + self->text_h + self->ypad * 2; self->cell_w = MAX(self->text_w, fm_config->big_icon_size) + self->xpad * 2; update_working_area(self); /* queue_layout_items(self); this is called in update_working_area */ GTK_WIDGET_CLASS(fm_desktop_parent_class)->size_allocate( w, alloc ); } void on_size_request( GtkWidget* w, GtkRequisition* req ) { GdkScreen* scr = gtk_widget_get_screen( w ); req->width = gdk_screen_get_width( scr ); req->height = gdk_screen_get_height( scr ); } static gboolean is_point_in_rect( GdkRectangle* rect, int x, int y ) { return rect->x < x && x < (rect->x + rect->width) && y > rect->y && y < (rect->y + rect->height); } FmDesktopItem* hit_test(FmDesktop* self, int x, int y) { FmDesktopItem* item; GList* l; for( l = self->items; l; l = l->next ) { item = (FmDesktopItem*) l->data; if( is_point_in_rect( &item->icon_rect, x, y ) || is_point_in_rect( &item->text_rect, x, y ) ) return item; } return NULL; } FmDesktopItem* get_nearest_item(FmDesktop* desktop, FmDesktopItem* item, GtkDirectionType dir) { GList* l; FmDesktopItem* item2, *ret = NULL; guint min_x_dist, min_y_dist; if(!desktop->items || !desktop->items->next) return NULL; min_x_dist = min_y_dist = (guint)-1; item2 = NULL; switch(dir) { case GTK_DIR_LEFT: for( l = desktop->items; l; l = l->next ) { int dist; item2 = (FmDesktopItem*) l->data; if(item2->x >= item->x) continue; dist = item->x - item2->x; if(dist < min_x_dist) { ret = item2; min_x_dist = dist; min_y_dist = ABS(item->y - item2->y); } else if(dist == min_x_dist && item2 != ret) /* if there is another item of the same x distance */ { /* get the one with smaller y distance */ dist = ABS(item2->y - item->y); if(dist < min_y_dist) { ret = item2; min_y_dist = dist; } } } break; case GTK_DIR_RIGHT: for( l = desktop->items; l; l = l->next ) { int dist; item2 = (FmDesktopItem*) l->data; if(item2->x <= item->x) continue; dist = item2->x - item->x; if(dist < min_x_dist) { ret = item2; min_x_dist = dist; min_y_dist = ABS(item->y - item2->y); } else if(dist == min_x_dist && item2 != ret) /* if there is another item of the same x distance */ { /* get the one with smaller y distance */ dist = ABS(item2->y - item->y); if(dist < min_y_dist) { ret = item2; min_y_dist = dist; } } } break; case GTK_DIR_UP: for( l = desktop->items; l; l = l->next ) { int dist; item2 = (FmDesktopItem*) l->data; if(item2->y >= item->y) continue; dist = item->y - item2->y; if(dist < min_y_dist) { ret = item2; min_y_dist = dist; min_x_dist = ABS(item->x - item2->x); } else if(dist == min_y_dist && item2 != ret) /* if there is another item of the same y distance */ { /* get the one with smaller x distance */ dist = ABS(item2->x - item->x); if(dist < min_x_dist) { ret = item2; min_x_dist = dist; } } } break; case GTK_DIR_DOWN: for( l = desktop->items; l; l = l->next ) { int dist; item2 = (FmDesktopItem*) l->data; if(item2->y <= item->y) continue; dist = item2->y - item->y; if(dist < min_y_dist) { ret = item2; min_y_dist = dist; min_x_dist = ABS(item->x - item2->x); } else if(dist == min_y_dist && item2 != ret) /* if there is another item of the same y distance */ { /* get the one with smaller x distance */ dist = ABS(item2->x - item->x); if(dist < min_x_dist) { ret = item2; min_x_dist = dist; } } } break; } return ret; } inline FmDesktopItem* desktop_item_new(GtkTreeIter* it) { FmDesktopItem* item = g_slice_new0(FmDesktopItem); item->it = *it; gtk_tree_model_get(GTK_TREE_MODEL(model), it, COL_FILE_ICON, &item->icon, COL_FILE_INFO, &item->fi, -1); return item; } void on_row_inserted(GtkTreeModel* mod, GtkTreePath* tp, GtkTreeIter* it, FmDesktop* desktop) { FmDesktopItem* item = desktop_item_new(it); desktop->items = g_list_insert(desktop->items, item, gtk_tree_path_get_indices(tp)[0]); queue_layout_items(desktop); } void on_row_deleted(GtkTreeModel* mod, GtkTreePath* tp, FmDesktop* desktop) { GList* l; int i = 0, idx = gtk_tree_path_get_indices(tp)[0]; for(l=desktop->items;l;l=l->next, ++i) { FmDesktopItem* item = (FmDesktopItem*)l->data; if(i == idx) { desktop_item_free(item); if(desktop->focus == item) { if(l->next) desktop->focus = (FmDesktopItem*)l->next->data; else if(l->prev) desktop->focus = (FmDesktopItem*)l->prev->data; else desktop->focus = NULL; } if(desktop->drop_hilight == item) desktop->drop_hilight = NULL; if(desktop->hover_item == item) desktop->hover_item = NULL; desktop->items = g_list_delete_link(desktop->items, l); break; } } queue_layout_items(desktop); } void on_row_changed(GtkTreeModel* mod, GtkTreePath* tp, GtkTreeIter* it, FmDesktop* desktop) { GList* l; for(l=desktop->items;l;l=l->next) { FmDesktopItem* item = (FmDesktopItem*)l->data; if(item->it.user_data == it->user_data) { if(item->icon) g_object_unref(item->icon); gtk_tree_model_get(mod, it, COL_FILE_ICON, &item->icon, COL_FILE_INFO, &item->fi, -1); redraw_item(desktop, item); /* FIXME: check if sorting of files is changed. */ break; } } /* queue_layout_items(desktop); */ } void on_rows_reordered(GtkTreeModel* mod, GtkTreePath* parent_tp, GtkTreeIter* parent_it, gpointer arg3, FmDesktop* desktop) { GtkTreeIter it; GList* new_items = NULL; if(!gtk_tree_model_get_iter_first(mod, &it)) return; do { GList* l; for(l = desktop->items; l; l=l->next) { FmDesktopItem* item = (FmDesktopItem*)l->data; if(item->it.user_data == it.user_data) { desktop->items = g_list_remove_link(desktop->items, l); new_items = g_list_concat(l, new_items); break; } } }while(gtk_tree_model_iter_next(mod, &it)); desktop->items = g_list_reverse(new_items); queue_layout_items(desktop); } void calc_item_size(FmDesktop* desktop, FmDesktopItem* item) { //int text_x, text_y, text_w, text_h; /* Probably goes along with the FIXME in this function */ PangoRectangle rc, rc2; /* icon rect */ if(item->icon) { item->icon_rect.width = gdk_pixbuf_get_width(item->icon); item->icon_rect.height = gdk_pixbuf_get_height(item->icon); item->icon_rect.x = item->x + (desktop->cell_w - item->icon_rect.width) / 2; item->icon_rect.y = item->y + desktop->ypad + (fm_config->big_icon_size - item->icon_rect.height) / 2; item->icon_rect.height += desktop->spacing; } else { item->icon_rect.width = fm_config->big_icon_size; item->icon_rect.height = fm_config->big_icon_size; item->icon_rect.x = item->x + desktop->ypad; item->icon_rect.y = item->y + desktop->ypad; item->icon_rect.height += desktop->spacing; } /* text label rect */ pango_layout_set_text(desktop->pl, NULL, 0); /* FIXME: we should cache text_h * PANGO_SCALE and text_w * PANGO_SCALE */ pango_layout_set_height(desktop->pl, desktop->pango_text_h); pango_layout_set_width(desktop->pl, desktop->pango_text_w); pango_layout_set_text(desktop->pl, fm_file_info_get_disp_name(item->fi), -1); pango_layout_get_pixel_extents(desktop->pl, &rc, &rc2); pango_layout_set_text(desktop->pl, NULL, 0); item->text_rect.x = item->x + (desktop->cell_w - rc2.width - 4) / 2; item->text_rect.y = item->icon_rect.y + item->icon_rect.height + rc2.y; item->text_rect.width = rc2.width + 4; item->text_rect.height = rc2.height + 4; } void layout_items(FmDesktop* self) { GList* l; FmDesktopItem* item; int x, y, bottom; GtkTextDirection direction = gtk_widget_get_direction(GTK_WIDGET(self)); y = self->working_area.y + self->ymargin; bottom = self->working_area.y + self->working_area.height - self->ymargin - self->cell_h; if(direction != GTK_TEXT_DIR_RTL) /* LTR or NONE */ { x = self->working_area.x + self->xmargin; for( l = self->items; l; l = l->next ) { item = (FmDesktopItem*)l->data; item->x = x; item->y = y; calc_item_size(self, item); y += self->cell_h; if(y > bottom) { x += self->cell_w; y = self->working_area.y + self->ymargin; } } } else /* RTL */ { x = self->working_area.x + self->working_area.width - self->xmargin; for( l = self->items; l; l = l->next ) { item = (FmDesktopItem*)l->data; item->x = x; item->y = y; calc_item_size(self, item); y += self->cell_h; if(y > bottom) { x -= self->cell_w; y = self->working_area.y + self->ymargin; } } } gtk_widget_queue_draw( GTK_WIDGET(self) ); } static gboolean on_idle_layout(FmDesktop* desktop) { desktop->idle_layout = 0; layout_items(desktop); return FALSE; } void queue_layout_items(FmDesktop* desktop) { if(0 == desktop->idle_layout) desktop->idle_layout = g_idle_add((GSourceFunc)on_idle_layout, desktop); } void paint_item(FmDesktop* self, FmDesktopItem* item, cairo_t* cr, GdkRectangle* expose_area) { GtkStyle* style; GtkWidget* widget = (GtkWidget*)self; GtkCellRendererState state = 0; GdkColor* fg; GdkWindow* window; int text_x, text_y; /* g_debug("%s, %d, %d, %d, %d", item->fi->path->name, expose_area->x, expose_area->y, expose_area->width, expose_area->height); */ style = gtk_widget_get_style(widget); window = gtk_widget_get_window(widget); pango_layout_set_text(self->pl, NULL, 0); pango_layout_set_width(self->pl, self->pango_text_w); pango_layout_set_height(self->pl, self->pango_text_h); pango_layout_set_text(self->pl, fm_file_info_get_disp_name(item->fi), -1); /* FIXME: do we need to cache this? */ text_x = item->x + (self->cell_w - self->text_w)/2 + 2; text_y = item->icon_rect.y + item->icon_rect.height + 2; if(item->is_selected || item == self->drop_hilight) /* draw background for text label */ { state = GTK_CELL_RENDERER_SELECTED; cairo_save(cr); gdk_cairo_rectangle(cr, &item->text_rect); gdk_cairo_set_source_color(cr, &style->bg[GTK_STATE_SELECTED]); cairo_clip(cr); cairo_paint(cr); cairo_restore(cr); fg = &style->fg[GTK_STATE_SELECTED]; } else { /* the shadow */ gdk_gc_set_rgb_fg_color(self->gc, &app_config->desktop_shadow); gdk_draw_layout( window, self->gc, text_x + 1, text_y + 1, self->pl ); fg = &app_config->desktop_fg; } /* real text */ gdk_gc_set_rgb_fg_color(self->gc, fg); gdk_draw_layout( window, self->gc, text_x, text_y, self->pl ); pango_layout_set_text(self->pl, NULL, 0); if(item == self->focus && gtk_widget_has_focus(widget) ) gtk_paint_focus(style, window, gtk_widget_get_state(widget), expose_area, widget, "icon_view", item->text_rect.x, item->text_rect.y, item->text_rect.width, item->text_rect.height); /* draw the icon */ g_object_set( self->icon_render, "pixbuf", item->icon, "info", fm_file_info_ref(item->fi), NULL ); gtk_cell_renderer_render(GTK_CELL_RENDERER(self->icon_render), window, widget, &item->icon_rect, &item->icon_rect, expose_area, state); } void redraw_item(FmDesktop* desktop, FmDesktopItem* item) { GdkRectangle rect; gdk_rectangle_union(&item->icon_rect, &item->text_rect, &rect); --rect.x; --rect.y; rect.width += 2; rect.height += 2; gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(desktop)), &rect, FALSE); } void calc_rubber_banding_rect( FmDesktop* self, int x, int y, GdkRectangle* rect ) { int x1, x2, y1, y2; if( self->drag_start_x < x ) { x1 = self->drag_start_x; x2 = x; } else { x1 = x; x2 = self->drag_start_x; } if( self->drag_start_y < y ) { y1 = self->drag_start_y; y2 = y; } else { y1 = y; y2 = self->drag_start_y; } rect->x = x1; rect->y = y1; rect->width = x2 - x1; rect->height = y2 - y1; } void update_rubberbanding( FmDesktop* self, int newx, int newy ) { GList* l; GdkRectangle old_rect, new_rect; //GdkRegion *region; GdkWindow *window; window = gtk_widget_get_window(GTK_WIDGET(self)); calc_rubber_banding_rect(self, self->rubber_bending_x, self->rubber_bending_y, &old_rect ); calc_rubber_banding_rect(self, newx, newy, &new_rect ); gdk_window_invalidate_rect(window, &old_rect, FALSE ); gdk_window_invalidate_rect(window, &new_rect, FALSE ); // gdk_window_clear_area(((GtkWidget*)self)->window, new_rect.x, new_rect.y, new_rect.width, new_rect.height ); /* region = gdk_region_rectangle( &old_rect ); gdk_region_union_with_rect( region, &new_rect ); // gdk_window_invalidate_region( ((GtkWidget*)self)->window, ®ion, TRUE ); gdk_region_destroy( region ); */ self->rubber_bending_x = newx; self->rubber_bending_y = newy; /* update selection */ for( l = self->items; l; l = l->next ) { FmDesktopItem* item = (FmDesktopItem*)l->data; gboolean selected; if( gdk_rectangle_intersect( &new_rect, &item->icon_rect, NULL ) || gdk_rectangle_intersect( &new_rect, &item->text_rect, NULL ) ) selected = TRUE; else selected = FALSE; if( item->is_selected != selected ) { item->is_selected = selected; redraw_item( self, item ); } } } void paint_rubber_banding_rect(FmDesktop* self, cairo_t* cr, GdkRectangle* expose_area) { GtkWidget* widget = (GtkWidget*)self; GdkRectangle rect; GdkColor clr; guchar alpha; calc_rubber_banding_rect( self, self->rubber_bending_x, self->rubber_bending_y, &rect ); if( rect.width <= 0 || rect.height <= 0 ) return; if(!gdk_rectangle_intersect(expose_area, &rect, &rect)) return; /* gtk_widget_style_get( icon_view, "selection-box-color", &clr, "selection-box-alpha", &alpha, NULL); */ clr = gtk_widget_get_style (widget)->base[GTK_STATE_SELECTED]; alpha = 64; /* FIXME: should be themable in the future */ cairo_save(cr); cairo_set_source_rgba(cr, (gdouble)clr.red/65535, (gdouble)clr.green/65536, (gdouble)clr.blue/65535, (gdouble)alpha/100); gdk_cairo_rectangle(cr, &rect); cairo_clip (cr); cairo_paint (cr); gdk_cairo_set_source_color(cr, &clr); cairo_rectangle (cr, rect.x + 0.5, rect.y + 0.5, rect.width - 1, rect.height - 1); cairo_stroke(cr); cairo_restore(cr); } static void update_background(FmDesktop* desktop) { GtkWidget* widget = (GtkWidget*)desktop; GdkPixbuf* pix, *scaled; GdkPixmap* pixmap; Pixmap pixmap_id; int src_w, src_h; int dest_w, dest_h; GdkWindow* root = gdk_screen_get_root_window(gtk_widget_get_screen(widget)); GdkWindow *window = gtk_widget_get_window(widget); if(app_config->wallpaper_mode == FM_WP_COLOR || !app_config->wallpaper || !*app_config->wallpaper || ! (pix = gdk_pixbuf_new_from_file(app_config->wallpaper, NULL)) ) /* solid color only */ { GdkColor bg = app_config->desktop_bg; gdk_rgb_find_color(gdk_drawable_get_colormap(window), &bg); gdk_window_set_back_pixmap(window, NULL, FALSE); gdk_window_set_background(window, &bg); gdk_window_set_back_pixmap(root, NULL, FALSE); gdk_window_set_background(root, &bg); gdk_window_clear(root); gdk_window_clear(window); gdk_window_invalidate_rect(window, NULL, TRUE); return; } src_w = gdk_pixbuf_get_width(pix); src_h = gdk_pixbuf_get_height(pix); if(app_config->wallpaper_mode == FM_WP_TILE) { dest_w = src_w; dest_h = src_h; pixmap = gdk_pixmap_new(window, dest_w, dest_h, -1); } else { GdkScreen* screen = gtk_widget_get_screen(widget); dest_w = gdk_screen_get_width(screen); dest_h = gdk_screen_get_height(screen); pixmap = gdk_pixmap_new(window, dest_w, dest_h, -1); } if(gdk_pixbuf_get_has_alpha(pix) || app_config->wallpaper_mode == FM_WP_CENTER || app_config->wallpaper_mode == FM_WP_FIT) { gdk_gc_set_rgb_fg_color(desktop->gc, &app_config->desktop_bg); gdk_draw_rectangle(pixmap, desktop->gc, TRUE, 0, 0, dest_w, dest_h); } switch(app_config->wallpaper_mode) { case FM_WP_TILE: gdk_draw_pixbuf(pixmap, desktop->gc, pix, 0, 0, 0, 0, dest_w, dest_h, GDK_RGB_DITHER_NORMAL, 0, 0); break; case FM_WP_STRETCH: if(dest_w == src_w && dest_h == src_h) scaled = (GdkPixbuf*)g_object_ref(pix); else scaled = gdk_pixbuf_scale_simple(pix, dest_w, dest_h, GDK_INTERP_BILINEAR); gdk_draw_pixbuf(pixmap, desktop->gc, scaled, 0, 0, 0, 0, dest_w, dest_h, GDK_RGB_DITHER_NORMAL, 0, 0); g_object_unref(scaled); break; case FM_WP_FIT: if(dest_w != src_w || dest_h != src_h) { gdouble w_ratio = (float)dest_w / src_w; gdouble h_ratio = (float)dest_h / src_h; gdouble ratio = MIN(w_ratio, h_ratio); if(ratio != 1.0) { src_w *= ratio; src_h *= ratio; scaled = gdk_pixbuf_scale_simple(pix, src_w, src_h, GDK_INTERP_BILINEAR); g_object_unref(pix); pix = scaled; } } /* continue to execute code in case FM_WP_CENTER */ case FM_WP_CENTER: { int x, y; x = (dest_w - src_w)/2; y = (dest_h - src_h)/2; gdk_draw_pixbuf(pixmap, desktop->gc, pix, 0, 0, x, y, -1, -1, GDK_RGB_DITHER_NORMAL, 0, 0); } break; } gdk_window_set_back_pixmap(root, pixmap, FALSE); gdk_window_set_back_pixmap(window, NULL, TRUE); pixmap_id = GDK_DRAWABLE_XID(pixmap); XChangeProperty(GDK_WINDOW_XDISPLAY(root), GDK_WINDOW_XID(root), XA_XROOTMAP_ID, XA_PIXMAP, 32, PropModeReplace, (guchar*)&pixmap_id, 1); g_object_unref(pixmap); if(pix) g_object_unref(pix); gdk_window_clear(root); gdk_window_clear(window); gdk_window_invalidate_rect(window, NULL, TRUE); } GdkFilterReturn on_root_event(GdkXEvent *xevent, GdkEvent *event, gpointer data) { XPropertyEvent * evt = ( XPropertyEvent* ) xevent; FmDesktop* self = (FmDesktop*)data; if ( evt->type == PropertyNotify ) { if(evt->atom == XA_NET_WORKAREA) update_working_area(self); } return GDK_FILTER_CONTINUE; } void update_working_area(FmDesktop* desktop) { GdkScreen* screen = gtk_widget_get_screen((GtkWidget*)desktop); GdkWindow* root = gdk_screen_get_root_window(screen); Atom ret_type; gulong len, after; int format; guchar* prop; guint32 n_desktops, cur_desktop; gulong* working_area; /* default to screen size */ desktop->working_area.x = 0; desktop->working_area.y = 0; desktop->working_area.width = gdk_screen_get_width(screen); desktop->working_area.height = gdk_screen_get_height(screen); if( XGetWindowProperty(GDK_WINDOW_XDISPLAY(root), GDK_WINDOW_XID(root), XA_NET_NUMBER_OF_DESKTOPS, 0, 1, False, XA_CARDINAL, &ret_type, &format, &len, &after, &prop) != Success) goto _out; if(!prop) goto _out; n_desktops = *(guint32*)prop; XFree(prop); if( XGetWindowProperty(GDK_WINDOW_XDISPLAY(root), GDK_WINDOW_XID(root), XA_NET_CURRENT_DESKTOP, 0, 1, False, XA_CARDINAL, &ret_type, &format, &len, &after, &prop) != Success) goto _out; if(!prop) goto _out; cur_desktop = *(guint32*)prop; XFree(prop); if( XGetWindowProperty(GDK_WINDOW_XDISPLAY(root), GDK_WINDOW_XID(root), XA_NET_WORKAREA, 0, 4 * 32, False, AnyPropertyType, &ret_type, &format, &len, &after, &prop) != Success) goto _out; if(ret_type == None || format == 0 || len != n_desktops*4 ) { if(prop) XFree(prop); goto _out; } working_area = ((gulong*)prop) + cur_desktop * 4; desktop->working_area.x = (gint)working_area[0]; desktop->working_area.y = (gint)working_area[1]; desktop->working_area.width = (gint)working_area[2]; desktop->working_area.height = (gint)working_area[3]; XFree(prop); _out: queue_layout_items(desktop); return; } void on_screen_size_changed(GdkScreen* screen, FmDesktop* desktop) { gtk_window_resize((GtkWindow*)desktop, gdk_screen_get_width(screen), gdk_screen_get_height(screen)); } void on_dnd_src_data_get(FmDndSrc* ds, FmDesktop* desktop) { FmFileInfoList* files = fm_desktop_get_selected_files(desktop); if(files) { fm_dnd_src_set_files(ds, files); fm_list_unref(files); } } gboolean on_dnd_dest_query_info(FmDndDest* dd, int x, int y, GdkDragAction* action, FmDesktop* desktop) { FmDesktopItem* item = hit_test(desktop, x, y); if(item) { *action = GDK_ACTION_COPY; fm_dnd_dest_set_dest_file(dd, item->fi); } else { *action = GDK_ACTION_COPY; /* FIXME: prevent direct access to data member */ fm_dnd_dest_set_dest_file(dd, model->dir->dir_fi); } if(desktop->drop_hilight != item) { FmDesktopItem* old_drop = desktop->drop_hilight; desktop->drop_hilight = item; if(old_drop) redraw_item(desktop, old_drop); if(item) redraw_item(desktop, item); } return TRUE; } void on_dnd_dest_files_dropped(FmDndDest* dd, GdkDragAction action, int info_type, FmList* files, FmDesktop* desktop) { } void on_wallpaper_changed(FmConfig* cfg, gpointer user_data) { int i; for(i=0; i < n_screens; ++i) update_background(FM_DESKTOP(desktops[i])); } void on_desktop_text_changed(FmConfig* cfg, gpointer user_data) { int i; /* FIXME: we only need to redraw text lables */ for(i=0; i < n_screens; ++i) gtk_widget_queue_draw(desktops[i]); } void on_desktop_font_changed(FmConfig* cfg, gpointer user_data) { /* FIXME: this is a little bit dirty */ if(font_desc) pango_font_description_free(font_desc); if(app_config->desktop_font) { font_desc = pango_font_description_from_string(app_config->desktop_font); if(font_desc) { int i; for(i=0; i < n_screens; ++i) { FmDesktop* desktop = desktops[i]; PangoContext* pc = gtk_widget_get_pango_context( (GtkWidget*)desktop ); pango_context_set_font_description(pc, font_desc); pango_layout_context_changed(desktop->pl); gtk_widget_queue_resize(GTK_WIDGET(desktop)); /* layout_items(desktop); */ /* gtk_widget_queue_draw(desktops[i]); */ } } } else font_desc = NULL; } static void reload_icons() { int i; for(i=0; i < n_screens; ++i) { FmDesktop* desktop = desktops[i]; GList* l; for(l=desktop->items;l;l=l->next) { FmDesktopItem* item = (FmDesktopItem*)l->data; if(item->icon) { g_object_unref(item->icon); item->icon = NULL; gtk_tree_model_get(GTK_TREE_MODEL(model), &item->it, COL_FILE_ICON, &item->icon, -1); } } gtk_widget_queue_resize(GTK_WIDGET(desktop)); } } void on_big_icon_size_changed(FmConfig* cfg, gpointer user_data) { fm_folder_model_set_icon_size(model, fm_config->big_icon_size); reload_icons(); } void on_icon_theme_changed(GtkIconTheme* theme, gpointer user_data) { reload_icons(); } void on_paste(GtkAction* act, gpointer user_data) { FmPath* path = fm_path_get_desktop(); fm_clipboard_paste_files(NULL, path); } void on_select_all(GtkAction* act, gpointer user_data) { int i; for(i=0; i < n_screens; ++i) { FmDesktop* desktop = desktops[i]; select_all(desktop); } } void on_invert_select(GtkAction* act, gpointer user_data) { int i; for(i=0; i < n_screens; ++i) { FmDesktop* desktop = desktops[i]; GList* l; for(l=desktop->items;l;l=l->next) { FmDesktopItem* item = (FmDesktopItem*)l->data; item->is_selected = !item->is_selected; redraw_item(desktop, item); } } } void on_create_new(GtkAction* act, gpointer user_data) { const char* name = gtk_action_get_name(act); if( strcmp(name, "NewFolder") == 0 ) name = TEMPL_NAME_FOLDER; else if( strcmp(name, "NewBlank") == 0 ) name = TEMPL_NAME_BLANK; pcmanfm_create_new(NULL, fm_path_get_desktop(), name); } void on_sort_type(GtkAction* act, GtkRadioAction *cur, gpointer user_data) { desktop_sort_type = gtk_radio_action_get_current_value(cur); gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), desktop_sort_by, desktop_sort_type); } void on_sort_by(GtkAction* act, GtkRadioAction *cur, gpointer user_data) { desktop_sort_by = gtk_radio_action_get_current_value(cur); gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), desktop_sort_by, desktop_sort_type); } void on_open_in_new_tab(GtkAction* act, gpointer user_data) { FmFileMenu* menu = (FmFileMenu*)user_data; FmFileInfoList* files = fm_file_menu_get_file_info_list(menu); GList* l; for( l = fm_list_peek_head_link(files); l; l=l->next ) { FmFileInfo* fi = (FmFileInfo*)l->data; fm_main_win_open_in_last_active(fi->path); } } void on_open_in_new_win(GtkAction* act, gpointer user_data) { FmFileMenu* menu = (FmFileMenu*)user_data; FmFileInfoList* files = fm_file_menu_get_file_info_list(menu); GList* l; FmFileInfo* fi = fm_list_peek_head(files); FmMainWin* win = fm_main_win_add_win(NULL, fi->path); for( l = fm_list_peek_head_link(files)->next; l; l=l->next ) { fi = (FmFileInfo*)l->data; fm_main_win_add_tab(win, fi->path); } } void on_open_folder_in_terminal(GtkAction* act, gpointer user_data) { FmFileMenu* menu = (FmFileMenu*)user_data; FmFileInfoList* files = fm_file_menu_get_file_info_list(menu); GList* l; for(l=fm_list_peek_head_link(files);l;l=l->next) { FmFileInfo* fi = (FmFileInfo*)l->data; if(fm_file_info_is_dir(fi) /*&& !fm_file_info_is_virtual(fi)*/) pcmanfm_open_folder_in_terminal(NULL, fi->path); } fm_list_unref(files); } GList* get_selected_items(FmDesktop* desktop, int* n_items) { GList* items = NULL; GList* l; int n = 0; FmDesktopItem* focus = NULL; for(l=desktop->items; l; l=l->next) { FmDesktopItem* item = (FmDesktopItem*)l->data; if(item->is_selected) { if(G_LIKELY(item != desktop->focus)) { items = g_list_prepend(items, item); ++n; } else focus = item; } } items = g_list_reverse(items); if(focus) { items = g_list_prepend(items, focus); ++n; } if(n_items) *n_items = n; return items; } FmFileInfoList* fm_desktop_get_selected_files(FmDesktop* desktop) { GList* l; FmFileInfoList* files = fm_file_info_list_new(); for(l=desktop->items; l; l=l->next) { FmDesktopItem* item = (FmDesktopItem*)l->data; if(item->is_selected) fm_list_push_tail(files, item->fi); } if(fm_list_is_empty(files)) { fm_list_unref(files); files = NULL; } return files; } FmPathList* fm_desktop_get_selected_paths(FmDesktop* desktop) { GList* l; FmPathList* files = fm_path_list_new(); for(l=desktop->items; l; l=l->next) { FmDesktopItem* item = (FmDesktopItem*)l->data; if(item->is_selected) fm_list_push_tail(files, item->fi->path); } if(fm_list_is_empty(files)) { fm_list_unref(files); files = NULL; } return files; } /* This function is taken from xfdesktop */ void forward_event_to_rootwin( GdkScreen *gscreen, GdkEvent *event ) { XButtonEvent xev, xev2; Display *dpy = GDK_DISPLAY_XDISPLAY( gdk_screen_get_display( gscreen ) ); if ( event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE ) { if ( event->type == GDK_BUTTON_PRESS ) { xev.type = ButtonPress; /* * rox has an option to disable the next * instruction. it is called "blackbox_hack". Does * anyone know why exactly it is needed? */ XUngrabPointer( dpy, event->button.time ); } else xev.type = ButtonRelease; xev.button = event->button.button; xev.x = event->button.x; /* Needed for icewm */ xev.y = event->button.y; xev.x_root = event->button.x_root; xev.y_root = event->button.y_root; xev.state = event->button.state; xev2.type = 0; } else if ( event->type == GDK_SCROLL ) { xev.type = ButtonPress; xev.button = event->scroll.direction + 4; xev.x = event->scroll.x; /* Needed for icewm */ xev.y = event->scroll.y; xev.x_root = event->scroll.x_root; xev.y_root = event->scroll.y_root; xev.state = event->scroll.state; xev2.type = ButtonRelease; xev2.button = xev.button; } else return ; xev.window = GDK_WINDOW_XWINDOW( gdk_screen_get_root_window( gscreen ) ); xev.root = xev.window; xev.subwindow = None; xev.time = event->button.time; xev.same_screen = True; XSendEvent( dpy, xev.window, False, ButtonPressMask | ButtonReleaseMask, ( XEvent * ) & xev ); if ( xev2.type == 0 ) return ; /* send button release for scroll event */ xev2.window = xev.window; xev2.root = xev.root; xev2.subwindow = xev.subwindow; xev2.time = xev.time; xev2.x = xev.x; xev2.y = xev.y; xev2.x_root = xev.x_root; xev2.y_root = xev.y_root; xev2.state = xev.state; xev2.same_screen = xev.same_screen; XSendEvent( dpy, xev2.window, False, ButtonPressMask | ButtonReleaseMask, ( XEvent * ) & xev2 ); }