Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SWT org.eclipse.swt.widgets.Menu: a very tall menu exceeding the screen when scrolled has the effect to blank the neighboring menus #2349

Open
backwindj opened this issue Oct 1, 2024 · 11 comments
Labels
bug Something isn't working Linux Happens on Linux

Comments

@backwindj
Copy link

Hello,

Issue description:
In a GUI application made with "Eclipse IDE for RCP and RAP Developers" on Linux Fedora 40, I have a menu attached to a tree. For one of the TreeItems, the menu has a very large number of MenuItems and consequently exceeds the screen height. For the next TreeItem, the menu has only four MenuItems. If I open and close the menus of these two TreeItems, the rendering of both is perfectly fine.

If I open the large menu then scroll inside it with the mouse to see off-screen MenuItems then close the large menu and then open the small menu next to it, the small menu is rendered as 4 four blank lines plus a top handle as if it was a menu exceeding the screen. If I scroll down long enough inside the small menu, the four expected MenuItems finally will appear, the top handle
is not shown any more and scrolling in the small menu has no effect any more.

I verified I can reproduce this issue against Eclipse Version: 2024-09 (4.33.0) Build id: 20240905-0614

I did not expect the small menu to be blank and to have to scroll inside it.

I understand reporting an issue to this OSS project does not mandate anyone to fix it. Other contributors may consider the issue, or not, at their own convenience. The most efficient way to get it fixed is that I fix it myself and contribute it back as a good quality patch to the project.

Best regards,
François

@backwindj backwindj added the bug Something isn't working label Oct 1, 2024
@iloveeclipse iloveeclipse added the Linux Happens on Linux label Oct 1, 2024
@iloveeclipse
Copy link
Member

Screenshot would help. I believe it's a known GTK issue we reported some years ago to GTK, @trancexpress ?

@backwindj
Copy link
Author

Copie d'écran_20241001_220639

@backwindj
Copy link
Author

I will rework my menu to fit the screen.

@trancexpress
Copy link
Contributor

Screenshot would help. I believe it's a known GTK issue we reported some years ago to GTK, @trancexpress ?

We had/have: https://bugs.eclipse.org/bugs/show_bug.cgi?id=564910

Without a video its hard to tell if its the same issue.

@backwindj
Copy link
Author

vokoscreenNG-2024-10-02_22-06-18.mp4

The issue is shown twice in this recording.

Best regards,
François

@trancexpress
Copy link
Contributor

We've not ran into this issue yet. If I find time I'll try to come up with a GTK3 repoducer. Maybe its again some weird combo behavior (menus share code with combo drop downs) that is supposed to make to make a combo entry easy to click (due to mouse pointer proximity)...

@trancexpress
Copy link
Contributor

I tried a few reproducers, I couldn't reproduce the problem with GTK3.

Next step here should be an SWT snippet that reproduces the problem, maybe with that we can reduce to a GTK3 snippet. I doubt its an SWT bug, I assume its some GTK3 bug.

@backwindj
Copy link
Author

Hello,

I have verified that the issue was not due to X11 but the behaviour is exactly the same with Wayland.

Best regards,
François

@backwindj
Copy link
Author

Hello,

Please find below a code snippet reproducing the issue.

package org.eclipse.swt.snippets;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MenuListener;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

public class Snippet
{
   public static void main (final String [] args)
   {
      final var display = new Display ();

      final var shell = new Shell (display);

      shell.setText ("Snippet");
      shell.setLayout (new FillLayout ());

      final var tree = new Tree (shell, SWT.FULL_SELECTION);

      final var menu = new Menu (tree);

      tree.setMenu (menu);

      menu.addMenuListener (MenuListener.menuShownAdapter (e -> build_menu (tree, menu)));

      final var nb_menu_entry = new int [] {4, 80, 8};

      for (int i = 0; i < 2; i++)
      {
         final var item_I = new TreeItem (tree, SWT.NONE);

         item_I.setText ("Level 1 - " + i);

         for (int j = 0; j < nb_menu_entry.length; j++)
         {
            final var item_J = new TreeItem (item_I, SWT.NONE);

            item_J.setText ("Level 2 - " + j);

            item_J.setData (Integer.valueOf (nb_menu_entry [j]));
         }
      }

      shell.setSize (800, 600);
      shell.open ();

      while (! shell.isDisposed ())
      {
         if (! display.readAndDispatch ())
         {
            display.sleep ();
         }
      }

      display.dispose ();
   }

