changeset 2463:79c7bf492bc1

iOS: Attempt to get context menus working for iOS 13+. Handle long tap on DWContainer to generate container context event. UIMenu creation seems to be working, but not getting the menu.
author bsmith@81767d24-ef19-dc11-ae90-00e081727c95
date Wed, 14 Apr 2021 20:53:46 +0000
parents 596bbc06e134
children 4fba0b9453ee
files ios/dw.m
diffstat 1 files changed, 146 insertions(+), 94 deletions(-) [+]
line wrap: on
line diff
--- a/ios/dw.m	Wed Apr 14 17:40:49 2021 +0000
+++ b/ios/dw.m	Wed Apr 14 20:53:46 2021 +0000
@@ -634,30 +634,43 @@
 @end
 
 API_AVAILABLE(ios(13.0))
-@interface DWMenuItem : UICommand
-{
-    int check;
+@interface DWMenu : NSObject
+{
+    UIMenu *menu;
+    NSMutableArray *children;
+    NSString *title;
     unsigned long tag;
 }
--(void)setCheck:(int)input;
--(void)setTag:(unsigned long)input;
--(int)check;
+-(id)initWithTag:(unsigned long)newtag;
+-(void)setTitle:(NSString *)input;
+-(UIMenu *)menu;
 -(unsigned long)tag;
+-(id)childWithTag:(unsigned long)tag;
+-(void)addItem:(id)item;
+-(void)removeItem:(id)item;
 -(void)dealloc;
 @end
 
 API_AVAILABLE(ios(13.0))
-@interface DWMenu : NSObject
-{
-    UIMenu *menu;
-}
--(void)setMenu:(UIMenu *)input;
--(UIMenu *)menu;
--(DWMenuItem *)itemWithTag:(unsigned long)tag;
+@interface DWMenuItem : UICommand
+{
+    BOOL check, enabled;
+    unsigned long tag;
+    DWMenu *submenu, *menu;
+}
+-(void)setCheck:(BOOL)input;
+-(void)setEnabled:(BOOL)input;
+-(void)setTag:(unsigned long)input;
+-(void)setSubmenu:(DWMenu *)input;
+-(void)setMenu:(DWMenu *)input;
+-(DWMenu *)submenu;
+-(DWMenu *)menu;
+-(BOOL)check;
+-(BOOL)enabled;
+-(unsigned long)tag;
 -(void)dealloc;
 @end
 
-
 /* So basically to implement our event handlers...
  * it looks like we are going to have to subclass
  * basically everything.  Was hoping to add methods
@@ -761,6 +774,7 @@
 
 @interface DWWindow : UIWindow
 {
+    UIMenu *popupmenu;
     int redraw;
     int shown;
 }
@@ -772,6 +786,8 @@
 -(int)shown;
 -(void)setShown:(int)val;
 -(void)layoutSubviews;
+-(UIMenu *)popupMenu;
+-(void)setPopupMenu:(UIMenu *)input;
 @end
 
 @implementation DWWindow
@@ -793,6 +809,8 @@
 -(void)setShown:(int)val { shown = val; }
 -(void)dealloc { dw_signal_disconnect_by_window(self); [super dealloc]; }
 -(void)layoutSubviews { }
+-(UIMenu *)popupMenu { return popupmenu; }
+-(void)setPopupMenu:(UIMenu *)input { [popupmenu release]; popupmenu = input; [popupmenu retain]; }
 @end
 
 @interface DWImage : NSObject
@@ -1327,11 +1345,14 @@
 /* Subclass for a top-level window */
 @interface DWView : DWBox /* <UIWindowDelegate> */
 {
-    DWMenu *windowmenu;
+    DWMenu *windowmenu, *popupmenu;
     CGSize oldsize;
 }
 -(BOOL)windowShouldClose:(id)sender;
 -(void)setMenu:(DWMenu *)input;
+-(void)setPopupMenu:(DWMenu *)input;
+-(DWMenu *)menu;
+-(DWMenu *)popupMenu;
 -(void)windowDidBecomeMain:(id)sender;
 -(void)menuHandler:(id)sender;
 @end
@@ -1385,6 +1406,9 @@
     _dw_event_handler([self window], nil, 13);
 }
 -(void)setMenu:(DWMenu *)input { windowmenu = input; [windowmenu retain]; }
