How do I do drag and drop in GTK3 - drag-and-drop

I was wondering how to drag and drop in GTK3. The code here is for GTK2, and does not work in GTK3. The compiler complains about there being no data element in seldata in onDragDataReceived. And also, this would not work if you have multiple files in one drag and drop.

How to do drag and drop in GTK3
The drag and drop implemented here is only for copying files into an application.
So, the first thing to do is to make your target entries, that is what sort of thing can be dragged in. For text editors, you would allow text to be dragged in. But in this example, we only want to drag in files.
static GtkTargetEntry targetentries[] =
{
{ "text/uri-list", 0, 0}
};
Now that we have the target entries, we can make the specific widget into a drag and drop destination.
gtk_drag_dest_set ( your_widget_here, GTK_DEST_DEFAULT_ALL, targetentries, 1, GDK_ACTION_COPY);
And now for the signal handler:
g_signal_connect (your_widget_here, "drag-data-received", G_CALLBACK (on_drag_data_received), some_data_to_pass_along);
So, when you drop a file on your widget, it will emit the signal, because you prep’d it by making it a dnd destination.
Here is the callback function:
void on_drag_data_received (GtkWidget *wgt, GdkDragContext *context, gint x, gint y, GtkSelectionData *seldata, guint info, guint time, gpointer data)
{
gchar **filenames = NULL;
filenames = g_uri_list_extract_uris((const gchar *) gtk_selection_data_get_data (seldata));
if (filenames == NULL) // If unable to retrieve filenames:
{
g_printerr(“FAILURE!”);
gtk_drag_finish(context, FALSE, FALSE, time); // Drag and drop was a failure.
return;
}
int iter = 0;
while(filenames[iter] != NULL) // The last URI list element is NULL.
{
char *filename = g_filename_from_uri (filenames[iter], NULL, NULL);
// Do something useful with the file, like opening it, right here.
iter++;
}
gtk_drag_finish(context, TRUE, FALSE, time); // Drag and drop was successful!
}
And you are done!

Related

Why doesn't my GtkBox update until I resize the whole window?

When I attach a menu to a window I detach the existing root control, add a vertical GtkBox to hold the menu and the root control and then attach that box to the GtkWindow, like so:
Gtk::GtkWidget *menubar = GTK_WIDGET(Info.obj);
Wnd->_VBox = Gtk::gtk_box_new(Gtk::GTK_ORIENTATION_VERTICAL, 0);
Gtk::GtkBox *vbox = GTK_BOX(Wnd->_VBox);
Gtk::GtkContainer *wndcontainer = GTK_CONTAINER(Wnd->Wnd);
g_object_ref(Wnd->_Root);
gtk_container_remove(wndcontainer, Wnd->_Root);
gtk_box_pack_start(vbox, menubar, false, false, 0);
gtk_box_pack_end(vbox, Wnd->_Root, true, true, 0);
gtk_container_add(wndcontainer, Wnd->_VBox);
gtk_widget_show_all(GTK_WIDGET(Wnd->Wnd));
g_object_unref(Wnd->_Root);
gtk_window_add_accel_group(Wnd->Wnd, AccelGrp);
In practice it looks like this:
What I'd like is the menu to appear in the correct place automatically without having to resize the window to force a layout update.
I've tried calling gtk_widget_queue_draw on the window but that made no difference. Am I doing something wrong here? Can a call an extra function to invalidate the layout and get it to refresh?
It's difficult to answer without having a minimal reproducible example. Your code in not even plain GTK or gtkmm... it seems to be some exotic variant between the two.
Here is my attempt: I tried to be as close as possible to your code. The issue you are describing is not present though.
/* gcc -o test test.c $(pkg-config --cflags --libs gtk+-3.0) */
#include <gtk/gtk.h>
static GMenu *
menu_model(void)
{
GMenu *menu = g_menu_new();
g_menu_append(menu, "File", NULL);
g_menu_append(menu, "Edit", NULL);
g_menu_append(menu, "Project", NULL);
/* ... */
return menu;
}
int main(int argc, char **argv)
{
GtkWidget *window;
GMenuModel *model;
GtkWidget *menubar;
GtkWidget *content;
GtkWidget *vbox;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
model = G_MENU_MODEL(menu_model());
menubar = gtk_menu_bar_new_from_model(model);
g_object_unref(model);
content = gtk_label_new("Some content here");
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
gtk_box_pack_end(GTK_BOX(vbox), content, TRUE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(window), vbox);
gtk_widget_show_all(GTK_WIDGET(window));
gtk_main();
return 0;
}
So while it should "just work" out of the box. And indeed on my Raspberry Pi the code does just do what it's supposed to do, this is still an issue on my Ubuntu 18 VM. I have found somewhat of a work around to kick the GtkBox into reconfiguring the child widget's layout:
GdkRectangle allocation = Wnd->GetClient();
g_signal_emit_by_name(G_OBJECT(vbox), "size-allocate", GTK_WIDGET(vbox), &allocation, NULL, NULL);
The menu now appears in the right location automatically. It seems like a "hack" that may stop working in the future or crash on some systems? IDK. But in terms of right now and Ubuntu 18, I don't have anything better.