   static private void build_menu (final Tree tree, final Menu menu)
   {
      final TreeItem [] selection = tree.getSelection ();

      if (selection.length != 0)
      {
         final TreeItem tree_item = selection [0];

         final var nb_entry = (Integer) tree_item.getData ();

         clear_menu (menu);

         if (nb_entry != null)
         {
            for (int i = 0; i < nb_entry.intValue (); i++)
            {
               final var item = new MenuItem (menu, SWT.PUSH);

               item.setText ("Menu entry " + i);

               item.addSelectionListener (SelectionListener.widgetSelectedAdapter (e -> System.out.println ("Coucou")));
            }
         }
      }
   }

   static private void clear_menu (final Menu menu)
   {
      for (final MenuItem menu_item : menu.getItems ())
      {
         final Menu child = menu_item.getMenu ();

         if (child != null)
         {
            clear_menu (child);
         }

         menu_item.dispose ();
      }
   }

}

Best regards,
François

@trancexpress
Copy link
Contributor

The behavior is reproducible with this GTK+ snippet:

// gcc -g menu_scroll_bug2.c  `pkg-config --cflags --libs gtk+-3.0`  -o MenuScrollBug && ./MenuScrollBug

#include <stdio.h>
#include <gtk/gtk.h>


static int menu_item_count = 60;

static GtkWidget *text;
static GtkWidget *menu;
static GtkWidget *tmp_item;

static gboolean b = TRUE;

void view_popup_menu_onDoSomething (GtkWidget *menuitem, gpointer userdata)
{
  g_print ("menu item pressed!\n");
}

void view_popup_menu (GtkWidget *widget, GdkEventButton *event, gpointer userdata)
{
  GList *children = gtk_container_get_children(GTK_CONTAINER(menu));
  GList *l;
  for (l = children; l; l = l->next)
  {
    gtk_container_remove (GTK_CONTAINER (menu), GTK_WIDGET (l->data));
  }

  if (b)
  {
    GtkWidget *menuitem;
    char buf[128];
    for (int i = 0; i < menu_item_count; ++i)
    {
      sprintf (buf, "menu 1 item %d", i);
      menuitem = gtk_menu_item_new_with_label(buf);
      g_signal_connect(menuitem, "activate", (GCallback) view_popup_menu_onDoSomething, text);
      gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
      gtk_widget_show(menuitem);
    }
  }
  else
  {
    GtkWidget *menuitem;
    char buf[128];
    for (int i = 0; i < 5; ++i)
    {
      sprintf (buf, "menu 2 item %d", i);
      menuitem = gtk_menu_item_new_with_label(buf);
      g_signal_connect(menuitem, "activate", (GCallback) view_popup_menu_onDoSomething, text);
      gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
      gtk_widget_show(menuitem);
    }
  }
  gtk_widget_show_all(menu);
  gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
  b = !b;
}

gboolean view_onPopupMenu (GtkWidget *widget, gpointer userdata)
{
  view_popup_menu(widget, NULL, userdata);
  return TRUE;
}


gboolean view_onButtonPressed (GtkWidget *widget, GdkEventButton *event, gpointer userdata)
{
  if (event->type == GDK_BUTTON_PRESS && event->button == 3)
  {
    view_popup_menu(widget, event, userdata);
    return TRUE;
  }
  return FALSE;
}

gboolean view_onKeyPressed (GtkWidget *widget, GdkEventButton *event, gpointer userdata)
{
  if (event->type == GDK_KEY_PRESS)
  {
    if (tmp_item != NULL)
    {
      gtk_widget_destroy(tmp_item);
      tmp_item = NULL;
    }
    else
    {
      tmp_item = gtk_menu_item_new_with_label("temp item");
      g_signal_connect(tmp_item, "activate", (GCallback) view_popup_menu_onDoSomething, text);
      gtk_menu_shell_append(GTK_MENU_SHELL(menu), tmp_item);
      gtk_widget_show(tmp_item);
    }
    return TRUE;
  }
  return FALSE;
}