+-(void)setPopupMenu:(DWMenu *)input { popupmenu = input; [popupmenu retain]; }
+-(DWMenu *)menu { return windowmenu; }
+-(DWMenu *)popupMenu { return popupmenu; }
 -(void)menuHandler:(id)sender
 {
     [DWObj menuHandler:sender];
@@ -1542,9 +1566,15 @@
 
 /* Subclass for a menu item type */
 @implementation DWMenuItem
--(void)setCheck:(int)input { check = input; }
+-(void)setCheck:(BOOL)input { check = input; }
+-(void)setEnabled:(BOOL)input { enabled = input; }
+-(void)setSubmenu:(DWMenu *)input { submenu = input; }
+-(void)setMenu:(DWMenu *)input { menu = input; }
 -(void)setTag:(unsigned long)input { tag = input; }
--(int)check { return check; }
+-(BOOL)check { return check; }
+-(BOOL)enabled { return enabled; }
+-(DWMenu *)submenu { return submenu; }
+-(DWMenu *)menu { return menu; }
 -(unsigned long)tag { return tag; }
 -(void)dealloc { dw_signal_disconnect_by_window(self); [super dealloc]; }
 @end
@@ -1554,16 +1584,67 @@
  * Currently in this category: DWMenu and DWImage
  */
 @implementation DWMenu
--(void)setMenu:(UIMenu *)input { menu = input; }
--(UIMenu *)menu { return menu; }
--(DWMenuItem *)itemWithTag:(unsigned long)tag
-{
-    NSArray *children = [menu children];
-    
-    for(DWMenuItem *menuitem in children)
-    {
-        if([menuitem tag] == tag)
-            return menuitem;
+-(id)initWithTag:(unsigned long)newtag
+{
+    self = [super init];
+    if(self)
+    {
+        children = [[NSMutableArray alloc] init];
+        tag = newtag;
+    }
+    return self;
+}
+-(void)setTitle:(NSString *)input { title = input; [title retain]; }
+-(UIMenu *)menu
+{
+    /* Create or recreate the UIMenu recursively */
+    UIMenu *oldmenu = menu;
+    NSMutableArray *menuchildren = [[NSMutableArray alloc] init];
+
+    for(id child in children)
+    {
+        if([child isMemberOfClass:[DWMenuItem class]])
+        {
+            DWMenuItem *menuitem = child;
+            DWMenu *submenu = [menuitem submenu];
+
+            if(submenu)
+                [menuchildren addObject:[submenu menu]];
+            else
+                [menuchildren addObject:child];
+        }
+    }
+    if(title)
+        menu = [UIMenu menuWithTitle:title children:menuchildren];
+    else
+    {
+        if(@available(iOS 14.0, *))
+            menu = [UIMenu menuWithChildren:menuchildren];
+        else
+            menu = [UIMenu menuWithTitle:@"" children:menuchildren];
+    }
+    if(oldmenu)
+        [oldmenu release];
+    return menu;
+}
+-(void)addItem:(id)item
+{
+    [children addObject:item];
+}
+-(void)removeItem:(id)item
+{
+    [children removeObject:item];
+}
+-(unsigned long)tag { return tag; }
+-(id)childWithTag:(unsigned long)tag
+{
+    if(tag > 0)
+    {
+        for(id child in children)
+        {
+            if([child isMemberOfClass:[DWMenuItem class]] && [child tag] == tag)
+                return child;
+        }
     }
     return nil;
 }
@@ -1945,6 +2026,7 @@
 -(NSInteger)tableView:(UITableView *)aTable numberOfRowsInSection:(NSInteger)section;
 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
 -(void)tableView:(UITableView*)tableView willDisplayCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexPath;
+-(UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point;
 -(void)addColumn:(NSString *)input andType:(int)type;
 -(NSString *)getColumn:(int)col;
 -(void *)userdata;
@@ -1966,7 +2048,6 @@
 -(void)setup;
 -(CGSize)getsize;
 -(void)setForegroundColor:(UIColor *)input;
--(DWMenu *)menuForEvent:(UIEvent *)event;
 @end
 
 @implementation DWContainer
@@ -2017,6 +2098,22 @@
             [cell setBackgroundColor:oddcolor];
     }
 }
+-(UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point
+{
+    DWWindow *window = (DWWindow *)[self window];
+    UIContextMenuConfiguration *config = nil;
+
+    _dw_event_handler(self, (UIEvent *)[self getRowTitle:(int)indexPath.row], 10);
+
+    if(window)
+    {
+        __block UIMenu *popupmenu = [window popupMenu];
+        config = [UIContextMenuConfiguration configurationWithIdentifier:@"DWContextMenu"
+                                                         previewProvider:nil
+                                                          actionProvider:^(NSArray* suggestedAction){return popupmenu;}];
+    }
+    return config;
+}
 -(void)addColumn:(NSString *)input andType:(int)type { if(tvcols) { [tvcols addObject:input]; [types addObject:[NSNumber numberWithInt:type]]; } }
 -(NSString *)getColumn:(int)col { if(tvcols) { return [tvcols objectAtIndex:col]; } return nil; }
 -(void *)userdata { return userdata; }
@@ -2287,16 +2384,6 @@
     if([self allowsMultipleSelection])
         [self tableView:tableView didSelectRowAtIndexPath:indexPath];
 }
--(DWMenu *)menuForEvent:(UIEvent *)event
-{
-#if 0 /* TODO: Fix this */
-    int row;
-    CGPoint where = [self convertPoint:[event locationInWindow] fromView:nil];
-    row = (int)[self rowAtPoint:where];
-    _dw_event_handler(self, (UIEvent *)[self getRowTitle:row], 10);
-#endif
-    return nil;
-}
 -(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
 @end
 
@@ -7404,8 +7491,7 @@
  */
 HMENUI API dw_menu_new(ULONG cid)
 {
-    DWMenu *menu = [[[DWMenu alloc] init] retain];
-    /* [menu setTag:cid]; Why doesn't this work? */
+    DWMenu *menu = [[[DWMenu alloc] initWithTag:cid] retain];
     return menu;
 }
 
@@ -7443,15 +7529,12 @@
  */
 void API dw_menu_popup(HMENUI *menu, HWND parent, int x, int y)
 {
-#if 0 /* TODO: Figure out how to do this */
     DWMenu *thismenu = (DWMenu *)*menu;
     id object = parent;
-    UIView *view = [object isKindOfClass:[UIWindow class]] ? [object contentView] : parent;
-    UIWindow *window = [view window];
+    DWWindow *window = [object isMemberOfClass:[DWWindow class]] ? object : (DWWindow *)[object window];
     [thismenu autorelease];
-    CGPoint p = CGPointMake(x, y);
-    [UIContextMenuInteraction a
-#endif
+    [window setPopupMenu:[thismenu menu]];
+    *menu = nil;
 }
 
 char _dw_removetilde(char *dest, const char *src)
@@ -7490,67 +7573,43 @@
 HWND API dw_menu_append_item(HMENUI menux, const char *title, ULONG itemid, ULONG flags, int end, int check, HMENUI submenux)
 {
     DWMenu *menu = menux;
-    DWMenuItem *item = NULL;
-    if(strlen(title) == 0)
-    {
-#if 0 /* TODO: Not sure if separators exist in iOS */
-        [menu addItem:[DWMenuItem separatorItem]];
-#endif
-    }
+    DWMenuItem *item = nil;
+    if(!title || strlen(title) == 0)
+        [menu addItem:[[NSNull alloc] init]];
     else
     {
         char accel[2];
         char *newtitle = malloc(strlen(title)+1);
         NSString *nstr;
-        UIMenu *newmenu, *oldmenu = [menu menu];
-        NSArray *newchildren, *oldchildren = [oldmenu children];
         
         accel[0] = _dw_removetilde(newtitle, title);
         accel[1] = 0;
 
-        nstr = [ NSString stringWithUTF8String:newtitle ];
+        nstr = [NSString stringWithUTF8String:newtitle];
         free(newtitle);
 
         item = [[DWMenuItem commandWithTitle:nstr image:nil
                                       action:@selector(menuHandler:)
                                 propertyList:nil] autorelease];
-        newchildren = [oldchildren arrayByAddingObjectsFromArray:@[item]];
-        if(oldmenu)
-        {
-            newmenu = [oldmenu menuByReplacingChildren:newchildren];
-            [oldmenu release];
-        }
-        else if(@available(iOS 14.0, *))
-            newmenu = [UIMenu menuWithChildren:newchildren];
-        else
-            newmenu = [UIMenu menuWithTitle:@"" children:newchildren];
-        [menu setMenu:newmenu];
+        [item setTag:itemid];
+        [menu addItem:item];
         
-        [item setTag:itemid];
         if(check)
         {
             [item setCheck:YES];
             if(flags & DW_MIS_CHECKED)
-            {
                 [item setState:UIMenuElementStateOn];
-            }
-        }
-#if 0 /* TODO: Disabled items not supported on iOS */
+        }
         if(flags & DW_MIS_DISABLED)
-        {
             [item setEnabled:NO];
-        }
-#endif
-
-#if 0 /* TODO: iOS may not support submenus... but may be able to cascade... with defered menus */
+
         if(submenux)
         {
             DWMenu *submenu = submenux;
 
             [submenu setTitle:nstr];
-            [menu setSubmenu:submenu forItem:item];
-        }
-#endif
+            [item setSubmenu:submenu];
+        }
         return item;
     }
     return item;
@@ -7567,18 +7626,14 @@
 void API dw_menu_item_set_check(HMENUI menux, unsigned long itemid, int check)
 {
     id menu = menux;
-    DWMenuItem *menuitem = [menu itemWithTag:itemid];
+    DWMenuItem *menuitem = [menu childWithTag:itemid];
 
     if(menuitem != nil)
     {
         if(check)
-        {
             [menuitem setState:UIMenuElementStateOn];
-        }
         else
-        {
             [menuitem setState:UIMenuElementStateOff];
-        }
     }
 }
 
@@ -7592,16 +7647,14 @@
  */
 int API dw_menu_delete_item(HMENUI menux, unsigned long itemid)
 {
-#if 0 /* TODO: Remove item from the children array */
     id menu = menux;
-    DWMenuItem *menuitem = [menu itemWithTag:itemid];
+    DWMenuItem *menuitem = [menu childWithTag:itemid];
 
     if(menuitem != nil)
     {
         [menu removeItem:menuitem];
         return DW_ERROR_NONE;
     }
-#endif
     return DW_ERROR_UNKNOWN;
 }
 
@@ -7616,7 +7669,7 @@
 void API dw_menu_item_set_state(HMENUI menux, unsigned long itemid, unsigned long state)
 {
     id menu = menux;
-    DWMenuItem *menuitem = [menu itemWithTag:itemid];
+    DWMenuItem *menuitem = [menu childWithTag:itemid];
 
     if(menuitem != nil)
     {
@@ -8334,14 +8387,13 @@
         [window release];
     }
     /* Handle removing menu items from menus */
-#if 0 /* TODO: The menus are technically immutable, so we'd need to recreate it...*/
-    else if([ object isKindOfClass:[UIMenuItem class]])
-    {
-        DWMenu *menu = [object menu];
+    else if([object isMemberOfClass:[DWMenuItem class]])
+    {
+        DWMenuItem *item = object;
+        DWMenu *menu = [item menu];
 
         [menu removeItem:object];
     }
-#endif
     /* Handle destroying a control or box */
     else if([object isKindOfClass:[UIView class]] || [object isKindOfClass:[UIControl class]])
     {