equodev / sdk

https://docs.equo.dev
MIT License
1 stars 0 forks source link

Define what to do for application menus #21

Open guidomodarelli opened 6 months ago

guidomodarelli commented 6 months ago

Decide whether to delegate to the application or provide an API

[!TIP] Create design document: It would be beneficial for you to create a design document before starting to write code, specifying what and where we would make changes. This includes the classes to be modified, the placement of menus/commands and their execution, as well as how we would allow adding new things there.

The contextual menu modified by @agustinschilling is similar; it has identifiers for static commands, but it also has configurable dynamic command identifiers. I recall that the documentation says something like there is a range of identifiers that the user can use, and below that range are the standard Chrome ones. The identifiers shown here

Image

are the ones we should use.

guidomodarelli commented 6 months ago

1. DESIGN DOCUMENT: DYNAMICALLY ADDING CUSTOM COMMANDS TO THE APPLICATION MENU IN CHROMIUM

1.1. SUMMARY

This document describes how to dynamically add custom commands to the application menu in Chromium. It covers the files and classes that we would need to modify, the placement of menus and commands, and how we would allow adding new entries to the menu at runtime.

1.2. FILES AND CLASSES TO MODIFY

The main files and classes we need to modify are:

  1. //chrome/browser/ui/toolbar/app_menu_model.cc: This class defines the application menu entries. We need to modify the Build() function to add our own entries to the menu.
  2. //chrome/app/chromium_strings.grd: This file defines the text strings displayed in the user interface. We need to add our own strings for the new menu entries.
  3. //chrome/browser/ui/views/toolbar/app_menu.cc: This class defines how the application menu is displayed and how user interactions with it are handled. We need to modify the ExecuteCommand() function to define what happens when the user selects our new menu entries.

The reason you need to modify ExecuteCommand() in chrome/browser/ui/views/toolbar/app_menu.cc instead of chrome/browser/ui/toolbar/app_menu_model.cc is due to how the code is organized in Chromium.

In Chromium, AppMenuModel (defined in app_menu_model.cc) is responsible for building the application menu, that is, defining which items appear in the menu and in what order. However, AppMenuModel is not responsible for handling the actions that occur when a menu item is selected. That responsibility falls on the AppMenu class (defined in app_menu.cc).

Therefore, if you want to change what happens when a menu item is selected, you need to modify AppMenu::ExecuteCommand(). This function is called when a menu item is selected, and its job is to determine what action should be taken based on the command identifier of the menu item.

On the other hand, if you only want to change the items that appear in the menu, but not what happens when they are selected, then you would only need to modify AppMenuModel::Build().

1.3. DYNAMICALLY ADDING NEW ENTRIES TO THE MENU

To dynamically add new entries to the application menu, we need to modify the Build() function in AppMenuModel to query a list of custom commands that can be updated at runtime. Here's an example of how we could do this:

void AppMenuModel::Build() {
  // ... existing code to build the menu ...

  // Add custom entries to the menu.
  for (const auto& command : custom_commands_) {
    AddItem(command.id, l10n_util::GetStringUTF16(command.string_id));
  }

  // ... more code to build the rest of the menu ...
}

In this example, custom_commands_ is a list of structures representing the custom commands. Each structure has an id that is a unique identifier for the command, and a string_id that is an identifier for the text string to be displayed in the menu.

1.3.1. WHAT IS THE STRUCTURE OF custom_commands_?

Below is an example of a possible implementation:

struct CustomCommand {
  int id;  // The unique identifier of the command.
  int string_id;  // The identifier of the text string to be displayed in the menu.
  base::RepeatingCallback<void()> callback;  // The function to be called when the command is selected.
};

std::vector<CustomCommand> custom_commands_;

In this example, CustomCommand is a structure representing a custom command. Each command has a unique identifier, a string identifier, and a callback function that will be invoked when the command is selected.

custom_commands_ is a list of these custom commands. It could be defined as a member of the AppMenuModel class, so that it's available in the Build() and ExecuteCommand() functions.

To add a new command to the list, you could do something like this:

custom_commands_.push_back({IDC_MY_CUSTOM_COMMAND, IDS_MY_CUSTOM_COMMAND, base::BindRepeating(&MyApplication::HandleMyCustomCommand, base::Unretained(my_application))});

