note description: "EV_TEXT widget representation of a document." legal: "See notice at end of class." status: "See notice at end of class." date: "$Date$" revision: "$Revision$" class DOCUMENT_TEXT_WIDGET inherit EV_RICH_TEXT redefine initialize, is_in_default_state, set_font end OBSERVER undefine copy, default_create end OBSERVED undefine copy, default_create end SHARED_OBJECTS undefine copy, default_create end XML_ROUTINES rename is_valid_xml as is_valid_xml_text undefine copy, default_create end GRAPHICAL_CONSTANTS undefine copy, default_create end create make feature -- Creation make (a_document: DOCUMENT) -- Make Current with `a_document' do document := a_document default_create document.attach (Current) attach (application_window) set_tab_width (18) end feature {NONE} -- Initialization initialize -- Initialization do Precursor {EV_RICH_TEXT} if document.text /= Void then append_rich_text (document.text) highlight end should_update := True create schema_validator if shared_document_editor.preferences.font /= Void then set_font (shared_document_editor.preferences.font) end if not shared_document_editor.preferences.word_wrap_on then disable_word_wrapping end -- Agents change_actions.extend (agent internal_update_subject) key_press_actions.extend (agent key_pressed) key_release_actions.extend (agent update_subject) pointer_button_release_actions.force_extend (agent internal_update_subject) pointer_button_release_actions.force_extend (agent pointer_released) pointer_button_press_actions.extend (agent pointer_pressed (?,?,?,?,?,?,?,?)) drop_actions.extend (agent pebble_dropped) end feature -- Query is_valid_xml: BOOLEAN -- Is `text' valid xml? local l_error: ERROR do if not text.is_empty then Result := is_valid_xml_text (text) if not Result then create error_report.make ("Invalid XML") create l_error.make_with_line_information (error_description, error_line, error_column) l_error.set_action (agent (error_report.actions).highlight_text_byte_in_editor (error_byte)) error_report.append_error (l_error) end else create error_report.make ("Invalid XML") create l_error.make ("File is empty") error_report.append_error (l_error) end end selection_is_valid_xml: BOOLEAN -- Is `selected_text' valid XML? do if not text.is_empty then Result := is_valid_xml_text (selected_text) end end is_valid_to_schema: BOOLEAN -- Is Current valid to the loaded schema? do if is_valid_xml then schema_validator.validate_against_text (text, Shared_document_manager.schema.name) Result := schema_validator.is_valid if not Result then error_report := schema_validator.error_report end end end can_insert (a_xml: STRING): BOOLEAN -- Can `xml' be inserted into Current? local l_constants: EV_DIALOG_CONSTANTS l_message_dialog: EV_MESSAGE_DIALOG do if not is_valid_xml_text (a_xml) then create l_message_dialog.make_with_text ((create {MESSAGE_CONSTANTS}).invalid_xml_dialog_title) l_message_dialog.set_title ((create {MESSAGE_CONSTANTS}).invalid_xml_dialog_title) l_message_dialog.set_text ((create {MESSAGE_CONSTANTS}).invalid_xml_file_warning) l_message_dialog.set_buttons (<<(create {EV_DIALOG_CONSTANTS}).ev_ok>>) l_message_dialog.show_modal_to_window (Application_window) elseif a_xml.has_substring ("code_block") then create l_constants create l_message_dialog.make_with_text ((create {MESSAGE_CONSTANTS}).html_pre_tag_warning) l_message_dialog.set_title (l_constants.ev_warning_dialog_title) l_message_dialog.set_buttons (<<"Continue", l_constants.ev_cancel>>) l_message_dialog.show_modal_to_window (application_window) if l_message_dialog.selected_button.is_equal ("Continue") then Result := True end l_message_dialog.destroy else Result := True end end feature -- Commands highlight -- Highlight output sections local l_highlight_filter: OUTPUT_FORMAT_FILTER l_parser: XM_EIFFEL_PARSER do if is_valid_xml then create l_highlight_filter.make (Current) create l_parser.make l_parser.set_callbacks (l_highlight_filter) l_parser.parse_from_string (text) flush_buffer else append_rich_text (document.text) end end highlight_error -- Highlight error. do end highlight_error_pos (a_no, a_pos: INTEGER) -- Highlight line at `a_no' and position `a_pos'. Since `l_no' denoted a file -- line number this must be converted to the relevant position in Current. local l_has_wrap: BOOLEAN l_start_pos, l_end_pos: INTEGER do l_has_wrap := has_word_wrapping if l_has_wrap then disable_word_wrapping end l_start_pos := first_position_from_line_number (a_no) + a_pos - 1 l_end_pos := l_start_pos + line (a_no).count - a_pos - 2 if l_has_wrap then enable_word_wrapping end select_region (l_start_pos, l_end_pos) scroll_to_line (a_no) set_focus end update_line_display -- Update line display to reflect caret position do Application_window.update_status_line (current_line_number, caret_position) end feature -- Status Setting set_font (a_font: EV_FONT) -- Assign `a_font' to `font'. local l_format: EV_CHARACTER_FORMAT do create l_format l_format.set_font (a_font) modify_region (1, text.count, l_format, create {EV_CHARACTER_FORMAT_RANGE_INFORMATION}.make_with_flags ({EV_CHARACTER_FORMAT_CONSTANTS}.font_family)) end tag_selection (a_tag: STRING) -- Enclose `selected_text' in `a_tag'. Eg, `some_text' -- becomes `a_tagsome_texta_tag'. If there is no selection -- just insert `a_taga_tag'. local l_text, l_prev_text: STRING l_selected: BOOLEAN do l_selected := has_selection create l_text.make_from_string (a_tag) if l_selected then if l_text.has_substring ("[tag]") then l_text.replace_substring_all ("[tag]", selected_text) else l_text.append (a_tag) l_text.append (selected_text) l_text.append (a_tag) end if not clipboard_content.is_empty then l_prev_text := clipboard_content end cut_selection end insert_text (l_text) if l_selected then select_region (caret_position, caret_position + (l_text.count - 1)) elseif l_text.has_substring ("[tag]") then select_region (caret_position + l_text.substring_index ("[tag]", 1) - 1, caret_position + l_text.substring_index ("[tag]", 1) + 3) end if l_prev_text /= Void then shared_document_editor.clipboard.set_text (l_prev_text) end end tag_selection_as_xml (a_tag: STRING) -- Enclose `selected_text' in XML `a_tag'. Eg, `some_text' -- becomes 'some_text'. If there is no selection -- just insert ''. local l_text, l_prev_text: STRING l_selected: BOOLEAN do l_selected := has_selection create l_text.make_from_string ("<" + a_tag + ">") if l_selected then if l_text.has_substring ("[tag]") then l_text.replace_substring_all ("[tag]", selected_text) else l_text.append (selected_text) end if not clipboard_content.is_empty then l_prev_text := clipboard_content end cut_selection end l_text.append ("") insert_text (l_text) if l_selected then select_region (caret_position + (a_tag.count + 2), caret_position + (l_text.count - 1) - (a_tag.count + 3)) else set_caret_position (caret_position + a_tag.count + 2) end if l_prev_text /= Void then shared_document_editor.clipboard.set_text (l_prev_text) end end key_pressed (a_key: EV_KEY) -- A key was pressed local l_format: EV_CHARACTER_FORMAT do l_format := character_format (caret_position) if l_format /= Void then l_format.set_color (content_color) set_current_format (l_format) end end update_subject (a_key: EV_KEY) -- Update the observed subjects of changes so it can update -- all of it observers local l_cnt, l_tab_count: INTEGER l_string: STRING done: BOOLEAN l_char: CHARACTER do if a_key.code = enter_key_code then should_update := False l_string := line (current_line_number - 1) if l_string /= Void and then not l_string.is_empty then from l_cnt := 1 until l_cnt > l_string.count or done loop l_char := l_string.item (l_cnt) if l_char = tab_char then l_tab_count := l_tab_count + 1 else done := True end l_cnt := l_cnt + 1 end if l_tab_count > 0 then from l_cnt := 1 l_string := "" until l_cnt > l_tab_count loop l_string.append ("%T") l_cnt := l_cnt + 1 end insert_text (l_string) set_caret_position (caret_position + l_tab_count) end end end should_update := True end feature -- Query is_modified: BOOLEAN -- Has text been modifed? feature -- Access xml: XM_DOCUMENT -- XML Document representation of `text' if `text' is valid XML (Void otherwise) do Result := internal_xml if Result = Void or is_modified then Result := deserialize_text (text) end end error_report: ERROR_REPORT -- Validation error string document: DOCUMENT -- Document subject feature {OBSERVED} -- Observer Pattern update -- Update Current do -- if not text.is_equal (document.text) then highlight -- end end feature {NONE} -- Implementation internal_xml: like xml -- XML schema_validator: SCHEMA_VALIDATOR -- Schema Validator internal_update_subject -- Update subject do notify_observers update_line_display end pointer_pressed (a_x, a_y, a_button: INTEGER; a_x_tilt, a_y_tilt, a_pressure: DOUBLE; a_screen_x, a_screen_y: INTEGER) -- Pointer was pressed, catch right-click local l_popup: EV_MENU l_menu_item: EV_MENU_ITEM menu_children: SORTED_TWO_WAY_LIST [STRING] l_parent: STRING do if Shared_document_manager.has_schema then -- Determine element parent based on cursor position l_parent := xml_parent if l_parent /= Void and then not l_parent.is_empty then -- Retrieve element list and build popup menu menu_children ?= sub_element_list (l_parent) if a_button = 3 then if menu_children /= Void and then not menu_children.is_empty then create l_popup from menu_children.start until menu_children.after loop create l_menu_item.make_with_text (menu_children.item) l_menu_item.select_actions.extend (agent tag_selection_as_xml (menu_children.item)) l_popup.extend (l_menu_item) menu_children.forth end l_popup.show_at (Current, a_x, a_y) end end end end end pointer_released -- Pointer was released local l_parent: STRING do if Shared_document_manager.has_schema then l_parent := xml_parent if l_parent /= Void and then not l_parent.is_empty then Application_window.update_sub_element_list (l_parent, sub_element_list (l_parent)) end end end xml_parent: STRING -- Determine the parent XML text based on the current -- cursor position local found_end_tag, found_start_tag, is_child_tag, invalid: BOOLEAN start_pos, end_pos, curr_pos, cnt, ch_cnt: INTEGER do from Result := text.substring (1, caret_position) cnt := Result.count ch_cnt := 1 until ((found_end_tag and found_start_tag and not is_child_tag) or (cnt = 0)) or invalid loop curr_pos := cnt - 1 if found_end_tag and found_start_tag and is_child_tag then found_end_tag := False found_start_tag := False end if not found_end_tag then found_end_tag := Result.substring (cnt, cnt).is_equal (">") if found_end_tag then if Result.substring (curr_pos, curr_pos).is_equal ("/") then ch_cnt := ch_cnt + 1 end end end_pos := curr_pos else found_start_tag := Result.substring (curr_pos, curr_pos).is_equal ("<") if found_start_tag then if Result.substring (cnt, cnt).is_equal ("/") then found_start_tag := True ch_cnt := ch_cnt + 1 else ch_cnt := ch_cnt - 1 end end start_pos := curr_pos end is_child_tag := ch_cnt /= 0 cnt := cnt - 1 end Result := Result.substring (start_pos + 1, end_pos) if not Result.is_empty then -- Tidy result Result.prune_all_leading (' ') Result.prune_all_leading ('%T') Result.prune_all_leading ('%N') Result.prune_all_trailing (' ') Result.prune_all_trailing ('%T') Result.prune_all_trailing ('%N') end -- Prune out any attribute declarations if Result.occurrences (' ') > 0 then Result := Result.substring (1, Result.index_of (' ', 1) - 1) end end sub_element_list (a_parent: STRING): SORTED_TWO_WAY_LIST [STRING] -- Alphabetically sorted list of sub elements of `a_parent' local schema_element: DOCUMENT_SCHEMA_ELEMENT children: ARRAYED_LIST [DOCUMENT_SCHEMA_ELEMENT] do schema_element ?= shared_document_manager.schema.get_element_by_name (a_parent) if schema_element /= void then if not schema_element.children.is_empty then children := schema_element.children.twin elseif not schema_element.type_children.is_empty then children := schema_element.type_children.twin end if children /= Void then from create Result.make children.start until children.after loop Result.extend (children.item.name) children.forth end Result.sort end end end pebble_dropped (a_url: STRING) -- Pebble dropped on target local l_start_pos, l_end_pos: INTEGER l_link: DOCUMENT_LINK l_url: STRING do set_focus l_start_pos := caret_position if document.is_persisted then create l_link.make (document.name, a_url) l_url := l_link.relative_url else l_url := a_url end l_end_pos := l_start_pos + l_url.count if has_selection then cut_selection end insert_text (l_url) select_region (l_start_pos, l_end_pos) end is_in_default_state: BOOLEAN = True -- Is in default state enter_key_code: INTEGER -- Enter key once Result := key_constants.key_enter end append_rich_text (a_text: STRING) -- Append `a_text', formatted local l_xml_filter: XML_FORMAT_FILTER l_parser: XM_EIFFEL_PARSER l_unformattable_text: STRING do create l_xml_filter.make (Current) create l_parser.make l_parser.set_callbacks (l_xml_filter) l_parser.set_string_mode_mixed l_parser.parse_from_string (a_text) flush_buffer if not l_parser.is_correct then print ("Gobo Error description: " + l_parser.last_error_extended_description) l_unformattable_text := a_text.substring (text.count + 1, a_text.count) append_text (l_unformattable_text) end end key_constants: EV_KEY_CONSTANTS -- Key constants once create Result end note copyright: "Copyright (c) 1984-2006, Eiffel Software" license: "GPL version 2 (see http://www.eiffel.com/licensing/gpl.txt)" licensing_options: "http://www.eiffel.com/licensing" copying: "[ This file is part of Eiffel Software's Eiffel Development Environment. Eiffel Software's Eiffel Development Environment is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License (available at the URL listed under "license" above). Eiffel Software's Eiffel Development Environment is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Eiffel Software's Eiffel Development Environment; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ]" source: "[ Eiffel Software 356 Storke Road, Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end -- class DOCUMENT_TEXT_WIDGET