blank dialogue box without my text nor OK button

I'm trying to popup an OK message box in my gtk browser, but all I'm getting is blank grey square patch without my text in it and even button is not visible.
PFB the function which I'm using to render message box :
Void DisplayOKPopup()
{
dialogue=gtk_message_dialogue_new(GTK_WINDOW(WebtBrowserWindow),
GTK_DIALOGUE_DESTROY_WITH_PARENT,
GTK_MESSAGE_INFO,
GTK_BUTTONS_OK,
"text message");
gtk_widget_show(dialogue);
LOGDEBUG(" 1");
gtk_dialogue_run(GTK_DIALOGUE (dialogue));
LOGDEBUG("2");
gtk_widget_destroy (dialogue);
}
As per my debug log, I can see that control is passing till LOGDEBUG("1") and after that it goes to gtk_dialogue_run after that UI is getting crashed, the line next to run i.e LOGDEBUG("2") is not getting executed.
Kindly provide your inputs as I'm working on this since 3 days:!
As concluded, the problem arises from the use of threads. There are several approaches to solve the issue but since there is not code, I'll try with a simple example so that you can recreate it on your code.
Take a global Boolean variable as a flag and on your thread, set it to true so that a "periodic" idle callback can check it and if the flag is TRUE then show the dialog. The logic is that the Gtk UI functions are called from the mainloop/main thread and not from the worker threads.
DisplayOkPopup just has a simple "counter" from 0 to MAX_INT (32 bits) and set's the global flag as TRUE.
check_for_dialog it's a callback that runs on mainloop idle time and check for the flag, if TRUE then runs the dialog.
exit is dirty and will output errors but the goal ain't that, its just a tip/hint for your solution.
Example:
#include <gtk/gtk.h>
gboolean dialog_active;
gboolean show_dialog_popup;
GtkWidget *window;
gpointer DisplayOKPopup (gpointer user_data) {
int i;
while (TRUE) {
for (i = 0; i < G_MAXINT32; i++) {
// nop
}
show_dialog_popup = TRUE;
}
return NULL;
}
gboolean check_for_dialog (gpointer user_data) {
if (show_dialog_popup == TRUE && dialog_active == FALSE) {
dialog_active = TRUE;
GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_INFO,
GTK_BUTTONS_OK,
"text message");
gtk_dialog_run(GTK_DIALOG (dialog));
show_dialog_popup = FALSE;
dialog_active = FALSE;
gtk_widget_destroy(dialog);
}
return TRUE;
}
static void app_activate(GtkApplication *app, gpointer user_data) {
window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(window), "Window Title Here");
gtk_window_set_default_size(GTK_WINDOW(window), 700, 400);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
//gtk_container_add(GTK_CONTAINER(window), fixed);
gtk_widget_show_all(window);
g_idle_add(check_for_dialog, NULL);
g_thread_new("my thread", DisplayOKPopup, NULL);
}
int main(int argc, char **argv) {
GtkApplication *app;
int status;
show_dialog_popup = FALSE;
dialog_active = FALSE;
app = gtk_application_new("your.application.id", G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(app_activate), NULL);
status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}
Build and run:
$ gcc -o test test.c `pkg-config --cflags --libs gtk+-3.0`
$ ./test
Result:
EDIT:
Answer to your comment is:
To have custom buttons on your dialog then use:
gtk_dialog_new_with_buttons
gtk_dialog_add_button (if dialog exists, will add 1 button)
gtk_dialog_add_buttons (same as above but can add many buttons)
Example for 1):
dialog = gtk_dialog_new_with_buttons ("My dialog",
main_app_window,
flags,
"_OK",
GTK_RESPONSE_ACCEPT,
"_Cancel",
GTK_RESPONSE_REJECT,
NULL);
Example for 2):
gtk_dialog_add_button (GTK_DIALOG(dialog),
"This is my button",
GTK_RESPONSE_ACCEPT);
Example for 3) is same as 2) but can handle many buttons and terminates with NULL.

