1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! Wraps NSMenu and handles instrumenting necessary delegate pieces.

use std::sync::{Arc, Mutex};

use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use objc_id::{Id, ShareId};

use crate::appkit::menu::item::MenuItem;
use crate::foundation::{id, NSInteger, NSString};

/// A struct that represents an `NSMenu`. It takes ownership of items, and handles instrumenting
/// them throughout the application lifecycle.
#[derive(Debug)]
pub struct Menu(pub Id<Object>);

impl Menu {
    /// Creates a new `Menu` with the given title, and uses the passed items as submenu items.
    ///
    /// This method effectively does three things:
    ///
    /// - Consumes the MenuItem Vec, and pulls out handlers we need to cache
    /// - Configures the menu items appropriately, and wires them up
    /// - Drops the values we no longer need, and returns only what's necessary
    ///     to get the menu functioning.
    ///
    pub fn new(title: &str, items: Vec<MenuItem>) -> Self {
        Menu(unsafe {
            let cls = class!(NSMenu);
            let alloc: id = msg_send![cls, alloc];
            let title = NSString::new(title);
            let menu: id = msg_send![alloc, initWithTitle:&*title];

            for item in items.into_iter() {
                let objc = item.to_objc();
                let _: () = msg_send![menu, addItem:&*objc];
            }

            Id::from_retained_ptr(menu)
        })
    }

    /// Given a set of `MenuItem`s, merges them into an existing Menu (e.g, for a context menu on a
    /// view).
    pub fn append(menu: id, items: Vec<MenuItem>) -> id {
        // You might look at the code below and wonder why we can't just call `removeAllItems`.
        //
        // Basically: that doesn't seem to properly decrement the retain count on the underlying
        // menu item, and we wind up leaking any callbacks for the returned `MenuItem` instances.
        //
        // Walking them and calling release after removing them from the underlying store gives us
        // the correct behavior.
        unsafe {
            let mut count: NSInteger = msg_send![menu, numberOfItems];

            while count != 0 {
                count -= 1;
                let item: id = msg_send![menu, itemAtIndex: count];
                let _: () = msg_send![menu, removeItemAtIndex: count];
                let _: () = msg_send![item, release];
            }
        }

        for item in items.into_iter() {
            unsafe {
                let objc = item.to_objc();
                let _: () = msg_send![menu, addItem:&*objc];
            }
        }

        menu
    }

    /// Convenience method for the bare-minimum NSMenu structure that "just works" for all
    /// applications, as expected.
    pub fn standard() -> Vec<Menu> {
        vec![
            Menu::new("", vec![
                MenuItem::Services,
                MenuItem::Separator,
                MenuItem::Hide,
                MenuItem::HideOthers,
                MenuItem::ShowAll,
                MenuItem::Separator,
                MenuItem::Quit,
            ]),
            Menu::new("File", vec![MenuItem::CloseWindow]),
            Menu::new("Edit", vec![
                MenuItem::Undo,
                MenuItem::Redo,
                MenuItem::Separator,
                MenuItem::Cut,
                MenuItem::Copy,
                MenuItem::Paste,
                MenuItem::Separator,
                MenuItem::SelectAll,
            ]),
            Menu::new("View", vec![MenuItem::EnterFullScreen]),
            Menu::new("Window", vec![
                MenuItem::Minimize,
                MenuItem::Zoom,
                MenuItem::Separator,
                MenuItem::new("Bring All to Front"),
            ]),
        ]
    }
}