Mouse-driven widgets. Drag and drop within the window

The usual understanding of Drag and Drop (D&D) assumes that, for example, a link to a file is taken from one widget and moved with the mouse to another window or widget. Next, we will talk not about the library functions of D&D, but about our own implementation of moving the widget within the window and related functionality. The code is more for example than concrete practical application, written in C style with classes. The editor is CodeBlocks 17.12, which stopped crashing on Ubuntu x64 compared to the 16th version.







image






There is a widget container GtkFixed, which can store other widgets at certain coordinates, the classic concept of creating applications on GTK involves the use of widget containers GtkBox (and others) to correctly stretch the window and fill the space. The container is either stretched to the size of the window (and to the borders of other widgets), or reduced to the size of the child widget as a rule.





The code is divided into main.cpp, main.hpp, movable_widgets.hpp. I did not select the implementation file separately. The content of main.cpp is pretty typical:







#include "main.hpp" #include "movable_widgets.hpp" void builder_init(gpointer user_data) { appdata *data=(appdata*) user_data; GError *error = NULL; GtkBuilder *builder = gtk_builder_new(); if (!gtk_builder_add_from_file (builder, "window.glade", &error)) { //     g_critical ("   : %s", error->message); g_error_free (error); } data->win=GTK_WIDGET(gtk_builder_get_object(builder, "window1")); data->notebook=GTK_NOTEBOOK(gtk_builder_get_object(builder, "notebook1")); gtk_notebook_remove_page(data->notebook,0); ///    gtk_builder_connect_signals (builder,data); g_clear_object(&builder); } void application_activate(GtkApplication *application, gpointer user_data) { appdata *data=(appdata*) user_data; builder_init(data); gtk_widget_set_size_request(data->win,320,240); gtk_application_add_window(data->app,GTK_WINDOW(data->win)); page_body *page=new page_body(data, G_OBJECT(data->notebook)); const gchar *text ="<span foreground=\"blue\" size=\"x-large\">Blue text</span>" ; GtkWidget *label = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (label), text); GtkWidget *image=gtk_image_new_from_file("opennet2.gif"); GtkWidget *image2=gtk_image_new_from_file("n_temp.png"); page->add_widget(label,label_t,10,10); page->add_widget(image,image_t,20,20); page->add_widget(image2,image_t,40,40); gtk_widget_show_all(data->win); } void application_shutdown(const GtkApplication *application, gpointer user_data) {} int main (int argc, char *argv[]) { appdata data; gtk_init (&argc, &argv); gint res; data.app = gtk_application_new("gtk3.org", G_APPLICATION_FLAGS_NONE); g_signal_connect(data.app, "activate", G_CALLBACK(application_activate), &data); g_signal_connect(data.app, "shutdown", G_CALLBACK(application_shutdown), &data); res = g_application_run(G_APPLICATION(data.app), 0, NULL); return 0; }
      
      





Some widgets are created from the XML description (function builder_init), the other part programmatically (page-> add_widget). Gtk_widget_show_all function (data-> win); needed for the recursive display of widgets and their contents. GTK independently clears the contents of widgets when they are deleted, in particular other child widgets. By the time the application_shutdown callback function is executed, the main window and all the content widgets have already been deleted.





 #ifndef MAIN_H #define MAIN_H #include <gtk/gtk.h> #include <stdbool.h> #include <stdlib.h> #define restrict __restrict__ class appdata { public: char *glade_name=(char*)"window.glade"; GtkApplication *restrict app; GtkWidget *restrict win; GtkNotebook *restrict notebook; GArray *restrict pages; }; #endif
      
      





The pages field is an array of pointers to classes with the contents of the pages, in this example it is not used, since 1 tab is only used. Using restrict for the amateur. Theoretically, gives a certain increase in performance. In this case, there is no need for use.



The widget itself is placed in a container of type GtkEventBox. He collects events

buttonclick. Also an optional container of the GtkFrame type for displaying a widget in a frame when clicking the left mouse button. The container change operation is fast enough. The tab itself, where widgets are inserted, has the following hierarchy of attachments: GtkScrolledWindow-> GtkViewport-> GtkFixed. Initially, widgets are of type GtkWidget, which is converted by macros to the types GtkViewport, GtkFixed. I would emphasize

attention to the macro view

InsertedWidgetWithProperty * widget_with_property = & g_array_index (widgets, InsertedWidgetWithProperty, i);