How to drag and drop multiple sprites in inspector in Unity3D

I have a script which handles many sprite arrays. I didn't want to drag them one by one so I wrote a CustomEditor for it that can allow me to assign multiple sprites at once using drag and drop operation:
[CustomEditor(typeof(MyMonoBehaviour))]
public class MyMonoBehaviourEditor : Editor
{
Sprite[] sprites;//actually Sprite[,][] but simplified here
Object[] DropAreaGUI()
{
Event evt = Event.current;
Rect drop_area = GUILayoutUtility.GetRect(0.0f, 20.0f, GUILayout.ExpandWidth(true));
GUI.Box(drop_area, "Drop here!");
switch (evt.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
if (!drop_area.Contains(evt.mousePosition))
return null;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
return DragAndDrop.objectReferences;
}
break;
}
return null;
}
void OnInspectorGUI(){
var drops = DropAreaGUI();
if (drops != null)
{
//the following line gives me error
sprites = drops.Select(x => (x as Sprite)).ToArray();
}
}
}
I lock the inspector, select 12 sprites from Project, drag them onto the box and when I drop them it gives me this error: ArgumentException: GUILayout: Mismatched LayoutGroup.DragPerform
I noticed that DragAndDrop.objectReferences returns an Object[] which is in my case a Texture2D[] and I can't cast it to Sprite[]. I tried Sprite.Create but it asks for rect and pivot which I don't have.
How can I make DragAndDrop recognize that I am dropping Sprites and not Texture2Ds?
If you're trying to assign multiple sprites to an array in the inspector, you can actually just drag and drop them onto the array itself.
What you'd typically do is select the asset you want to drag them into, then click the padlock in the upper-right of the inspector to lock it. Then you can select multiple objects and drag them onto the array itself (the name of the array with the drop-down menu) rather than into any particular slot of the array. Unity will then automatically populate the array with what you dropped there.
This works with arrays and lists (and possibly other container types, if unity displays them in the inspector).
I found the workaround.
instead of return DragAndDrop.objectReferences; I wrote return DragAndDrop.paths;
and then loading is possible:
sprites = drops.Select(x => AssetDatabase.LoadAssetAtPath<Sprite>(x)).ToArray();

GTK TextView auto scroll when text is added to text buffer

