Widget yang digerakkan mouse. Seret dan letakkan di dalam jendela

Pemahaman yang biasa tentang Drag and Drop (D&D) mengasumsikan bahwa, misalnya, tautan ke file diambil dari satu widget dan dipindahkan dengan mouse ke jendela atau widget lain. Selanjutnya, kita akan berbicara bukan tentang fungsi perpustakaan D & D, tetapi tentang implementasi kita sendiri dalam memindahkan widget di dalam jendela dan fungsionalitas terkait. Kode ini lebih sebagai contoh daripada aplikasi praktis konkret, ditulis dalam gaya C dengan kelas-kelas. Editornya adalah CodeBlocks 17.12, yang berhenti mogok di Ubuntu x64 dibandingkan dengan versi ke-16.

gambar

Ada wadah widget GtkFixed, yang dapat menyimpan widget lain pada koordinat tertentu, konsep klasik pembuatan aplikasi pada GTK melibatkan penggunaan wadah widget GtkBox (dan lainnya) untuk meregangkan jendela dengan benar dan mengisi ruang. Wadah tersebut diregangkan ke ukuran jendela (dan ke batas widget lainnya), atau dikurangi menjadi ukuran widget anak sebagai aturan.

Kode ini dibagi menjadi main.cpp, main.hpp, movable_widgets.hpp. Saya tidak memilih file implementasi secara terpisah. Konten main.cpp sangat khas:

#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; } 

Beberapa widget dibuat dari deskripsi XML (function builder_init), bagian lain secara terprogram (halaman-> add_widget). Fungsi Gtk_widget_show_all (data-> win); diperlukan untuk tampilan widget berulang dan isinya. GTK secara independen menghapus konten widget saat dihapus, khususnya widget anak lainnya. Pada saat fungsi callback application_shutdown dijalankan, jendela utama dan semua widget konten telah dihapus.

 #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 

Bidang halaman adalah array pointer ke kelas dengan konten halaman, dalam contoh ini tidak digunakan, karena 1 tab hanya digunakan. Menggunakan batasan adalah amatir. Secara teoritis, memberikan peningkatan kinerja tertentu. Dalam hal ini, tidak perlu digunakan.

Widget yang dimasukkan itu sendiri ditempatkan dalam wadah jenis GtkEventBox. Dia mengumpulkan acara
klik tombol. Juga wadah opsional dari tipe GtkFrame untuk menampilkan widget dalam bingkai ketika mengklik tombol kiri mouse. Operasi penggantian kontainer cukup cepat. Tab itu sendiri, tempat widget dimasukkan, memiliki hierarki lampiran berikut: GtkScrolledWindow-> GtkViewport-> GtkFixed. Awalnya, widget bertipe GtkWidget, yang dikonversi oleh makro ke tipe GtkViewport, GtkFixed. Saya akan menekankan
memperhatikan tampilan makro
InsertedWidgetWithProperty * widget_with_property = & g_array_index (widget, InsertedWidgetWithProperty, i);
karena paling mudah untuk membuat kesalahan di sini. Parameter x_correction, y_correction - koordinat klik mouse relatif terhadap widget GtkEvent yang dimasukkan. Bendera button_not_pressed digunakan untuk menampilkan wadah bingkai dengan benar. Secara logika, dapat dipahami bahwa jika salah satu tombol mouse diklik pada widget yang dimasukkan, maka itu harus ditempatkan dalam bingkai. Yaitu, peristiwa klik tombol dan peristiwa pelepasan tombol tidak dipasangkan, tidak seperti peristiwa enter-notify-event dan event-notify-event, yang dikaitkan dengan perubahan dalam bentuk kursor. Jika x_correction, parameter button_not_pressed adalah resmi, yaitu, mereka harus ditempatkan di bagian pribadi, flag click_order digunakan untuk menampilkan widget saat ini di atas sisanya.

 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; } }; 

Rumus untuk menghitung koordinat widget sehingga tidak bergerak ke arah mana pun ketika Anda mengklik tombol mouse di atasnya. Ini melibatkan koordinat klik relatif terhadap widget,
GtkFixed koordinat relatif terhadap jendela aplikasi, koordinat jendela relatif terhadap layar.
Faktor koreksi +25 agak membingungkan saya, tetapi saya tidak tahu bagaimana lebih mudah untuk menulis. Saya memeriksa pekerjaan dalam versi Ubuntu 15.10, 16.04, 18.04. Perbandingan dengan 0 dan -1 dilakukan sehingga widget yang dimasukkan tidak dihapus dari area yang dapat digulir. Scrolling itu sendiri disediakan oleh widget GtkScrolledWindow.

 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; } 

Sisa fungsi panggilan balik. Diimplementasikan penghapusan widget individu melalui menu konteks dengan tombol kanan mouse. Menghapus kelas page_body digantung dari peristiwa penghapusan GtkScrolledWindow.

 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; } } 

Terima kasih atas perhatian anda

github.com/SanyaZ7/movable_widgets_on_GtkFixed-

Source: https://habr.com/ru/post/id473716/


All Articles