class EL_MENU

(source code)

description

Application menu with optional localization via class EL_MODULE_LOCALE

note
	description: "[
		Application menu with optional localization via class ${EL_MODULE_LOCALE}
	]"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2024-11-10 15:02:55 GMT (Sunday 10th November 2024)"
	revision: "21"

deferred class
	EL_MENU

inherit
	EL_KEYBOARD_ACCELERATED
		rename
			new_accelerator_table as default_accelerator_table
		end

	EL_ITEM_LIST_REPLACEMENT [EV_MENU]

	EL_MODULE_LOG; EL_MODULE_SCREEN

	EV_KEY_CONSTANTS
		export
			{NONE} all
		end

feature {NONE} -- Initialization

	make (a_window: like window)
		require
			has_menu_bar: attached a_window.menu_bar
		do
			window := a_window
			menu := new_menu
			Widget.insert_at (menu_item_list, menu, position)
			create shortcut_descriptions.make (11)

			fill; add_keyboard_shortcuts; adjust_menu_texts
			is_menu_sensitive := True
		end

feature -- Access

	item (id: INTEGER): EV_MENU_ITEM
		require
			valid_id: is_valid_id (id)
		do
			if attached {EV_MENU_ITEM} menu.retrieve_item_by_data (id, True) as l_result then
				Result := l_result
			else
				create Result.make_with_text ("Invalid item")
			end
		end

	menu: like new_menu

	name: ZSTRING
		deferred
		end

feature -- Basic operations

	update
		do
		-- Replaces menu_bar menu with new one
			menu := replaced_item (menu, new_menu)
			fill; adjust_menu_texts; adjust_items_sensitivity
		end

feature -- Element change

	add_identified_item (id: INTEGER; a_name: READABLE_STRING_GENERAL; action: PROCEDURE)
		local
			l_item: like new_item
		do
			l_item := new_item (a_name, action)
			l_item.set_data (id)
			menu.extend (l_item)
		ensure
			is_valid_id (id)
		end

	add_identified_sub_menu (id: INTEGER; sub_menu: EL_MENU)
		do
			sub_menu.menu.set_data (id)
		end

	add_item (a_name: READABLE_STRING_GENERAL; action: PROCEDURE)
		do
			menu.extend (new_item (a_name, action))
		end

	add_separator
		do
			menu.extend (create {EV_MENU_SEPARATOR})
		end

feature -- Status change

	adjust_items_sensitivity
			-- override to adjust menu items sensitivity for current application state
			-- (automatically added to menu select_actions and called before each shortcut key action)
		do
		end

	disable_all_items_sensitive
		do
			apply_action_all (agent internal_disable_sensitive)
		end

	disable_item_sensitive (id: INTEGER)
		do
			disable_items_sensitive (<< id >>)
		end

	disable_items_sensitive (selected_ids: ARRAY [INTEGER])
		do
			apply_action (agent internal_disable_sensitive, selected_ids)
		end

	disable_sensitive
		do
			menu.disable_sensitive
			is_menu_sensitive := False
		end

	enable_all_items_sensitive
		do
			apply_action_all (agent internal_enable_sensitive)
		end

	enable_item_sensitive (id: INTEGER)
		do
			enable_items_sensitive (<< id >>)
		end

	enable_item_sensitive_if (id: INTEGER; condition: BOOLEAN)
		do
			if condition then
				enable_items_sensitive (<< id >>)
			else
				disable_items_sensitive (<< id >>)
			end
		end

	enable_items_sensitive (selected_ids: ARRAY [INTEGER])
		do
			apply_action (agent internal_enable_sensitive, selected_ids)
		end

	enable_sensitive
		do
			menu.enable_sensitive
			is_menu_sensitive := True
		end

	set_item_sensitivity (id: INTEGER; sensitive: BOOLEAN)
		require
			is_valid_id (id)
		do
			if sensitive then
				internal_enable_sensitive (item (id))
			else
				internal_disable_sensitive (item (id))
			end
		end

feature -- Status query

	is_menu_sensitive: BOOLEAN

	is_valid_id (id: INTEGER): BOOLEAN
		do
			Result := attached {EV_MENU_ITEM} menu.retrieve_item_by_data (id, True)
		end

feature -- Basic operations

	apply_action (action: PROCEDURE [EV_MENU_ITEM]; selected_ids: ARRAY [INTEGER])
			-- applies action to all menu items identified by selected_ids
		do
			across selected_ids as id loop
				action.call ([item (id.item)])
			end
		end

	apply_action_all (action: PROCEDURE [EV_MENU_ITEM])
			-- applies action to all menu items
		do
			across menu as menu_item loop
				action.call ([menu_item.item])
			end
		end

feature {NONE} -- Factory

	new_item (a_name: READABLE_STRING_GENERAL; action: PROCEDURE): EV_MENU_ITEM
		do
			create Result.make_with_text_and_action (a_name.to_string_32, action)
		end

	new_menu: EV_MENU
		do
			create Result.make_with_text (name.to_unicode)
			Result.select_actions.extend (agent adjust_items_sensitivity)
		end

feature {NONE} -- Event handler

	on_keyboard_shortcut (id: INTEGER)
		do
			adjust_items_sensitivity
			if is_menu_sensitive
				and then attached {EV_MENU_ITEM} menu.retrieve_item_by_data (id, True) as menu_item
				and then attached menu_item.select_actions as actions
				and then actions.state = actions.Normal_state
			then
				actions.call (Void)
			end
		end

feature {NONE} -- Implementation

	accelerators: EV_ACCELERATOR_LIST
		do
			Result := window.accelerators
		end

	add_keyboard_shortcuts
		do
		end

	add_shortcut (id: INTEGER; modified_code: like combined)
		-- add keyboard shortcut with modifiers Ctrl, Alt, Shift combined with logical OR
		require
			valid_id: is_valid_id (id)
		local
			l_key: EL_KEY
		do
			l_key := modified_code
			accelerators.extend (create {EL_ACCELERATOR}.make_with_action (l_key, agent on_keyboard_shortcut (id)))
			shortcut_descriptions [id] := l_key.description
		end

	adjust_menu_item_text (menu_item: EV_MENU_ITEM)
		local
			adjusted_text: ZSTRING
		do
			if not attached {EV_MENU_SEPARATOR} menu_item then
				adjusted_text := menu_item.text
				if attached {INTEGER} menu_item.data as id then
					if shortcut_descriptions.has_key (id) then
						adjusted_text.append_character ('%T')
						adjusted_text.append (shortcut_descriptions.found_item)
					end
				end
				menu_item.set_text (adjusted_text.to_unicode)
			end
		end

	adjust_menu_texts
			-- adjust menu texts to include shortcut information and extra space for Windows Aero themes
		do
			menu.do_all (agent adjust_menu_item_text)
		end

	fill
		deferred
		end

	internal_disable_sensitive (menu_item: EV_MENU_ITEM)
		do
			menu_item.disable_sensitive
			menu_item.select_actions.block
		end

	internal_enable_sensitive (menu_item: EV_MENU_ITEM)
		do
			menu_item.enable_sensitive
			menu_item.select_actions.resume
		end

	menu_item_list: EV_MENU_ITEM_LIST
		do
			Result := window.menu_bar
		end

	position: INTEGER
		-- position at which to enter menu
		-- if 0 then extend menu_bar
		do
		end

feature {NONE} -- Internal attributes

	shortcut_descriptions: EL_HASH_TABLE [ZSTRING, INTEGER]
		-- keyboard shortcuts info indexed by menu item id

	window: EV_WINDOW

end