indexing description: "[ A widget which displays a single line editable textbox. The cursor position is counted from zero. (0: before first character, 1: after first and before second character, ...) ]" date: "$Date$" revision: "$Revision$" class EM_TEXTBOX inherit EM_WIDGET export {NONE} add_widget, remove_widget redefine delegate, next_frame end EM_TIME_SINGLETON export {NONE} all end create make_from_size, make_from_text, make_empty feature {NONE} -- Initialisation make_empty is -- Initialise textbox empty. do make_from_text ("") end make_from_size (n: INTEGER) is -- initialise the textfield to display `n' characters at least (measured with the width of character 'e'). do make_from_text (create {STRING}.make_filled ('e', n)) set_text ("") end make_from_text (a_text: STRING) is -- Initialise textfield with content `a_text'. require a_text_not_void: a_text /= Void local keyboard: EM_KEYBOARD do text := a_text create text_changed_event create return_pressed_event make_void_surface set_keyboard_sensitive (True) resize_to_optimal_dimension -- check if unicode is enabled create keyboard.make_snapshot -- TODO: on some computers setting this in the root class doesnt work. check if somewhere the setting gets reset or if is a problem of specific sdl librarys (or platforms) keyboard.enable_unicode_characters is_unicode_enabled := keyboard.is_unicode_characters_enabled -- register events mouse_clicked_event.subscribe (agent handle_mouse_clicked) key_down_event.subscribe (agent handle_key_down) focus_lost_event.subscribe (agent handle_focus_lost) focus_received_event.subscribe (agent handle_focus_received) end delegate_factory: FUNCTION [ANY, TUPLE [], like delegate] is -- Factory to create default delegate do Result := theme_delegates.textbox_delegate_factory end feature -- Access delegate: EM_TEXTBOX_DELEGATE -- Delegate text: STRING -- Text to display cursor_position: INTEGER -- Position of the cursor in the string cursor_x: INTEGER -- x position of the cursor on the widget feature -- Status report is_unicode_enabled: BOOLEAN -- Are unicode charachters enabled? -- This value is set at creation. Late changes of the keyboard setting are ignored is_cursor_visible: BOOLEAN -- Is the cursor visible? has_focus: BOOLEAN -- Does this textfield have the keyboard focus? feature -- Element change set_text (a_text: like text) is -- Set `text' to `a_text'. -- This will set the cursor to the last position. require a_text_not_void: a_text /= void do text := a_text set_cursor_position (text.count) Component_event_queue.add_event (text_changed_event, []) set_changed ensure text_set: text = a_text cursor_to_last: cursor_position = text.count changed: is_changed end set_cursor_position (a_position: like cursor_position) is -- Set `cursor_position' to `a_position'. require valid_position: 0 <= a_position and a_position <= text.count do cursor_position := a_position update_cursor_x ensure cursor_position_set: cursor_position = a_position end feature -- Basic operations insert_character_at_cursor (a_character: CHARACTER) is -- Insert 'a_character' left from cursor. -- This feature moves the cursor one position to the right! do text.insert_character (a_character, cursor_position+1) move_cursor_right Component_event_queue.add_event (text_changed_event, []) set_changed ensure character_inserted: text.count = old text.count + 1 character_inserted_at_position: text.item (cursor_position) = a_character cursor_moved: cursor_position = old cursor_position + 1 changed: is_changed end delete_character_left_of_cursor is -- Delete the character left of the current cursor position. -- This feature moves the cursor one position to the left. require cursor_not_left: cursor_position > 0 do move_cursor_left text.remove (cursor_position+1) Component_event_queue.add_event (text_changed_event, []) set_changed ensure character_removed: text.count = old text.count - 1 cursor_moved: cursor_position = old cursor_position - 1 changed: is_changed end delete_character_right_of_cursor is -- Delete the character right of the current cursor position. require cursor_not_right: cursor_position < text.count do text.remove (cursor_position+1) Component_event_queue.add_event (text_changed_event, []) set_changed ensure character_removed: text.count = old text.count - 1 changed: is_changed end move_cursor_left is -- Move the cursor one position to the left. require cursor_can_move: cursor_position > 0 do cursor_position := cursor_position - 1 update_cursor_x ensure cursor_moved: cursor_position = old cursor_position - 1 end move_cursor_right is -- Move the cursor one position to the right require cursor_can_move: cursor_position < text.count do cursor_position := cursor_position + 1 update_cursor_x ensure cursor_moved: cursor_position = old cursor_position + 1 end feature -- Miscellaneous next_frame is -- React on frame change. do if has_focus then if time.ticks > cursor_switch_tick then cursor_switch_tick := time.ticks + 300 is_cursor_visible := not is_cursor_visible set_changed end end end feature -- Events text_changed_event: EM_EVENT_CHANNEL [TUPLE []] -- Text changed event return_pressed_event: EM_EVENT_CHANNEL [TUPLE []] -- Return pressed event feature {NONE} -- Mouse management handle_mouse_clicked (event: EM_MOUSEBUTTON_EVENT) is -- Handle mouse clicked event. do set_cursor_position (delegate.position_to_text_index (Current, event.x, event.y)) end feature {NONE} -- Keyboard management handle_key_down (event: EM_KEYBOARD_EVENT) is -- Handle key down event. local inserted_character: CHARACTER do if is_unicode_enabled then if event.unicode_character.code > 0 then if event.key /= event.sdlk_backspace and event.key /= event.sdlk_delete and event.key /= event.sdlk_return and event.key /= event.sdlk_tab then inserted_character := event.unicode_character end end else if event.is_shift_pressed then inserted_character := event.character.as_upper else inserted_character := event.character end end if inserted_character.code > 0 then insert_character_at_cursor (inserted_character) elseif event.key = event.sdlk_backspace and cursor_position > 0 then delete_character_left_of_cursor elseif event.key = event.sdlk_delete and cursor_position < text.count then delete_character_right_of_cursor elseif event.key = event.sdlk_left and cursor_position > 0 then move_cursor_left elseif event.key = event.sdlk_right and cursor_position < text.count then move_cursor_right elseif event.key = event.sdlk_home then set_cursor_position (0) elseif event.key = event.sdlk_end then set_cursor_position (text.count) elseif event.key = event.sdlk_return then Component_event_queue.add_event (return_pressed_event, []) end set_changed ensure then changed: is_changed end handle_focus_received is -- Handle the focus received event. do has_focus := True set_changed end handle_focus_lost is -- Handle the focus lost event. do has_focus := False is_cursor_visible := False set_changed end feature {NONE} -- Implementation cursor_switch_tick: INTEGER -- Tick when the cursor view will be switched update_cursor_x is -- Update cursor position. do if cursor_position > 0 then cursor_x := font.string_width (text.substring (1, cursor_position)) else cursor_x := 0 end end invariant text_not_void: text /= Void text_changed_event_not_void: text_changed_event /= Void return_pressed_event_not_void: return_pressed_event /= Void valid_cursor_position: 0 <= cursor_position and cursor_position <= text.count end