I am trying to create a very simple log-like GUI application that merely displays text from a log file dynamically and asynchronously. The problem is that when the log file is updated, the text view in the GUI scrolls back up to line 1. Every attempt to fix this has failed and I am wondering if I have stumbled across a bug in GTK. Here is a summary of my code:
using Cairo;
using Gtk;
namespace ServerManager {
public class ServerManager : Window {
public TextView text_view;
public TextIter myIter;
public TextMark myMark;
public async void read_something_async (File file) {
var text = new StringBuilder ();
var dis = new DataInputStream (file.read ());
string line;
while ((line = yield dis.read_line_async (Priority.DEFAULT)) != null) {
text.append (line);
text.append_c('\n');
}
this.text_view.buffer.text = text.str;
text_view.buffer.get_end_iter(out myIter);
text_view.scroll_to_iter(myIter, 0, false, 0, 0);
}
public static int main (string[] args) {
Gtk.init (ref args);
var window = new ServerManager ();
// The read-only TextView
window.text_view = new TextView ();
window.text_view.editable = false;
window.text_view.cursor_visible = false;
window.text_view.wrap_mode = Gtk.WrapMode.WORD;
// Add scrolling functionality to the TextView
var scroll = new ScrolledWindow (null, null);
scroll.set_policy (PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
scroll.add (window.text_view);
// Vbox so that our TextView has someplace to live
var vbox = new Box (Orientation.VERTICAL, 0);
vbox.pack_start (scroll, true, true, 0);
window.add (vbox);
window.set_border_width (12);
window.set_position (Gtk.WindowPosition.CENTER);
window.set_default_size (800, 600);
window.destroy.connect (Gtk.main_quit);
window.show_all ();
File file = File.new_for_path ("/home/user/temp.log");
FileMonitor monitor = file.monitor (FileMonitorFlags.NONE, null);
stdout.printf ("Monitoring: %s\n", file.get_path ());
monitor.changed.connect (() => {
window.read_something_async(file);
});
Gtk.main ();
return 0;
}
}
}
I also tried using TextMarks instead of Iters but that had no affect.
Scroll to 1st row happens because read_something_async() deletes the current contents of the buffer and then writes the new one (this is what setting the text property does). Maybe this is what you want but unless you keep track of the scroll location you will lose it.
The reason your scroll_to_iter() didn't work as expected is probably this:
Note that this function uses the currently-computed height of the lines in the text buffer. Line heights are computed in an idle handler; so this function may not have the desired effect if it’s called before the height computations. To avoid oddness, consider using gtk_text_view_scroll_to_mark() which saves a point to be scrolled to after line validation.
Calling TextView.ScrollToMark() with a "right gravity" TextMark should work for you.

Updating a gtk_tree_view from pthreads

I've been trying to figure out some method to cause a GtkTreeView to redraw after I update the bound GtkListStore from a background thread created with pthreads.
Generally, the widget does not update until something obscures an existing row (even a mouse cursor ).
Most of my searches for this problem has "your tree model doesn't/isn't generating the correct signals" ....
I'm running an old Red Hat 9 with gtk+ 2.0.0, for industrial embedded applications. Most of the data comes from ipc/socket/pipes and gets displayed by a GTK app. Unfortunately so does CRITICAL alarms, which has a habit of not showing when they should. We will (one day) move to a current kernel, but I need to get something working with the existing software.
I've tried emiting the "row-changed" signals, tried calling the gtk_widget_queue_draw and also tried connecting to the "expose-event", where I've tried various things that don't work or seg fault.
server.c
bool Server::Start()
{
// ....
// pthread_t _id;
//
pthread_create( & _id, NULL, &StaticServerThread, this );
// ....
}
viewer.c
bool Viewer::ReadFinished( SocketArgs * args )
{
gdk_threads_enter();
// Populate the buffer and message
//
// GtkListStore *_outputStore;
// gchar *buffer;
// gchar *message;
GtkTreeIter iter;
gtk_list_store_insert_with_values( _outputStore, &iter, 0,
0, buffer, 1, message, -1 );
// ....
gdk_threads_leave();
}
You can perform the updates to the list store in the main thread. For example, you can use g_idle_add() in the worker thread.