In this example, IDC_MY_CUSTOM_COMMAND and IDS_MY_CUSTOM_COMMAND are the command and string identifiers respectively, and MyApplication::HandleMyCustomCommand is the function that will be called when the command is selected. my_application is an instance of the MyApplication class that contains the HandleMyCustomCommand function.

1.3.1.1. WHERE SHOULD IT BE DEFINED?

The definition of the CustomCommand structure and the declaration of the custom_commands_ class member should go in the app_menu_model.h header file.

An example of how we could do this:

// In app_menu_model.h

#include "base/callback.h"
#include <vector>

// ... other includes ...

class AppMenuModel {
 public:
  // ... other class declarations ...

  struct CustomCommand {
    int id;  // The unique identifier of the command.
    int string_id;  // The identifier of the text string to be displayed in the menu.
    base::RepeatingCallback<void()> callback;  // The function to be called when the command is selected.
  };

  // ... more class declarations ...

 private:
  // ... other class declarations ...

  std::vector<CustomCommand> custom_commands_;

  // ... more class declarations ...
};

1.4. DEFINING TEXT STRINGS

To define text strings for our new menu entries, we need to modify the chromium_strings.grd file to add new strings as needed. However, since we are adding commands dynamically, we also need a way to add text strings at runtime.

One way to do this would be to have a function in our application that can generate unique string identifiers and register new strings with those identifiers. Then, we could use these identifiers when adding our entries to the menu.

class MyApplication {
public:
  // Registers a new string and returns its identifier.
  int RegisterString(const std::string& str) {
    int id = NextStringId();
    // Registers the string with the new identifier.
    string_resources_[id] = str;
    return id;
  }

  // Retrieves a registered string.
  std::string GetString(int id) {
    auto it = string_resources_.find(id);
    if (it != string_resources_.end()) {
      return it->second;
    } else {
      // Handles the case where `id` is not found in `string_resources_`.
      // You could throw an exception, return an empty string, etc.
      return std::string();
    }
  }

private:
  // Returns the next available unique string identifier.
  int NextStringId() {
    // We need to ensure this identifier doesn't conflict with any existing string identifiers.
    return next_string_id_++;
  }

  // `next_string_id_` keeps track of the next available string identifier.
  int next_string_id_ = IDS_CONTENT_CONTEXT_CUSTOM_FIRST;
  std::map<int, std::string> string_resources_;
};

In the pseudocode, string_resources_ is a map from string identifiers to strings. When we register a new string with RegisterString(), we add it to this map. Then, when we need to retrieve the string, we look it up in the map with GetString().

1.5. HANDLING USER INTERACTIONS

To define what happens when the user selects our new menu entries, we need to modify the ExecuteCommand() function in AppMenu. Here's an example of how we could do this:

void AppMenu::ExecuteCommand(int command_id, int event_flags) {
  // ... existing code to handle existing commands ...

  // Handle custom commands.
  for (const auto& command : custom_commands_) {
    if (command_id == command.id) {
      // Here goes the code to handle the custom command.
      return;
    }
  }

  // ... more code to handle the rest of the commands ...
}

In this example, custom_commands_ is the same list of custom commands we use in Build().

1.6. ALLOWING ADDITION OF NEW MENU ENTRIES

To allow the addition of new entries to the menu at runtime, we could add a new function to AppMenuModel that allows adding commands to the custom_commands_ list. Here's an example of how we could do this:

void AppMenuModel::AddCustomCommand(int command_id, int string_id, base::RepeatingCallback<void()> callback) {
  custom_commands_.push_back({command_id, string_id, std::move(callback)});
  // Rebuilds the menu to reflect the changes.
  Build();
}

In this example, callback is an additional parameter passed to AddCustomCommand. Then, when we add the new command to custom_commands_, we move the callback to the new command with std::move(callback). This is to avoid unnecessary copies of the callback, as callbacks can be heavyweight objects.

Then, to add a new command, you could do something like this:

app_menu_model.AddCustomCommand(IDC_MY_CUSTOM_COMMAND, IDS_MY_CUSTOM_COMMAND, base::BindRepeating(&MyApplication::HandleMyCustomCommand, base::Unretained(my_application)));

In this example, MyApplication::HandleMyCustomCommand is the function that will be called when the command is selected.