since it’s the easiest to make a mistake here. Parameters x_correction, y_correction - coordinates of the mouse click relative to the inserted widget GtkEvent. The button_not_pressed flag is used to correctly display the frame container. By logic, it is understood that if one of the mouse buttons is clicked on the inserted widget, then it should be placed in a frame. That is, the buttonclick and buttonrelease events are not paired, unlike the enter-notify-event and leave-notify-event events, which are associated with a change in the shape of the cursor. If the x_correction, button_not_pressed parameters are service parameters, that is, they should be placed in the private section, the click_order flag is used to display the current widget on top of the rest.







 typedef struct { GtkWidget *restrict widget_ptr; GtkWidget *restrict eventbox; GtkWidget *restrict frame; //   GtkWidget *restrict pmenu; widget_type type; bool button_not_pressed; } InsertedWidgetWithProperty; class page_body { public: GtkWidget *restrict scrolledwindow; GtkWidget *restrict viewport; GtkWidget *restrict fixed; GArray *restrict widgets=g_array_new(FALSE, TRUE, sizeof(InsertedWidgetWithProperty)); GtkAdjustment *restrict h_adj; GtkAdjustment *restrict v_adj; int num_of_current_widget=0; double x_correction=0; double y_correction=0; GtkWidget *restrict window; ///      int widget_count=0; bool click_order=FALSE; //TRUE -  page_body(appdata *data, GObject *container) { window=data->win; h_adj=gtk_adjustment_new(0.0,4.0,900.0,1.0,5.0,10.0); v_adj=gtk_adjustment_new(0.0,4.0,900.0,1.0,5.0,10.0); scrolledwindow=gtk_scrolled_window_new(h_adj, v_adj); viewport=gtk_viewport_new(h_adj, v_adj); fixed=gtk_fixed_new(); gtk_container_add(GTK_CONTAINER(scrolledwindow),GTK_WIDGET(viewport)); gtk_container_add(GTK_CONTAINER(viewport),GTK_WIDGET(fixed)); if(GTK_IS_NOTEBOOK(container)) { gtk_notebook_append_page ((GtkNotebook*)container,scrolledwindow,NULL); } else if(GTK_IS_WIDGET(container)) { gtk_container_add(GTK_CONTAINER(container),scrolledwindow); } g_signal_connect(fixed,"motion-notify-event",G_CALLBACK(fixed_motion_notify), this); g_signal_connect(scrolledwindow,"destroy",G_CALLBACK(scrolled_window_destroy_cb), this); } ~page_body() { int i=widgets->len; if(widget_count>0) { for(i; i>=0; i--) { InsertedWidgetWithProperty *widget_with_property; widget_with_property=&g_array_index(widgets,InsertedWidgetWithProperty,i); } } g_array_free(widgets,TRUE); } void add_widget(GtkWidget *widget, widget_type type, int x, int y) { ++widget_count; InsertedWidgetWithProperty *widget_with_property=(InsertedWidgetWithProperty*) g_malloc0(sizeof(InsertedWidgetWithProperty)); widget_with_property->eventbox=gtk_event_box_new(); widget_with_property->type=type; widget_with_property->widget_ptr=widget; gtk_container_add(GTK_CONTAINER(widget_with_property->eventbox),widget); gtk_fixed_put(GTK_FIXED(fixed),widget_with_property->eventbox,x,y); widget_with_property->pmenu=gtk_menu_new(); GtkWidget *menu_items = gtk_menu_item_new_with_label (""); gtk_widget_show(menu_items); gtk_menu_shell_append (GTK_MENU_SHELL (widget_with_property->pmenu), menu_items); g_signal_connect(widget_with_property->eventbox,"button-press-event",G_CALLBACK(eventbox_press_cb),this); g_signal_connect(widget_with_property->eventbox,"button-release-event",G_CALLBACK(eventbox_release_cb),this); g_signal_connect(menu_items,"activate",G_CALLBACK(menu_delete_activate),this); g_signal_connect(widget_with_property->eventbox,"leave-notify-event",G_CALLBACK(eventbox_leave_cb),this); g_signal_connect(widget_with_property->eventbox,"enter-notify-event",G_CALLBACK(eventbox_enter_cb),this); gtk_widget_set_events(widget_with_property->eventbox,GDK_LEAVE_NOTIFY_MASK|GDK_ENTER_NOTIFY_MASK|GDK_STRUCTURE_MASK); g_array_append_val(widgets, *widget_with_property); } inline void change_cursor(char *cursor_name) { GdkDisplay *display; GdkCursor *cursor; display = gtk_widget_get_display (window); if(cursor_name) cursor = gdk_cursor_new_from_name (display, cursor_name); else cursor = gdk_cursor_new_from_name (display, "default"); GdkWindow *gdkwindow=gtk_widget_get_window (window); gdk_window_set_cursor (gdkwindow, cursor); } inline void delete_widget(int i) { InsertedWidgetWithProperty *widget_with_property= &g_array_index(this->widgets,InsertedWidgetWithProperty,i); GtkWidget *eventbox=widget_with_property->eventbox; g_object_ref(eventbox); gtk_container_remove(GTK_CONTAINER(this->fixed),eventbox); if(widget_with_property->frame!=NULL) { gtk_widget_destroy(widget_with_property->frame); } gtk_widget_destroy(widget_with_property->eventbox); this->widgets=g_array_remove_index_fast(this->widgets,i); --this->widget_count; } };
      
      





A formula for calculating the coordinates of a widget so that it does not move in any direction when you click the mouse button on it. It involves the coordinates of the click relative to the widget,

GtkFixed coordinates relative to the application window, window coordinates relative to the screen.

The correction factor +25 confuses me somewhat, but I don’t know how simpler to write. Checked work in versions of Ubuntu 15.10, 16.04, 18.04. Comparison with 0 and -1 is carried out so that the inserted widget is not removed from the scrollable area. The scrolling itself is provided by the GtkScrolledWindow widget.



 gboolean fixed_motion_notify (GtkWidget *widget, GdkEvent *event, gpointer user_data) { page_body *page=(page_body*) user_data; int x_win, y_win, x_fixed, y_fixed; gtk_window_get_position(GTK_WINDOW(page->window),&x_win,&y_win); gtk_widget_translate_coordinates(page->window,page->fixed,x_win,y_win,&x_fixed,&y_fixed); double correction_y=(-y_fixed+y_win)*2+25; double correction_x=(-x_fixed+x_win); double x_corr=page->x_correction; double y_corr=page->y_correction; int position_x=event->motion.x_root-x_corr-x_win-correction_x; int position_y=event->motion.y_root-y_corr-y_fixed-correction_y; InsertedWidgetWithProperty *widget_with_property=&g_array_index(page->widgets,InsertedWidgetWithProperty,page->num_of_current_widget); GtkWidget *fixed=page->fixed; GtkWidget *eventbox=widget_with_property->eventbox; if(position_x<-1) position_x=0; if(position_y<-1) position_y=0; gtk_fixed_move(GTK_FIXED(fixed), eventbox, position_x, position_y); return FALSE; }
      
      





The rest of the callback functions. Implemented the removal of individual widgets via the context menu with the right mouse button. Removing the page_body class is hung from the GtkScrolledWindow delete event.



 void scrolled_window_destroy_cb (GtkWidget *object, gpointer user_data) { page_body *page=(page_body*) user_data; delete page; } void menu_delete_activate (GtkMenuItem *menuitem, gpointer user_data) { page_body *page=(page_body*) user_data; page->delete_widget(page->num_of_current_widget); } gboolean eventbox_leave_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { page_body *page=(page_body*) user_data; page->change_cursor(NULL); } gboolean eventbox_enter_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { page_body *page=(page_body*) user_data; page->change_cursor("pointer"); } gboolean eventbox_press_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { page_body *page=(page_body*) user_data; page->x_correction=event->button.x; page->y_correction=event->button.y; int i=0; InsertedWidgetWithProperty *widget_compare; for(i; i<=page->widgets->len; i++) { widget_compare=(InsertedWidgetWithProperty*) page->widgets->data+i; if(widget==widget_compare->eventbox) { page->num_of_current_widget=i; break; } } if(widget_compare->button_not_pressed==FALSE) { GtkWidget *eventbox=widget_compare->eventbox; if(page->click_order) { int x, y; gtk_widget_translate_coordinates(page->fixed, eventbox,0,0,&x, &y); gtk_container_remove(GTK_CONTAINER(page->fixed),eventbox); gtk_fixed_put(GTK_FIXED(page->fixed),eventbox,-x,-y); } g_object_ref(widget_compare->widget_ptr); gtk_container_remove(GTK_CONTAINER(eventbox),widget_compare->widget_ptr); if(widget_compare->frame==NULL) widget_compare->frame=gtk_frame_new(NULL); gtk_container_add(GTK_CONTAINER(widget_compare->frame),widget_compare->widget_ptr); gtk_container_add(GTK_CONTAINER(eventbox),widget_compare->frame); gtk_widget_show_all(eventbox); widget_compare->button_not_pressed=TRUE; } ///   const gint RIGHT_CLICK = 3; if (event->type == GDK_BUTTON_PRESS) { GdkEventButton *bevent = (GdkEventButton *) event; if (bevent->button == RIGHT_CLICK) { gtk_menu_popup(GTK_MENU(widget_compare->pmenu), NULL, NULL, NULL, NULL, bevent->button, bevent->time); } } return FALSE; } gboolean eventbox_release_cb (GtkWidget *eventbox, GdkEvent *event, gpointer user_data) { page_body *page=(page_body*) user_data; InsertedWidgetWithProperty *widget_with_property= &g_array_index(page->widgets,InsertedWidgetWithProperty,page->num_of_current_widget); ///      ,    if(widget_with_property->button_not_pressed==TRUE) { widget_with_property->frame=(GtkWidget*) g_object_ref(widget_with_property->frame); widget_with_property->widget_ptr=(GtkWidget*) g_object_ref(widget_with_property->widget_ptr); GtkWidget *frame=widget_with_property->frame; GtkWidget *widget=widget_with_property->widget_ptr; gtk_container_remove(GTK_CONTAINER(eventbox), frame); gtk_container_remove(GTK_CONTAINER(frame), widget); gtk_container_add(GTK_CONTAINER(eventbox), widget); widget_with_property->button_not_pressed=FALSE; } }
      
      





Thanks for attention.



github.com/SanyaZ7/movable_widgets_on_GtkFixed-



All Articles