Skip to content

IPopover is not sufficiently documented and needs more runtime checks #4122

@ChristopheI

Description

@ChristopheI

Describe the bug
Since I'm suing last version of v2_develop (commit: 7490ac9), my revisited version of combobox are no more working on my project ... I'm stuck :(

To Reproduce
Using the example provide below:

  1. Click on "Forwarding Immediate" menu
  2. Select any option using mouse => event SelectedItemChanged of ListView is NOT triggered 😑
    to be more precise OnMouseEvent() method of ListView is not called
  3. Use Mouse Up / or Mouse Down => event SelectedItemChanged of ListView is triggered 👍

Expected behavior
Event SelectedItemChanged of ListView must be triggered

Screenshots

Image

Desktop (please complete the following information):

  • OS: [Windows 11]

Code to reproduce:

using System.Collections.ObjectModel;
using System.Drawing;
using Terminal.Gui.Drawing;
using Terminal.Gui.Input;
using Terminal.Gui.ViewBase;
using Terminal.Gui.Views;
using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;

public class Item
{
    public String Id { get; set; }
    
    public string Text { get; set; }

    public Item()
    {
        Id = Text = "";
    }

    public Item(String id, String text)
    {
        Id = id;
        Text = text;
    }

    public override string ToString()
    {
        if (String.IsNullOrEmpty(Text))
            return Id;
        return Text;
    }
}

public class ContextMenuSelector : PopoverMenu
{
    protected override void OnAccepted(CommandEventArgs args)
    {
        args.Handled = true;
    }
}

public class ItemSelector : View
{
    private Label lbl;
    private Label lblSelector;
    private Label lblArrow;
    private ContextMenuSelector contextMenu;

    private ListViewSelector categoryList;
    private int maxItemLength = 0;

    List<Item>? items;
    Item? itemSelected;


    public event EventHandler<Item>? SelectedItemUpdated;

    public Item? ItemSelected
    {
        get => itemSelected;
    }

    public ItemSelector(String? label = null, List<Item>? items = null, Item? itemSelected = null)
    {
        lbl = new()
        {
            X = 0,
            Y = 0,
            Height = 1,
            Width = (label is null) ? 0 : label.Length + 1,
            Text = $"{label}:",
            CanFocus = true,
            //SchemeName = "BlackOnGray"
        };

        lblSelector = new()
        {
            X = Pos.Right(lbl) + 1,
            Y = 0,
            Height = 1,
            Width = Dim.Func(() =>
            {
                var textLength = (lblSelector is null) ? 0 : lblSelector.Text.Length;
                var spaceAvailable = Frame.Width - lbl.Frame.Width - ((lblArrow is null) ? 0 : lblArrow.Frame.Width) - 2;
                if (spaceAvailable < 0) spaceAvailable = 0;
                return Math.Min(textLength, spaceAvailable);
            }),
            CanFocus = true
            //SchemeName = "BlueOnGray"
        };
        lblSelector.MouseClick += View_MouseClick;

        lblArrow = new()
        {
            X = Pos.Right(lblSelector) + 1,
            Y = 0,
            Height = 1,
            Width = 2,
            Text = Emojis.TRIANGLE_DOWN,
            //SchemeName = "BlueOnGray"
        };
        lblArrow.MouseClick += View_MouseClick;

        Add(lbl, lblSelector, lblArrow);

        Height = 1;
        Width = Dim.Fill();

        // Create categoryList
        categoryList = new()
        {
            X = Pos.Func(() => lblSelector.FrameToScreen().X),
            Y = Pos.Func(() => lblSelector.FrameToScreen().Y + 1),
            Width = Dim.Fill(
                        Dim.Func(() =>
                        {
                            var delta = (categoryList?.VerticalScrollBar.Visible == true) ? 4 : 3;
                            var nbElements = maxItemLength + delta;
                            var spaceAvailable = Terminal.Gui.App.Application.Screen.Width - lblSelector.FrameToScreen().X;
                            return spaceAvailable - Math.Min(nbElements, spaceAvailable);
                        })),
            Height = Dim.Fill(
                         Dim.Func(() =>
                         {
                             var delta = (categoryList?.HorizontalScrollBar.Visible == true) ? 4 : 3;
                             var nbElements = ((this.items is null) ? 0 : this.items.Count) + delta;
                             var spaceAvailable = Terminal.Gui.App.Application.Screen.Height - lblSelector.FrameToScreen().Y;
                             return spaceAvailable - Math.Min(nbElements, spaceAvailable);
                         })),
            AllowsMarking = false,
            CanFocus = true,
            BorderStyle = LineStyle.Rounded,
            SuperViewRendersLineCanvas = false,
            //Source = new ListWrapper<Item>(null),
            //SchemeName = "BlueOnGray" // /!\ Need to be set !!!
        };
        categoryList.VerticalScrollBar.AutoShow = true;
        categoryList.HorizontalScrollBar.AutoShow = true;

        categoryList.SelectedItemChanged += CategoryList_SelectedItemChanged;
        categoryList.SameItemSelected += CategoryList_SameItemSelected;

        // Create contextMenu
        contextMenu = new()
        {
            CanFocus = true,
            //SchemeName = "BlueOnGray"
        };
        contextMenu.Add(categoryList);

        this.itemSelected = itemSelected;
        SetItems(items);
        CanFocus = true;
        //SchemeName = "BlueOnGray";
    }

    protected override void OnViewportChanged(DrawEventArgs e)
    {
        HideViewSelection();
    }

    public void HideViewSelection()
    {
        Terminal.Gui.App.Application.Popover?.Hide(contextMenu);
    }

    public void ShowViewSelection()
    {
        if (items?.Count > 0)
        {
            Point position = new Point(lblSelector.FrameToScreen().X, lblSelector.FrameToScreen().Y + 1);
            contextMenu.MakeVisible(position);
            contextMenu.MouseClick += ContextMenu_MouseClick;
        }
    }

    private void ContextMenu_MouseClick(object? sender, MouseEventArgs e)
    {
        e.Handled = false;
    }

    private void View_MouseClick(object? sender, MouseEventArgs e)
    {
        e.Handled = true;
        ShowViewSelection();
    }

    private void CategoryList_SelectedItemChanged(object? sender, ListViewItemEventArgs e)
    {
        HideViewSelection();

        itemSelected = (Item)e.Value;

        Terminal.Gui.App.Application.Invoke(() =>
        {
            UpdateDisplay();
        });

        SelectedItemUpdated?.Invoke(this, (Item)e.Value);
    }

    private void CategoryList_SameItemSelected(object? sender, EventArgs e)
    {
        HideViewSelection();
    }

    private void UpdateDisplay()
    {
        if (itemSelected is null)
        {
            lblSelector.Text = "NONE";
            lblSelector.Enabled = false;
            lblArrow.Width = 0;
        }
        else
        {
            lblSelector.Text = itemSelected.Text;
            lblSelector.Enabled = true;
            lblArrow.Width = 2;
        }
        HideViewSelection();
    }

    public void SetItems(List<Item>? items)
    {
        this.items = items;

        var previousItemSelected = itemSelected;

        // Check itemSelected value
        if (items?.Count > 0)
        {
            if ((itemSelected is null)
                || (items.FirstOrDefault(i => i.Id == itemSelected.Id) is null))
                itemSelected = items[0];
        }
        else
        {
            itemSelected = null;
        }

        if (items?.Count > 0)
        {
            var strings = items.Select(i => i.ToString());
            maxItemLength = strings.Aggregate("", (max, cur) => max.Length > cur.Length ? max : cur).Length;

            categoryList.SetSource(new ObservableCollection<Item>(items));
            categoryList.SelectedItem = 0;
        }
        else
        {
            maxItemLength = 0;
            categoryList.SetSource(new ObservableCollection<Item>());
        }

        // Do we need to update the display ?
        if (previousItemSelected?.Id == itemSelected?.Id)
            return;

        Terminal.Gui.App.Application.Invoke(() =>
        {
            UpdateDisplay();
        });

    }

    public void SetItems(List<String>? elements)
    {
        List<Item>? items = null;
        if (elements?.Count > 0)
        {
            items = [];
            int count = 0;
            foreach (var elm in elements)
            {
                var item = new Item()
                {
                    Id = $"{count++}",
                    Text = elm,
                };
                items.Add(item);
            }
        }
        SetItems(items);
    }

    public Boolean SetItemSelected(Item? item)
    {
        if ((item is not null) && (items is not null))
        {
            var index = items.FindIndex(x => x.Id == item.Id);
            if (index > 0)
                return SetItemSelected(index);
        }
        return false;
    }

    public Boolean SetItemSelected(int index)
    {
        if ((index >= 0) && (items?.Count > index))
        {
            var item = items[index];
            if (item.Id != itemSelected?.Id)
            {
                itemSelected = items[index];
                Terminal.Gui.App.Application.Invoke(() =>
                {
                    categoryList.SelectedItem = index;
                    UpdateDisplay();
                });
                return true;
            }
        }
        return false;
    }
}


public class ListViewSelector : ListView
{
    public event EventHandler<EventArgs> SameItemSelected;

    public override bool OnSelectedChanged()
    {
        var result = base.OnSelectedChanged();
        if (!result)
            SameItemSelected?.Invoke(this, EventArgs.Empty);
        return result;
    }
}

public class TestWindow: Window
{
    readonly List<string> listFwdType = ["Forwarding Immediate", "Forward on busy", "Forward on no reply", "Forward on busy/no reply"];
    private Shortcut ShVersion;

    public TestWindow()
    {
        BorderStyle = LineStyle.None;

        

        var callForwardSelector = new ItemSelector("Call forward")
        {
            X = 0,
            Y = 0,
            Width = 80,
            CanFocus = true
        };
        callForwardSelector.SetItems(listFwdType);
        callForwardSelector.SelectedItemUpdated += CallForwardSelector_SelectedItemUpdated; ;

        Add(callForwardSelector);

        // Create StatusBar
        StatusBar statusBar = new()
        {
            Visible = true,
            AlignmentModes = AlignmentModes.IgnoreFirstOrLast,
            CanFocus = false
        };

        ShVersion = new()
        {
            Title = "Version Info",
            CanFocus = false
        };
        statusBar.Add(ShVersion); // always add it as the last one
        Add(statusBar);

        Height = Dim.Fill();
        Width = Dim.Fill();

        // Need to manage Loaded event
        Loaded += LoadedHandler;
    }

    private void CallForwardSelector_SelectedItemUpdated(object? sender, Item e)
    {
        var id = (int.Parse(e.Id));
    }

    private void LoadedHandler(object? sender, EventArgs? args)
    {
        if (ShVersion is { })
        {
            ShVersion.Title = $"{RuntimeEnvironment.OperatingSystem} {RuntimeEnvironment.OperatingSystemVersion}, {Terminal.Gui.App.Application.Driver.GetVersionInfo()}";
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions