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

Linux Nested Sub Menu #21

Open
cranst0n opened this issue Dec 12, 2022 · 0 comments
Open

Linux Nested Sub Menu #21

cranst0n opened this issue Dec 12, 2022 · 0 comments

Comments

@cranst0n
Copy link

I had a need to have a menu on Linux with a menu with a sub menu nested inside a sub menu. I'm not proficient in C++ so after I fumbled around for a while, I ultimately came up with this solution. I'll let it in this issue since I doubt this hackery is worthy of a PR. All edits were made inside native_context_menu_plugin_handle_method_call:

static void native_context_menu_plugin_handle_method_call(
    NativeContextMenuPlugin* self, FlMethodCall* method_call) {
  g_autoptr(FlMethodResponse) response = nullptr;
  const gchar* method = fl_method_call_get_name(method_call);
  if (strcmp(method, kShowMenu) == 0) {
    // Clear previously saved object instances.
    self->last_menu_items.clear();
    self->last_menu_item_selected = false;
    if (self->last_menu_thread != nullptr) {
      self->last_menu_thread->detach();
      self->last_menu_thread.reset(nullptr);
    }
    auto arguments = fl_method_call_get_args(method_call);
    auto device_pixel_ratio =
        fl_value_lookup_string(arguments, "devicePixelRatio");

    auto position = fl_value_lookup_string(arguments, "position");

    GdkWindow* window = get_window(self);

    GtkWidget* top_level_menu = gtk_menu_new();
    auto top_level_items = fl_value_lookup_string(arguments, "items");

    for (int32_t i = 0; i < fl_value_get_length(top_level_items); i++) {

      int32_t top_level_id = fl_value_get_int(
          fl_value_lookup_string(fl_value_get_list_value(top_level_items, i), "id"));
      const char* top_level_title = fl_value_get_string(
          fl_value_lookup_string(fl_value_get_list_value(top_level_items, i), "title"));

      auto second_level_items =
          fl_value_lookup_string(fl_value_get_list_value(top_level_items, i), "items");

      self->last_menu_items.emplace_back(std::make_unique<MenuItem>(top_level_id, top_level_title));

      GtkWidget* top_level_item = gtk_menu_item_new_with_label(top_level_title);

      // Check for second level items & create a second level menu.
      if (fl_value_get_length(second_level_items) > 0) {

        GtkWidget* second_level_menu = gtk_menu_new();

        for (int32_t j = 0; j < fl_value_get_length(second_level_items); j++) {

          int32_t second_level_id = fl_value_get_int(fl_value_lookup_string(
              fl_value_get_list_value(second_level_items, j), "id"));
          const char* second_level_title = fl_value_get_string(fl_value_lookup_string(
              fl_value_get_list_value(second_level_items, j), "title"));

          auto third_level_items =
            fl_value_lookup_string(fl_value_get_list_value(second_level_items, j), "items");

          self->last_menu_items.back()->items().emplace_back(
              std::make_unique<MenuItem>(second_level_id, second_level_title));

          GtkWidget* second_level_item = gtk_menu_item_new_with_label(second_level_title);

          // Check for third level items & create a third level menu.
          if (fl_value_get_length(third_level_items) > 0) {
            GtkWidget* third_level_menu = gtk_menu_new();

            for (int32_t k = 0; k < fl_value_get_length(third_level_items); k++) {

              int32_t third_level_id = fl_value_get_int(fl_value_lookup_string(
                  fl_value_get_list_value(third_level_items, k), "id"));
              const char* third_level_title = fl_value_get_string(fl_value_lookup_string(
                  fl_value_get_list_value(third_level_items, k), "title"));

              self->last_menu_items.back()->items().back()->items().emplace_back(
                std::make_unique<MenuItem>(third_level_id, third_level_title));

              GtkWidget* third_level_item = gtk_menu_item_new_with_label(third_level_title);

              gtk_widget_show(third_level_item);
              gtk_menu_shell_append(GTK_MENU_SHELL(third_level_menu), third_level_item);

              g_signal_connect(
                  G_OBJECT(third_level_item), "activate",
                  G_CALLBACK(on_menu_item_clicked),
                  (gpointer)self->last_menu_items.back()->items().at(j).get()->items().at(k).get());
            }

            gtk_menu_item_set_submenu(GTK_MENU_ITEM(second_level_item), third_level_menu);

          } else {
            g_signal_connect(G_OBJECT(second_level_item), "activate",
                            G_CALLBACK(on_menu_item_clicked),
                            (gpointer)self->last_menu_items.back()->items().at(j).get());
          }

          gtk_widget_show(second_level_item);
          gtk_menu_shell_append(GTK_MENU_SHELL(second_level_menu), second_level_item);

        }

        gtk_menu_item_set_submenu(GTK_MENU_ITEM(top_level_item), second_level_menu);

      } else {
        g_signal_connect(G_OBJECT(top_level_item), "activate",
                         G_CALLBACK(on_menu_item_clicked),
                         (gpointer)self->last_menu_items.back().get());
      }

      gtk_widget_show(top_level_item);
      gtk_menu_shell_append(GTK_MENU_SHELL(top_level_menu), top_level_item);
    }

    GdkRectangle rectangle;
    // Pass `devicePixelRatio` and `position` from Dart to show menu at
    // specified coordinates. If it is not defined, WIN32 will use
    // `GetCursorPos` to show the context menu at the cursor's position.
    if (device_pixel_ratio != nullptr && position != nullptr) {
      rectangle.x = fl_value_get_float(fl_value_get_list_value(position, 0)) *
                    fl_value_get_float(device_pixel_ratio);
      rectangle.y = fl_value_get_float(fl_value_get_list_value(position, 1)) *
                    fl_value_get_float(device_pixel_ratio);
    } else {
      GdkDevice* mouse_device;
      int x, y;
      // Legacy support.
#if GTK_CHECK_VERSION(3, 20, 0)
      GdkSeat* seat = gdk_display_get_default_seat(gdk_display_get_default());
      mouse_device = gdk_seat_get_pointer(seat);
#else
      GdkDeviceManager* devman =
          gdk_display_get_device_manager(gdk_display_get_default());
      mouse_device = gdk_device_manager_get_client_pointer(devman);
#endif
      gdk_window_get_device_position(window, mouse_device, &x, &y, NULL);
      rectangle.x = x;
      rectangle.y = y;
    }
    g_signal_connect(G_OBJECT(top_level_menu), "deactivate",
                     G_CALLBACK(on_menu_deactivated), nullptr);
    // `gtk_menu_popup_at_rect` is used since `gtk_menu_popup_at_pointer` will
    // require event box creation & another callback will be involved. This way
    // is straight forward & easy to work with.
    // NOTE: GDK_GRAVITY_NORTH_WEST is hard-coded by default since no analog is
    // present for it inside the Dart platform channel code (as of now). In
    // summary, this will create a menu whose body is in bottom-right to the
    // position of the mouse pointer.
    gtk_menu_popup_at_rect(GTK_MENU(top_level_menu), window, &rectangle,
                           GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_WEST,
                           NULL);

    // Responding with `null`, click event & respective `id` of the `MenuItem`
    // is notified through callback. Otherwise the GUI will become unresponsive.
    // To keep the API same, a `Completer` is used in the Dart.
    response =
        FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_null()));
  } else {
    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
  }
  fl_method_call_respond(method_call, response, nullptr);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant