الحاجيات التي تحركها الماوس. سحب وإسقاط داخل النافذة

الفهم المعتاد لسحب وإسقاط (D&D) يفترض ، على سبيل المثال ، يتم أخذ رابط لملف من عنصر واجهة مستخدم ونقله بالماوس إلى نافذة أو عنصر واجهة مستخدم آخر. بعد ذلك ، لن نتحدث عن وظائف مكتبة D&D ، ولكن عن تنفيذنا الخاص بنقل عنصر واجهة المستخدم داخل النافذة والوظائف ذات الصلة. الرمز أكثر من تطبيق عملي ملموس ، مكتوب بأسلوب C مع فصول. المحرر هو CodeBlocks 17.12 ، الذي توقف عن التعطل على Ubuntu x64 مقارنة بالإصدار السادس عشر.

صورة

هناك حاوية عنصر واجهة مستخدم GtkFixed ، والتي يمكن تخزين عناصر واجهة المستخدم الأخرى في إحداثيات معينة ، والمفهوم الكلاسيكي لإنشاء تطبيقات على GTK ينطوي على استخدام حاويات القطعة GtkBox (وغيرها) لتمديد النافذة بشكل صحيح وملء المساحة. تمدد الحاوية إما إلى حجم النافذة (وإلى حدود عناصر واجهة المستخدم الأخرى) ، أو يتم تصغيرها إلى حجم عنصر واجهة المستخدم كقاعدة.

الكود مقسم إلى main.cpp ، main.hpp ، movable_widgets.hpp. لم أختر ملف التنفيذ بشكل منفصل. محتوى main.cpp نموذجي إلى حد ما:

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

يتم إنشاء بعض عناصر واجهة التعامل من وصف XML (دالة builder_init) ، جزء آخر برمجيًا (صفحة-> add_widget). وظيفة Gtk_widget_show_all (البيانات> فوز) ؛ اللازمة لعرض العودية من الحاجيات ومحتوياتها. يقوم GTK بمسح محتويات الأدوات المصغّرة بشكل مستقل عند حذفها ، ولا سيما التطبيقات المصغّرة التابعة. بحلول الوقت الذي يتم فيه تنفيذ وظيفة معاودة الاتصال application_shutdown ، تم حذف النافذة الرئيسية وجميع عناصر واجهة تعامل المحتوى بالفعل.

 #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 

حقل الصفحات عبارة عن صفيف من المؤشرات إلى الفئات مع محتويات الصفحات ، لا يتم استخدام هذا المثال ، حيث يتم استخدام علامة تبويب واحدة فقط. باستخدام تقييد هو الهاوي. من الناحية النظرية ، يعطي زيادة معينة في الأداء. في هذه الحالة ، ليست هناك حاجة للاستخدام.

يتم وضع عنصر واجهة المستخدم المدرج نفسه في حاوية من نوع GtkEventBox. انه يجمع الأحداث
buttonclick. أيضًا حاوية اختيارية من نوع GtkFrame لعرض عنصر واجهة مستخدم في إطار عند النقر فوق زر الماوس الأيسر. عملية تغيير الحاوية سريعة بما فيه الكفاية. تحتوي علامة التبويب نفسها ، حيث يتم إدراج عناصر واجهة المستخدم ، على التسلسل الهرمي التالي للمرفقات: GtkScrolledWindow-> GtkViewport-> GtkFixed. في البداية ، تكون عناصر واجهة التعامل من النوع GtkWidget ، والتي يتم تحويلها بواسطة وحدات ماكرو إلى أنواع GtkViewport ، GtkFixed. أود أن أؤكد
الانتباه إلى وجهة نظر الماكرو
InsertedWidgetWithProperty * widget_with_property = & g_array_index (الأدوات ، InsertedWidgetWithProperty ، i)؛
نظرًا لأنه من الأسهل ارتكاب خطأ هنا. المعلمات x_correction ، y_correction - إحداثيات النقر بالماوس بالنسبة إلى عنصر واجهة المستخدم المدرج GtkEvent. يتم استخدام علامة button_not_pressed لعرض حاوية الإطار بشكل صحيح. حسب المنطق ، من المفهوم أنه إذا تم النقر فوق أحد أزرار الماوس على عنصر واجهة المستخدم المدرج ، فيجب وضعه في إطار. بمعنى أنه لا يتم إقران أحداث النقر والإلغاء ، على عكس حدثي حدث إعلام - حدث وحدث إعلام ترك - المرتبطان بتغيير في شكل المؤشر. إذا كانت المعلمات x_correction ، button_not_pressed هي معلمات خدمة ، أي ، يجب وضعها في القسم الخاص ، يتم استخدام علامة click_order لعرض الأداة الحالية أعلى الباقي.

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

الصيغة لحساب إحداثيات القطعة بحيث لا تتحرك في أي اتجاه عند النقر فوق زر الماوس عليها. أنها تنطوي على إحداثيات النقر بالنسبة إلى القطعة ،
إحداثيات GtkFixed بالنسبة لإطار التطبيق ، إحداثيات النافذة بالنسبة إلى الشاشة.
عامل التصحيح +25 يربكني إلى حد ما ، لكنني لا أعرف مدى بساطة الكتابة. راجعت العمل في إصدارات Ubuntu 15.10 ، 16.04 ، 18.04. يتم إجراء مقارنة مع 0 و -1 بحيث لا تتم إزالة عنصر واجهة المستخدم المدرج من منطقة التمرير. يتم توفير التمرير نفسه بواسطة عنصر واجهة مستخدم 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; } 

بقية وظائف رد الاتصال. نفذت إزالة الحاجيات الفردية عبر قائمة السياق باستخدام زر الماوس الأيمن. تتم إزالة إزالة فئة page_body من حدث حذف 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; } } 

شكرا لاهتمامكم

github.com/SanyaZ7/movable_widgets_on_GtkFixed-

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


All Articles