int main(int argc, char *argv[]) {
  GtkWidget *window, *scrolled_window;
  GtkTextBuffer *buffer;
  GtkTextIter iter;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 400, 500);
  gtk_window_set_title(GTK_WINDOW(window), "Menu scroll bug");
  g_signal_connect(window, "delete_event", gtk_main_quit, NULL);

  text = gtk_text_view_new();
  menu = gtk_menu_new();

  scrolled_window = gtk_scrolled_window_new (NULL, NULL);

  gtk_container_add(GTK_CONTAINER(window), scrolled_window);

  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));

  gtk_text_buffer_get_iter_at_offset(buffer, &iter, 0);

  char buf[128];
  for (int i = 0; i < 120; ++i)
  {
    sprintf (buf, "row %d - abcdefghijklmnopqrstuvwxyz0123456789\n", i);
    gtk_text_buffer_insert(buffer, &iter, buf, -1);
  }

  gtk_container_add(GTK_CONTAINER(scrolled_window), text);

  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

  g_signal_connect(text, "button-press-event", G_CALLBACK(view_onButtonPressed), NULL);
  g_signal_connect(text, "popup-menu", G_CALLBACK(view_onPopupMenu), NULL);
  g_signal_connect (text, "key-press-event", G_CALLBACK(view_onKeyPressed), NULL);

  gtk_widget_show_all(window);

  gtk_main();
}

I assume when reusing the same menu, the scroll state is preserved despite removing items. I don't find any option in SWT to scroll the menu programmatically.

@backwindj are you able to use several SWT menu objects? Likely this will be the easiest option for a workaround. I assume the GTK3 behavior here is expected, to fix this behavior some sort of a workaround will be needed.

@backwindj
Copy link
Author

Hello,

I think I have found a general workaround with only one menu. The menu has to be disposed and recreated when closed:

package org.eclipse.swt.snippets;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MenuListener;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

public class Snippet
{
   public static void main (String [] args)
   {
      final var display = new Display ();

      final var shell = new Shell (display);

      shell.setText ("Snippet");
      shell.setLayout (new FillLayout ());

      final var tree = new Tree (shell, SWT.FULL_SELECTION);

      final var menu = new Menu (tree);

      menu.addMenuListener (MenuListener.menuShownAdapter (e -> build_menu (tree)));

      tree.setMenu (menu);

      final var nb_menu_entry = new int [] {4, 80, 8};

      for (int i = 0; i < 2; i++)
      {
         final var item_I = new TreeItem (tree, SWT.NONE);

         item_I.setText ("Level 1 - " + i);

         for (int j = 0; j < nb_menu_entry.length; j++)
         {
            final var item_J = new TreeItem (item_I, SWT.NONE);

            item_J.setText ("Level 2 - " + j);

            item_J.setData (Integer.valueOf (nb_menu_entry [j]));
         }
      }

      shell.setSize (800, 600);
      shell.open ();

      while (! shell.isDisposed ())
      {
         if (! display.readAndDispatch ())
         {
            display.sleep ();
         }
      }

      display.dispose ();
   }

   static private void build_menu (final Tree tree)
   {
      final TreeItem [] selection = tree.getSelection ();

      if (selection.length != 0)
      {
         final TreeItem tree_item = selection [0];

         final var nb_entry = (Integer) tree_item.getData ();

         if (nb_entry != null)
         {
            final Menu menu = tree.getMenu ();

            clear_menu (menu);

            menu.addMenuListener (MenuListener.menuHiddenAdapter (e -> dispose_menu (tree)));

            for (int i = 0; i < nb_entry.intValue (); i++)
            {
               final var item = new MenuItem (menu, SWT.PUSH);

               item.setText ("Menu entry " + i);

               item.addSelectionListener (SelectionListener.widgetSelectedAdapter (e -> System.out.println ("Coucou")));
            }
         }
      }
   }

   static private void clear_menu (final Menu menu)
   {
      for (final MenuItem menu_item : menu.getItems ())
      {
         final Menu child = menu_item.getMenu ();

         if (child != null)
         {
            clear_menu (child);
         }

         menu_item.dispose ();
      }
   }

   static private void dispose_menu (final Tree tree)
   {
      tree.getMenu ().dispose ();

      final var menu = new Menu (tree);

      menu.addMenuListener (MenuListener.menuShownAdapter (e -> build_menu (tree)));

      tree.setMenu (menu);
   }

}

Best regards,
François

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Linux Happens on Linux
Projects
None yet
Development

No branches or pull requests

3 participants