BMenu: implement scripting

Fixes #15540.

Change-Id: I965d04bcbb6db41cc7a744d18fa3c9ffab2a81f1
Reviewed-on: https://review.haiku-os.org/c/haiku/+/2941
Reviewed-by: Adrien Destugues <pulkomandy@gmail.com>
This commit is contained in:
X512 2020-06-22 04:36:41 +09:00 committed by waddlesplash
parent abbd654ba9
commit 8fbce87334
2 changed files with 331 additions and 28 deletions

View File

@ -192,6 +192,14 @@ private:
bool keyDown = false);
void _Hide();
BMenuItem* _Track(int* action, long start = -1);
void _ScriptReceived(BMessage* message);
void _ItemScriptReceived(BMessage* message,
BMenuItem* item);
status_t _ResolveItemSpecifier(const BMessage& specifier,
int32 what, BMenuItem*& item);
status_t _InsertItemAtSpecifier(
const BMessage& specifier, int32 what,
BMenuItem* item);
void _UpdateNavigationArea(BPoint position,
BRect& navAreaRectAbove,

View File

@ -41,6 +41,7 @@
#include <Window.h>
#include <AppServerLink.h>
#include <AutoDeleter.h>
#include <binary_compatibility/Interface.h>
#include <BMCPrivate.h>
#include <MenuPrivate.h>
@ -453,6 +454,9 @@ BMenu::Draw(BRect updateRect)
void
BMenu::MessageReceived(BMessage* message)
{
if (message->HasSpecifiers())
return _ScriptReceived(message);
switch (message->what) {
case B_MOUSE_WHEEL_CHANGED:
{
@ -1169,34 +1173,8 @@ BMenu::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
BPropertyInfo propInfo(sPropList);
BHandler* target = NULL;
switch (propInfo.FindMatch(msg, 0, specifier, form, property)) {
case B_ERROR:
break;
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
target = this;
break;
case 8:
// TODO: redirect to menu
target = this;
break;
case 9:
case 10:
case 11:
case 12:
target = this;
break;
case 13:
// TODO: redirect to menuitem
target = this;
break;
if (propInfo.FindMatch(msg, index, specifier, form, property) >= B_OK) {
target = this;
}
if (!target)
@ -1619,6 +1597,323 @@ BMenu::_Hide()
}
void BMenu::_ScriptReceived(BMessage* message)
{
BMessage replyMsg(B_REPLY);
status_t err = B_BAD_SCRIPT_SYNTAX;
int32 index;
BMessage specifier;
int32 what;
const char* property;
if (message->GetCurrentSpecifier(&index, &specifier, &what, &property)
!= B_OK) {
return BView::MessageReceived(message);
}
BPropertyInfo propertyInfo(sPropList);
switch (propertyInfo.FindMatch(message, index, &specifier, what,
property)) {
case 0: // Enabled: GET
if (message->what == B_GET_PROPERTY)
err = replyMsg.AddBool("result", IsEnabled());
break;
case 1: // Enabled: SET
if (message->what == B_SET_PROPERTY) {
bool isEnabled;
err = message->FindBool("data", &isEnabled);
if (err >= B_OK)
SetEnabled(isEnabled);
}
break;
case 2: // Label: GET
case 3: // Label: SET
case 4: // Mark: GET
case 5: { // Mark: SET
BMenuItem *item = Superitem();
if (item != NULL)
return Supermenu()->_ItemScriptReceived(message, item);
break;
}
case 6: // Menu: CREATE
if (message->what == B_CREATE_PROPERTY) {
const char *label;
ObjectDeleter<BMessage> invokeMessage(new BMessage());
BMessenger target;
ObjectDeleter<BMenuItem> item;
err = message->FindString("data", &label);
if (err >= B_OK) {
invokeMessage.SetTo(new BMessage());
err = message->FindInt32("what",
(int32*)&invokeMessage->what);
if (err == B_NAME_NOT_FOUND) {
invokeMessage.Unset();
err = B_OK;
}
}
if (err >= B_OK) {
item.SetTo(new BMenuItem(new BMenu(label),
invokeMessage.Detach()));
}
if (err >= B_OK) {
err = _InsertItemAtSpecifier(specifier, what, item.Get());
}
if (err >= B_OK)
item.Detach();
}
break;
case 7: { // Menu: DELETE
if (message->what == B_DELETE_PROPERTY) {
BMenuItem *item = NULL;
err = _ResolveItemSpecifier(specifier, what, item);
if (err >= B_OK) {
if (item->Submenu() == NULL)
err = B_BAD_VALUE;
else
RemoveItem(item);
}
}
break;
}
case 8: { // Menu: *
// TODO: check that submenu looper is running and handle it
// correctly
BMenu *submenu = NULL;
BMenuItem *item;
err = _ResolveItemSpecifier(specifier, what, item);
if (err >= B_OK)
submenu = item->Submenu();
if (submenu != NULL) {
message->PopSpecifier();
return submenu->_ScriptReceived(message);
}
break;
}
case 9: // MenuItem: COUNT
if (message->what == B_COUNT_PROPERTIES)
err = replyMsg.AddInt32("result", CountItems());
break;
case 10: // MenuItem: CREATE
if (message->what == B_CREATE_PROPERTY) {
const char *label;
ObjectDeleter<BMessage> invokeMessage(new BMessage());
bool targetPresent = true;
BMessenger target;
ObjectDeleter<BMenuItem> item;
err = message->FindString("data", &label);
if (err >= B_OK) {
err = message->FindMessage("be:invoke_message",
invokeMessage.Get());
if (err == B_NAME_NOT_FOUND) {
err = message->FindInt32("what",
(int32*)&invokeMessage->what);
if (err == B_NAME_NOT_FOUND) {
invokeMessage.Unset();
err = B_OK;
}
}
}
if (err >= B_OK) {
err = message->FindMessenger("be:target", &target);
if (err == B_NAME_NOT_FOUND) {
targetPresent = false;
err = B_OK;
}
}
if (err >= B_OK) {
item.SetTo(new BMenuItem(label, invokeMessage.Detach()));
if (targetPresent)
err = item->SetTarget(target);
}
if (err >= B_OK) {
err = _InsertItemAtSpecifier(specifier, what, item.Get());
}
if (err >= B_OK)
item.Detach();
}
break;
case 11: // MenuItem: DELETE
if (message->what == B_DELETE_PROPERTY) {
BMenuItem *item = NULL;
err = _ResolveItemSpecifier(specifier, what, item);
if (err >= B_OK)
RemoveItem(item);
}
break;
case 12: { // MenuItem: EXECUTE
if (message->what == B_EXECUTE_PROPERTY) {
BMenuItem *item = NULL;
err = _ResolveItemSpecifier(specifier, what, item);
if (err >= B_OK) {
if (!item->IsEnabled())
err = B_NOT_ALLOWED;
else
err = item->Invoke();
}
}
break;
}
case 13: { // MenuItem: *
BMenuItem *item = NULL;
err = _ResolveItemSpecifier(specifier, what, item);
if (err >= B_OK) {
message->PopSpecifier();
return _ItemScriptReceived(message, item);
}
break;
}
default:
return BView::MessageReceived(message);
}
if (err != B_OK) {
replyMsg.what = B_MESSAGE_NOT_UNDERSTOOD;
if (err == B_BAD_SCRIPT_SYNTAX)
replyMsg.AddString("message", "Didn't understand the specifier(s)");
else
replyMsg.AddString("message", strerror(err));
}
replyMsg.AddInt32("error", err);
message->SendReply(&replyMsg);
}
void BMenu::_ItemScriptReceived(BMessage* message, BMenuItem* item)
{
BMessage replyMsg(B_REPLY);
status_t err = B_BAD_SCRIPT_SYNTAX;
int32 index;
BMessage specifier;
int32 what;
const char* property;
if (message->GetCurrentSpecifier(&index, &specifier, &what, &property)
!= B_OK) {
return BView::MessageReceived(message);
}
BPropertyInfo propertyInfo(sPropList);
switch (propertyInfo.FindMatch(message, index, &specifier, what,
property)) {
case 0: // Enabled: GET
if (message->what == B_GET_PROPERTY)
err = replyMsg.AddBool("result", item->IsEnabled());
break;
case 1: // Enabled: SET
if (message->what == B_SET_PROPERTY) {
bool isEnabled;
err = message->FindBool("data", &isEnabled);
if (err >= B_OK)
item->SetEnabled(isEnabled);
}
break;
case 2: // Label: GET
if (message->what == B_GET_PROPERTY)
err = replyMsg.AddString("result", item->Label());
break;
case 3: // Label: SET
if (message->what == B_SET_PROPERTY) {
const char *label;
err = message->FindString("data", &label);
if (err >= B_OK)
item->SetLabel(label);
}
case 4: // Mark: GET
if (message->what == B_GET_PROPERTY)
err = replyMsg.AddBool("result", item->IsMarked());
break;
case 5: // Mark: SET
if (message->what == B_SET_PROPERTY) {
bool isMarked;
err = message->FindBool("data", &isMarked);
if (err >= B_OK)
item->SetMarked(isMarked);
}
break;
case 6: // Menu: CREATE
case 7: // Menu: DELETE
case 8: // Menu: *
case 9: // MenuItem: COUNT
case 10: // MenuItem: CREATE
case 11: // MenuItem: DELETE
case 12: // MenuItem: EXECUTE
case 13: // MenuItem: *
break;
default:
return BView::MessageReceived(message);
}
if (err != B_OK) {
replyMsg.what = B_MESSAGE_NOT_UNDERSTOOD;
replyMsg.AddString("message", strerror(err));
}
replyMsg.AddInt32("error", err);
message->SendReply(&replyMsg);
}
status_t BMenu::_ResolveItemSpecifier(const BMessage& specifier, int32 what,
BMenuItem*& item)
{
status_t err;
item = NULL;
switch (what) {
case B_INDEX_SPECIFIER:
case B_REVERSE_INDEX_SPECIFIER: {
int32 index;
err = specifier.FindInt32("index", &index);
if (err < B_OK)
return err;
if (what == B_REVERSE_INDEX_SPECIFIER)
index = CountItems() - index;
item = ItemAt(index);
break;
}
case B_NAME_SPECIFIER: {
const char* name;
err = specifier.FindString("name", &name);
if (err < B_OK)
return err;
item = FindItem(name);
break;
}
}
if (item == NULL)
return B_BAD_INDEX;
return B_OK;
}
status_t BMenu::_InsertItemAtSpecifier(const BMessage& specifier, int32 what,
BMenuItem* item)
{
status_t err;
switch (what) {
case B_INDEX_SPECIFIER:
case B_REVERSE_INDEX_SPECIFIER: {
int32 index;
err = specifier.FindInt32("index", &index);
if (err < B_OK) return err;
if (what == B_REVERSE_INDEX_SPECIFIER)
index = CountItems() - index;
if (!AddItem(item, index))
return B_BAD_INDEX;
break;
}
case B_NAME_SPECIFIER:
return B_NOT_SUPPORTED;
break;
}
return B_OK;
}
// #pragma mark - mouse tracking