From 5de848de0429cab39a44a8ac46e5f8a94e12c8f0 Mon Sep 17 00:00:00 2001 From: Giuseppe Penone Date: Thu, 14 Mar 2024 21:49:01 +0000 Subject: [PATCH] improved search in tables and codeboxes to list and select all the instances (#2155, #2444) --- src/ct/ct_actions.h | 30 +-- src/ct/ct_actions_find.cc | 447 +++++++++++++++++++++++++++---------- src/ct/ct_dialogs.h | 26 ++- src/ct/ct_dialogs_find.cc | 51 ++++- src/ct/ct_misc_utils.cc | 51 +++++ src/ct/ct_misc_utils.h | 5 + src/ct/ct_table.cc | 40 +++- src/ct/ct_table.h | 12 +- src/ct/ct_table_light.cc | 29 ++- src/ct/ct_treestore.cc | 1 + src/ct/ct_types.h | 16 +- tests/tests_read_write.cpp | 1 + 12 files changed, 561 insertions(+), 148 deletions(-) diff --git a/src/ct/ct_actions.h b/src/ct/ct_actions.h index 2d7811534..aab3b7518 100644 --- a/src/ct/ct_actions.h +++ b/src/ct/ct_actions.h @@ -226,23 +226,27 @@ class CtActions Gtk::TextIter start_iter, bool forward, bool all_matches); - const size_t _line_content_limit{100u}; - Glib::ustring _get_line_content(Glib::RefPtr text_buffer, - const int match_end_offset); - Glib::ustring _get_first_line_content(Glib::RefPtr text_buffer); - Glib::ustring _check_pattern_in_object(Glib::RefPtr pattern, - CtAnchoredWidget* obj); - std::pair _check_pattern_in_object_between(CtTreeIter tree_iter, - Glib::RefPtr text_buffer, - Glib::RefPtr pattern, - int start_offset, - int end_offset, - bool forward, - Glib::ustring& obj_content); + bool _check_pattern_in_object(Glib::RefPtr re_pattern, + CtAnchoredWidget* pAnchWidg, + CtAnchMatchList& anchMatchList); + bool _check_pattern_in_object_between(CtTreeIter tree_iter, + Glib::RefPtr pattern, + int start_offset, + int end_offset, + const bool forward, + const bool all_matches, + CtAnchMatchList& anchMatchList); int _get_num_objs_before_offset(Glib::RefPtr text_buffer, int max_offset); void _update_all_matches_progress(); public: void find_matches_store_reset(); + static void find_match_in_obj_focus(const int obj_offset, + Glib::RefPtr pTextBuffer, + const CtTreeIter& tree_iter, + const CtAnchWidgType anch_type, + const size_t anch_cell_idx, + const int anch_offs_start, + const int anch_offs_end); public: // find actions diff --git a/src/ct/ct_actions_find.cc b/src/ct/ct_actions_find.cc index d0a6c45d7..69364c771 100644 --- a/src/ct/ct_actions_find.cc +++ b/src/ct/ct_actions_find.cc @@ -57,6 +57,7 @@ void CtActions::find_in_selected_node() Glib::RefPtr curr_buffer = _pCtMainWin->get_text_view().get_buffer(); if (not _s_state.from_find_iterated) { + _s_state.find_iter_anchlist_size = 0u; auto iter_insert = curr_buffer->get_iter_at_mark(curr_buffer->get_insert()); auto iter_bound = curr_buffer->get_iter_at_mark(curr_buffer->get_selection_bound()); auto entry_predefined_text = curr_buffer->get_text(iter_insert, iter_bound); @@ -142,6 +143,7 @@ void CtActions::find_in_multiple_nodes() Glib::RefPtr curr_buffer = ctTextView.get_buffer(); if (not _s_state.from_find_iterated) { + _s_state.find_iter_anchlist_size = 0u; Gtk::TextIter iter_insert = curr_buffer->get_insert()->get_iter(); Gtk::TextIter iter_bound = curr_buffer->get_selection_bound()->get_iter(); Glib::ustring entry_predefined_text = curr_buffer->get_text(iter_insert, iter_bound); @@ -292,6 +294,40 @@ void CtActions::find_again_iter(const bool fromIterativeDialog) const bool restore_iterative_dialog = _s_options.iterative_dialog; _s_options.iterative_dialog = fromIterativeDialog; _s_state.from_find_iterated = true; + if (0u != _s_state.find_iter_anchlist_size) { + if (_s_state.from_find_back == _s_options.direction_fw) { + if (_s_state.find_iter_anchlist_idx >= 1u) { + --_s_state.find_iter_anchlist_idx; + spdlog::debug("{}-- {}/{}", __FUNCTION__, _s_state.find_iter_anchlist_idx, _s_state.find_iter_anchlist_size); + } + else { + spdlog::debug("{} LOLIM {}/{}", __FUNCTION__, _s_state.find_iter_anchlist_idx, _s_state.find_iter_anchlist_size); + _s_state.find_iter_anchlist_size = 0u; + Glib::RefPtr text_buffer = _pCtMainWin->get_text_view().get_buffer(); + Gtk::TextIter min_iter = text_buffer->get_iter_at_offset(_s_state.latest_match_offsets.first); + if (not min_iter.backward_char()) { + spdlog::debug("?? {} obj at offs 0", __FUNCTION__); + _s_state.find_back_exclude_obj_offs_zero = true; + } + else { + text_buffer->place_cursor(min_iter); + } + } + } + else { + if (_s_state.find_iter_anchlist_idx < (_s_state.find_iter_anchlist_size - 1u)) { + ++_s_state.find_iter_anchlist_idx; + spdlog::debug("{}++ {}/{}", __FUNCTION__, _s_state.find_iter_anchlist_idx, _s_state.find_iter_anchlist_size); + } + else { + spdlog::debug("{} HILIM {}/{}", __FUNCTION__, _s_state.find_iter_anchlist_idx, _s_state.find_iter_anchlist_size); + _s_state.find_iter_anchlist_size = 0u; + Glib::RefPtr text_buffer = _pCtMainWin->get_text_view().get_buffer(); + Gtk::TextIter max_iter = text_buffer->get_iter_at_offset(_s_state.latest_match_offsets.second); + text_buffer->place_cursor(max_iter); + } + } + } switch (_s_state.curr_find_type) { case CtCurrFindType::SingleNode: { find_in_selected_node(); @@ -445,12 +481,12 @@ bool CtActions::_parse_node_name_n_tags_iter(CtTreeIter& node_iter, if (all_matches) { gint64 node_id = node_iter.get_node_id(); Glib::ustring node_hier_name = CtMiscUtil::get_node_hierarchical_name(node_iter, " / ", false/*for_filename*/, true/*root_to_leaf*/); - Glib::ustring line_content = _get_first_line_content(node_iter.get_node_text_buffer()); + Glib::ustring line_content = CtTextIterUtil::get_first_line_content(node_iter.get_node_text_buffer()); const Glib::ustring text_tags = node_iter.get_node_tags(); _s_state.match_store->add_row(node_id, text_tags.empty() ? node_name : node_name + "\n [" + _("Tags") + _(": ") + text_tags + "]", str::xml_escape(node_hier_name), - 0, 0, 1, line_content); + 0, 0, 1, line_content, CtAnchWidgType::None, 0, 0, 0); } if (_s_state.replace_active and not node_iter.get_node_read_only()) { std::string replacer_text = _s_options.str_replace; @@ -588,7 +624,7 @@ bool CtActions::_find_pattern(CtTreeIter tree_iter, std::pair match_offsets{-1, -1}; if (forward) { Glib::MatchInfo match_info; - if (re_pattern->match(text, str::symb_pos_to_byte_pos(text, start_offset) - start_num_objs/*start_position*/, match_info)) { + if (re_pattern->match(text, std::max(str::symb_pos_to_byte_pos(text, start_offset) - start_num_objs, 0)/*start_position*/, match_info)) { if (match_info.matches()) { match_info.fetch_pos(0, match_offsets.first, match_offsets.second); } @@ -596,7 +632,7 @@ bool CtActions::_find_pattern(CtTreeIter tree_iter, } else { Glib::MatchInfo match_info; - re_pattern->match(text, str::symb_pos_to_byte_pos(text, start_offset) - start_num_objs/*string_len*/, 0/*start_position*/, match_info); + re_pattern->match(text, std::max(str::symb_pos_to_byte_pos(text, start_offset) - start_num_objs, 0)/*string_len*/, 0/*start_position*/, match_info); while (match_info.matches()) { match_info.fetch_pos(0, match_offsets.first, match_offsets.second); match_info.next(); @@ -607,36 +643,107 @@ bool CtActions::_find_pattern(CtTreeIter tree_iter, match_offsets.second = str::byte_pos_to_symb_pos(text, match_offsets.second); } - std::pair obj_match_offsets{-1, -1}; - Glib::ustring obj_content; - if (not _s_state.replace_active) { - obj_match_offsets = _check_pattern_in_object_between(tree_iter, text_buffer, re_pattern, - start_iter.get_offset(), match_offsets.first, forward, obj_content); + CtAnchMatchList anchMatchList; + int obj_search_start_offs = start_iter.get_offset(); + int obj_search_end_offs = match_offsets.first != -1 ? match_offsets.first : (forward ? text_buffer->end().get_offset() : 0); + if (not forward) { + std::swap(obj_search_start_offs, obj_search_end_offs); + } + if (_check_pattern_in_object_between(tree_iter, + re_pattern, + obj_search_start_offs, + obj_search_end_offs, + forward, + all_matches, + anchMatchList)) + { + // find_iter_anchlist_idx is always 0 for all_matches, changes only in iterative find + if (not all_matches) { + if (0u == _s_state.find_iter_anchlist_size) { + // first iteration in anch match list + _s_state.find_iter_anchlist_idx = forward ? 0u : anchMatchList.size() - 1u; + } + else if (anchMatchList.size() != _s_state.find_iter_anchlist_size) { + spdlog::debug("?? find_iter_anchlist_size {}->{}", _s_state.find_iter_anchlist_size, anchMatchList.size()); + _s_state.find_iter_anchlist_idx = forward ? 0u : anchMatchList.size() - 1u; + } + else if (_s_state.find_iter_anchlist_idx >= anchMatchList.size()) { + spdlog::debug("?? after anchMatchList of {}", anchMatchList.size()); + _s_state.find_iter_anchlist_idx = forward ? 0u : anchMatchList.size() - 1u; + } + if (not forward and _s_state.find_back_exclude_obj_offs_zero) { + anchMatchList.clear(); + _s_state.find_iter_anchlist_size = 0u; + _s_state.find_back_exclude_obj_offs_zero = false; + spdlog::debug("find_back_exclude_obj_offs_zero"); + return false; + } + _s_state.find_iter_anchlist_size = anchMatchList.size(); + spdlog::debug("anchMatchList {}->{} {}/{}", + obj_search_start_offs, obj_search_end_offs, + _s_state.find_iter_anchlist_idx, _s_state.find_iter_anchlist_size); + } + match_offsets.first = anchMatchList[0]->start_offset; + match_offsets.second = match_offsets.first + 1; + } + else { + if (not all_matches) { + if (0u != _s_state.find_iter_anchlist_size) { + _s_state.find_iter_anchlist_size = 0u; + } + } } - if (obj_match_offsets.first != -1) match_offsets = obj_match_offsets; if (match_offsets.first == -1) return false; // match found! - const int num_objs = _get_num_objs_before_offset(text_buffer, match_offsets.first); - _s_state.latest_match_offsets.first = match_offsets.first + num_objs; - _s_state.latest_match_offsets.second = match_offsets.second + num_objs; + if (0u == anchMatchList.size()) { + const int num_objs = _get_num_objs_before_offset(text_buffer, match_offsets.first); + _s_state.latest_match_offsets.first = match_offsets.first + num_objs; + _s_state.latest_match_offsets.second = match_offsets.second + num_objs; + } + else { + // the match offset comes from the anchored widget + _s_state.latest_match_offsets.first = match_offsets.first; + _s_state.latest_match_offsets.second = match_offsets.second; + } CtMatchRowData* pCtMatchRowData{nullptr}; if (all_matches) { const gint64 node_id = tree_iter.get_node_id(); const Glib::ustring node_name = tree_iter.get_node_name(); const std::string node_hier_name = CtMiscUtil::get_node_hierarchical_name(tree_iter, " / ", false/*for_filename*/, true/*root_to_leaf*/); - const Glib::ustring line_content = obj_match_offsets.first != -1 ? - obj_content : _get_line_content(text_buffer, _s_state.latest_match_offsets.second); - int line_num = text_buffer->get_iter_at_offset(_s_state.latest_match_offsets.first).get_line(); - line_num += 1; + const Glib::ustring esc_node_hier_name = str::xml_escape(node_hier_name); const Glib::ustring text_tags = tree_iter.get_node_tags(); - pCtMatchRowData = _s_state.match_store->add_row(node_id, - text_tags.empty() ? node_name : node_name + "\n [" + _("Tags") + _(": ") + text_tags + "]", - str::xml_escape(node_hier_name), - _s_state.latest_match_offsets.first, - _s_state.latest_match_offsets.second, - line_num, - line_content); + const Glib::ustring node_name_w_tags = text_tags.empty() ? node_name : node_name + "\n [" + _("Tags") + _(": ") + text_tags + "]"; + if (0u == anchMatchList.size()) { + const int line_num = text_buffer->get_iter_at_offset(_s_state.latest_match_offsets.first).get_line(); + const Glib::ustring line_content = CtTextIterUtil::get_line_content(text_buffer, _s_state.latest_match_offsets.second); + pCtMatchRowData = _s_state.match_store->add_row(node_id, + node_name_w_tags, + esc_node_hier_name, + _s_state.latest_match_offsets.first, + _s_state.latest_match_offsets.second, + line_num, + line_content, + CtAnchWidgType::None, 0, 0, 0); + } + else { + for (std::shared_ptr& pAnchMatch : anchMatchList) { + _s_state.latest_match_offsets.first = pAnchMatch->start_offset; + _s_state.latest_match_offsets.second = _s_state.latest_match_offsets.first + 1; + const int line_num = text_buffer->get_iter_at_offset(_s_state.latest_match_offsets.first).get_line(); + (void)_s_state.match_store->add_row(node_id, + node_name_w_tags, + esc_node_hier_name, + _s_state.latest_match_offsets.first, + _s_state.latest_match_offsets.second, + line_num, + pAnchMatch->line_content, + pAnchMatch->anch_type, + pAnchMatch->anch_cell_idx, + pAnchMatch->anch_offs_start, + pAnchMatch->anch_offs_end); + } + } } else { CtTreeIter curr_tree_iter = _pCtMainWin->curr_tree_iter(); @@ -646,8 +753,18 @@ bool CtActions::_find_pattern(CtTreeIter tree_iter, CtTextView& ct_text_view = _pCtMainWin->get_text_view(); ct_text_view.set_selection_at_offset_n_delta(_s_state.latest_match_offsets.first, match_offsets.second - match_offsets.first); ct_text_view.scroll_to(text_buffer->get_insert(), CtTextView::TEXT_SCROLL_MARGIN); + if (anchMatchList.size() > 0u) { + auto& pAnchMatch = anchMatchList[_s_state.find_iter_anchlist_idx]; + CtActions::find_match_in_obj_focus(_s_state.latest_match_offsets.first, + text_buffer, + tree_iter, + pAnchMatch->anch_type, + pAnchMatch->anch_cell_idx, + pAnchMatch->anch_offs_start, + pAnchMatch->anch_offs_end); + } } - if (_s_state.replace_active) { + if (_s_state.replace_active and 0u == anchMatchList.size()) { if (tree_iter.get_node_read_only()) return false; Gtk::TextIter sel_start = text_buffer->get_iter_at_offset(_s_state.latest_match_offsets.first); Gtk::TextIter sel_end = text_buffer->get_iter_at_offset(_s_state.latest_match_offsets.second); @@ -676,79 +793,212 @@ bool CtActions::_find_pattern(CtTreeIter tree_iter, return true; } -// Search for the pattern in the given object -Glib::ustring CtActions::_check_pattern_in_object(Glib::RefPtr pattern, CtAnchoredWidget* obj) +/*static*/void CtActions::find_match_in_obj_focus(const int obj_offset, + Glib::RefPtr pTextBuffer, + const CtTreeIter& tree_iter, + const CtAnchWidgType anch_type, + const size_t anch_cell_idx, + const int anch_offs_start, + const int anch_offs_end) { - if (CtImageEmbFile* image = dynamic_cast(obj)) { - Glib::ustring text = image->get_file_name().string(); - if (_s_options.accent_insensitive) { - text = str::diacritical_to_ascii(text); + spdlog::debug("{} obj={} cell={} {}->{}", __FUNCTION__, obj_offset, anch_cell_idx, anch_offs_start, anch_offs_end); + Gtk::TextIter anchor_iter = pTextBuffer->get_iter_at_offset(obj_offset); + Glib::RefPtr rChildAnchor = anchor_iter.get_child_anchor(); + if (rChildAnchor) { + CtAnchoredWidget* pCtAnchoredWidget = tree_iter.get_anchored_widget(rChildAnchor); + if (pCtAnchoredWidget) { + switch (anch_type) { + case CtAnchWidgType::CodeBox: { + if (auto pCodebox = dynamic_cast(pCtAnchoredWidget)) { + pCodebox->get_text_view().set_selection_at_offset_n_delta(anch_offs_start, + anch_offs_end - anch_offs_start); + } + else { + spdlog::debug("? {} !pCodebox", __FUNCTION__); + } + } break; + case CtAnchWidgType::TableHeavy: [[fallthrough]]; + case CtAnchWidgType::TableLight: { + if (auto pTable = dynamic_cast(pCtAnchoredWidget)) { + const size_t num_columns = pTable->get_num_columns(); + const size_t rowIdx = anch_cell_idx / num_columns; + const size_t colIdx = anch_cell_idx % num_columns; + pTable->set_current_row_column(rowIdx, colIdx); + pTable->grab_focus(); + pTable->set_selection_at_offset_n_delta(anch_offs_start, + anch_offs_end - anch_offs_start); + } + else { + spdlog::debug("? {} !pTable", __FUNCTION__); + } + } break; + default: break; + } } - if (pattern->match(text)) return text; - } - else if (CtImageAnchor* image = dynamic_cast(obj)) { - Glib::ustring text = image->get_anchor_name(); - if (_s_options.accent_insensitive) { - text = str::diacritical_to_ascii(text); + else { + spdlog::debug("? {} !pCtAnchoredWidget", __FUNCTION__); } - if (pattern->match(text)) return text; } - else if (auto table = dynamic_cast(obj)) { - std::vector> rows; - table->write_strings_matrix(rows); - for (auto& row : rows) { - for (Glib::ustring& col : row) { + else { + spdlog::debug("? {} !rChildAnchor", __FUNCTION__); + } +} + +bool CtActions::_check_pattern_in_object(Glib::RefPtr re_pattern, + CtAnchoredWidget* pAnchWidg, + CtAnchMatchList& anchMatchList) +{ + bool retVal{false}; + const CtAnchWidgType anchWidgType = pAnchWidg->get_type(); + switch (anchWidgType) { + case CtAnchWidgType::ImageEmbFile: { + if (CtImageEmbFile* pImageEmbFile = dynamic_cast(pAnchWidg)) { + Glib::ustring text = pImageEmbFile->get_file_name().string(); if (_s_options.accent_insensitive) { - col = str::diacritical_to_ascii(col); + text = str::diacritical_to_ascii(text); } - if (pattern->match(col)) { - return ""; + if (re_pattern->match(text)) { + auto pAnchMatch = std::make_shared(); + pAnchMatch->start_offset = pAnchWidg->getOffset(); + pAnchMatch->line_content = text; + pAnchMatch->anch_type = anchWidgType; + anchMatchList.push_back(pAnchMatch); + retVal = true; } } - } - } - else if (CtCodebox* codebox = dynamic_cast(obj)) { - Glib::ustring text = codebox->get_text_content(); - if (_s_options.accent_insensitive) { - text = str::diacritical_to_ascii(text); - } - if (pattern->match(text)) return ""; + else { + spdlog::warn("!! unexp no CtImageEmbFile"); + } + } break; + case CtAnchWidgType::ImageAnchor: { + if (CtImageAnchor* pImageAnchor = dynamic_cast(pAnchWidg)) { + Glib::ustring text = pImageAnchor->get_anchor_name(); + if (_s_options.accent_insensitive) { + text = str::diacritical_to_ascii(text); + } + if (re_pattern->match(text)) { + auto pAnchMatch = std::make_shared(); + pAnchMatch->start_offset = pAnchWidg->getOffset(); + pAnchMatch->line_content = text; + pAnchMatch->anch_type = anchWidgType; + anchMatchList.push_back(pAnchMatch); + retVal = true; + } + } + else { + spdlog::warn("!! unexp no CtImageAnchor"); + } + } break; + case CtAnchWidgType::CodeBox: { + if (CtCodebox* pCodebox = dynamic_cast(pAnchWidg)) { + Glib::ustring text = pCodebox->get_text_content(); + if (_s_options.accent_insensitive) { + text = str::diacritical_to_ascii(text); + } + Glib::MatchInfo match_info; + if (re_pattern->match(text, match_info)) { + CtAnchMatchList localAnchMatchList; + while (match_info.matches()) { + int match_start_offset, match_end_offset; + match_info.fetch_pos(0, match_start_offset, match_end_offset); + match_start_offset = str::byte_pos_to_symb_pos(text, match_start_offset); + match_end_offset = str::byte_pos_to_symb_pos(text, match_end_offset); + auto pAnchMatch = std::make_shared(); + pAnchMatch->start_offset = pAnchWidg->getOffset(); + pAnchMatch->line_content = CtTextIterUtil::get_line_content(pCodebox->get_buffer(), match_end_offset); + pAnchMatch->anch_type = anchWidgType; + pAnchMatch->anch_offs_start = match_start_offset; + pAnchMatch->anch_offs_end = match_end_offset; + localAnchMatchList.push_back(pAnchMatch); + match_info.next(); + } + for (auto& pAnchMatch : localAnchMatchList) { + anchMatchList.push_back(pAnchMatch); + } + retVal = true; + } + } + else { + spdlog::warn("!! unexp no CtCodebox"); + } + } break; + case CtAnchWidgType::TableHeavy: + case CtAnchWidgType::TableLight: { + if (auto pTable = dynamic_cast(pAnchWidg)) { + std::vector> rows; + pTable->write_strings_matrix(rows); + CtAnchMatchList localAnchMatchList; + size_t rowIdx{0u}; + for (auto& row : rows) { + size_t colIdx{0u}; + for (Glib::ustring& text : row) { + if (_s_options.accent_insensitive) { + text = str::diacritical_to_ascii(text); + } + Glib::MatchInfo match_info; + if (re_pattern->match(text, match_info)) { + while (match_info.matches()) { + int match_start_offset, match_end_offset; + match_info.fetch_pos(0, match_start_offset, match_end_offset); + match_start_offset = str::byte_pos_to_symb_pos(text, match_start_offset); + match_end_offset = str::byte_pos_to_symb_pos(text, match_end_offset); + auto pAnchMatch = std::make_shared(); + pAnchMatch->start_offset = pAnchWidg->getOffset(); + pAnchMatch->line_content = pTable->get_line_content(rowIdx, colIdx, match_end_offset); + pAnchMatch->anch_type = anchWidgType; + pAnchMatch->anch_offs_start = match_start_offset; + pAnchMatch->anch_offs_end = match_end_offset; + pAnchMatch->anch_cell_idx = pTable->get_num_columns()*rowIdx + colIdx; + localAnchMatchList.push_back(pAnchMatch); + match_info.next(); + } + } + ++colIdx; + } + ++rowIdx; + } + if (localAnchMatchList.size() > 0u) { + for (auto& pAnchMatch : localAnchMatchList) { + anchMatchList.push_back(pAnchMatch); + } + retVal = true; + } + } + else { + spdlog::warn("!! unexp no CtTableCommon"); + } + } break; + default: break; } - return ""; + return retVal; } -// Search for the pattern in the given slice and direction -std::pair CtActions::_check_pattern_in_object_between(CtTreeIter tree_iter, - Glib::RefPtr text_buffer, - Glib::RefPtr pattern, - int start_offset, - int end_offset, - bool forward, - Glib::ustring& obj_content) +bool CtActions::_check_pattern_in_object_between(CtTreeIter tree_iter, + Glib::RefPtr re_pattern, + int start_offset, + int end_offset, + const bool forward, + const bool all_matches, + CtAnchMatchList& anchMatchList) { - if (not forward) start_offset -= 1; - if (end_offset < 0) { - if (forward) { - Gtk::TextIter start, end; - text_buffer->get_bounds(start, end); - end_offset = end.get_offset(); - } else - end_offset = 0; - } - if (not forward) std::swap(start_offset, end_offset); - + bool retVal{false}; std::list obj_vec = tree_iter.get_anchored_widgets(start_offset, end_offset); - if (not forward) + if (not forward) { std::reverse(obj_vec.begin(), obj_vec.end()); - for (auto element : obj_vec) { - obj_content = _check_pattern_in_object(pattern, element); - if (not obj_content.empty()) - return {element->getOffset(), element->getOffset() + 1}; } - return {-1, -1}; + for (CtAnchoredWidget* pAnchWidg : obj_vec) { + if (_check_pattern_in_object(re_pattern, pAnchWidg, anchMatchList)) { + if (not retVal) { + retVal = true; + } + if (not all_matches) { + break; + } + } + } + return retVal; } -// Returns the num of objects from buffer start to the given offset int CtActions::_get_num_objs_before_offset(Glib::RefPtr text_buffer, int max_offset) { int num_objs = 0; @@ -771,41 +1021,6 @@ int CtActions::_get_num_objs_before_offset(Glib::RefPtr text_bu return num_objs; } -// Returns the Line Content Given the Text Iter -Glib::ustring CtActions::_get_line_content(Glib::RefPtr text_buffer, const int match_end_offset) -{ - Gtk::TextIter line_start = text_buffer->get_iter_at_offset(match_end_offset); - Gtk::TextIter line_end = line_start; - if (not line_start.backward_char()) return ""; - while (line_start.get_char() != '\n') - if (not line_start.backward_char()) - break; - if (line_start.get_char() == '\n') - line_start.forward_char(); - while (line_end.get_char() != '\n') - if (not line_end.forward_char()) - break; - Glib::ustring line_content = text_buffer->get_text(line_start, line_end); - return line_content.size() <= _line_content_limit ? - line_content : line_content.substr(0u, _line_content_limit) + "..."; -} - -// Returns the First Not Empty Line Content Given the Text Buffer -Glib::ustring CtActions::_get_first_line_content(Glib::RefPtr text_buffer) -{ - Gtk::TextIter start_iter = text_buffer->get_iter_at_offset(0); - while (start_iter.get_char() == '\n') - if (not start_iter.forward_char()) - return ""; - Gtk::TextIter end_iter = start_iter; - while (end_iter.get_char() != '\n') - if (not end_iter.forward_char()) - break; - Glib::ustring line_content = text_buffer->get_text(start_iter, end_iter); - return line_content.size() <= _line_content_limit ? - line_content : line_content.substr(0u, _line_content_limit) + "..."; -} - void CtActions::_update_all_matches_progress() { double frac = double(_s_state.processed_nodes)/double(_s_state.counted_nodes); diff --git a/src/ct/ct_dialogs.h b/src/ct/ct_dialogs.h index cb7960a14..ef8fa1b57 100644 --- a/src/ct/ct_dialogs.h +++ b/src/ct/ct_dialogs.h @@ -92,6 +92,10 @@ struct CtMatchRowData { int end_offset; int line_num; Glib::ustring line_content; + CtAnchWidgType anch_type; + int anch_cell_idx; + int anch_offs_start; + int anch_offs_end; }; class CtMatchDialogStore : public Gtk::ListStore { @@ -105,6 +109,10 @@ class CtMatchDialogStore : public Gtk::ListStore Gtk::TreeModelColumn end_offset; Gtk::TreeModelColumn line_num; Gtk::TreeModelColumn line_content; + Gtk::TreeModelColumn anch_type; + Gtk::TreeModelColumn anch_cell_idx; + Gtk::TreeModelColumn anch_offs_start; + Gtk::TreeModelColumn anch_offs_end; CtMatchModelColumns() { add(node_id); add(node_name); @@ -113,6 +121,10 @@ class CtMatchDialogStore : public Gtk::ListStore add(end_offset); add(line_num); add(line_content); + add(anch_type); + add(anch_cell_idx); + add(anch_offs_start); + add(anch_offs_end); } } columns; std::array dlg_size{0,0}; @@ -122,13 +134,17 @@ class CtMatchDialogStore : public Gtk::ListStore static Glib::RefPtr create(const size_t maxMatchesInPage); void deep_clear(); - CtMatchRowData* add_row(gint64 node_id, + CtMatchRowData* add_row(const gint64 node_id, const Glib::ustring& node_name, const Glib::ustring& node_hier_name, - int start_offset, - int end_offset, - int line_num, - const Glib::ustring& line_content); + const int start_offset, + const int end_offset, + const int line_num, + const Glib::ustring& line_content, + const CtAnchWidgType anch_type, + const int anch_cell_idx, + const int anch_offs_start, + const int anch_offs_end); void load_current_page(); void load_next_page(); void load_prev_page(); diff --git a/src/ct/ct_dialogs_find.cc b/src/ct/ct_dialogs_find.cc index c445114a7..8416a1590 100644 --- a/src/ct/ct_dialogs_find.cc +++ b/src/ct/ct_dialogs_find.cc @@ -381,13 +381,17 @@ void CtMatchDialogStore::deep_clear() _all_matches.clear(); } -CtMatchRowData* CtMatchDialogStore::add_row(gint64 node_id, +CtMatchRowData* CtMatchDialogStore::add_row(const gint64 node_id, const Glib::ustring& node_name, const Glib::ustring& node_hier_name, - int start_offset, - int end_offset, - int line_num, - const Glib::ustring& line_content) + const int start_offset, + const int end_offset, + const int line_num, + const Glib::ustring& line_content, + const CtAnchWidgType anch_type, + const int anch_cell_idx, + const int anch_offs_start, + const int anch_offs_end) { _all_matches.push_back(CtMatchRowData{ .node_id = node_id, @@ -396,7 +400,11 @@ CtMatchRowData* CtMatchDialogStore::add_row(gint64 node_id, .start_offset = start_offset, .end_offset = end_offset, .line_num = line_num, - .line_content = line_content + .line_content = line_content, + .anch_type = anch_type, + .anch_cell_idx = anch_cell_idx, + .anch_offs_start = anch_offs_start, + .anch_offs_end = anch_offs_end }); return &_all_matches.back(); } @@ -484,6 +492,10 @@ Gtk::TreeIter CtMatchDialogStore::_add_row(const CtMatchRowData& row_data) { row[columns.end_offset] = row_data.end_offset; row[columns.line_num] = row_data.line_num; row[columns.line_content] = row_data.line_content; + row[columns.anch_type] = row_data.anch_type; + row[columns.anch_cell_idx] = row_data.anch_cell_idx; + row[columns.anch_offs_start] = row_data.anch_offs_start; + row[columns.anch_offs_end] = row_data.anch_offs_end; return retIter; } @@ -542,10 +554,12 @@ void CtDialogs::match_dialog(const std::string& str_find, auto select_found_line = [pTreeview, &s_state, pCtMainWin](){ if (s_state.in_loading) { + //spdlog::debug("in_loading"); return; } Gtk::TreeIter list_iter = pTreeview->get_selection()->get_selected(); if (not list_iter) { + //spdlog::debug("!get_selected"); return; } gint64 node_id = list_iter->get_value(s_state.match_store->columns.node_id); @@ -558,14 +572,31 @@ void CtDialogs::match_dialog(const std::string& str_find, } // remove previous selection because it can cause freezing in specific cases, see more in issue auto fake_iter = pCtMainWin->get_text_view().get_buffer()->get_iter_at_offset(-1); - pCtMainWin->get_text_view().get_buffer()->place_cursor(fake_iter); + auto rCurrBuffer = pCtMainWin->get_text_view().get_buffer(); + rCurrBuffer->place_cursor(fake_iter); pCtMainWin->get_tree_view().set_cursor_safe(tree_iter); - auto rCurrBuffer = pCtMainWin->get_text_view().get_buffer(); - rCurrBuffer->select_range(rCurrBuffer->get_iter_at_offset(list_iter->get_value(s_state.match_store->columns.start_offset)), - rCurrBuffer->get_iter_at_offset(list_iter->get_value(s_state.match_store->columns.end_offset))); + const int start_offset = list_iter->get_value(s_state.match_store->columns.start_offset); + const int end_offset = list_iter->get_value(s_state.match_store->columns.end_offset); + rCurrBuffer->select_range(rCurrBuffer->get_iter_at_offset(start_offset), + rCurrBuffer->get_iter_at_offset(end_offset)); pCtMainWin->get_text_view().scroll_to(rCurrBuffer->get_insert(), CtTextView::TEXT_SCROLL_MARGIN); + const CtAnchWidgType anch_type = list_iter->get_value(s_state.match_store->columns.anch_type); + //spdlog::debug("anch_type={}", static_cast(anch_type)); + if (CtAnchWidgType::None != anch_type) { + const size_t anch_cell_idx = list_iter->get_value(s_state.match_store->columns.anch_cell_idx); + const int anch_offs_start = list_iter->get_value(s_state.match_store->columns.anch_offs_start); + const int anch_offs_end = list_iter->get_value(s_state.match_store->columns.anch_offs_end); + CtActions::find_match_in_obj_focus(start_offset, + rCurrBuffer, + tree_iter, + anch_type, + anch_cell_idx, + anch_offs_start, + anch_offs_end); + } + // pump events so UI's not going to freeze (#835) while (gdk_events_pending()) gtk_main_iteration(); diff --git a/src/ct/ct_misc_utils.cc b/src/ct/ct_misc_utils.cc index 707163507..fc979996f 100644 --- a/src/ct/ct_misc_utils.cc +++ b/src/ct/ct_misc_utils.cc @@ -523,6 +523,57 @@ int CtTextIterUtil::get_words_count(const Glib::RefPtr& text_bu return words; } +// Returns the Line Content Given the Text Iter +Glib::ustring CtTextIterUtil::get_line_content(Glib::RefPtr text_buffer, const int match_end_offset) +{ + Gtk::TextIter line_start = text_buffer->get_iter_at_offset(match_end_offset); + Gtk::TextIter line_end = line_start; + if (not line_start.backward_char()) return ""; + while (line_start.get_char() != '\n') + if (not line_start.backward_char()) + break; + if (line_start.get_char() == '\n') + line_start.forward_char(); + while (line_end.get_char() != '\n') + if (not line_end.forward_char()) + break; + Glib::ustring line_content = text_buffer->get_text(line_start, line_end); + return line_content.size() <= LINE_CONTENT_LIMIT ? + line_content : line_content.substr(0u, LINE_CONTENT_LIMIT) + "..."; +} + +Glib::ustring CtTextIterUtil::get_line_content(const Glib::ustring& text_multiline, const int match_end_offset) +{ + std::vector splitted = str::split(text_multiline, "\n"); + int sum_rows_chars{0}; + for (const Glib::ustring& line_content : splitted) { + sum_rows_chars += line_content.size(); + ++sum_rows_chars; // newline + if (sum_rows_chars >= match_end_offset) { + return line_content.size() <= LINE_CONTENT_LIMIT ? + line_content : line_content.substr(0u, LINE_CONTENT_LIMIT) + "..."; + } + } + spdlog::warn("!! {} offs {}", __FUNCTION__, match_end_offset); + return "!?"; +} + +// Returns the First Not Empty Line Content Given the Text Buffer +Glib::ustring CtTextIterUtil::get_first_line_content(Glib::RefPtr text_buffer) +{ + Gtk::TextIter start_iter = text_buffer->get_iter_at_offset(0); + while (start_iter.get_char() == '\n') + if (not start_iter.forward_char()) + return ""; + Gtk::TextIter end_iter = start_iter; + while (end_iter.get_char() != '\n') + if (not end_iter.forward_char()) + break; + Glib::ustring line_content = text_buffer->get_text(start_iter, end_iter); + return line_content.size() <= LINE_CONTENT_LIMIT ? + line_content : line_content.substr(0u, LINE_CONTENT_LIMIT) + "..."; +} + // copied from https://gitlab.gnome.org/GNOME/gtk.git origin/gtk-3-24 gtkpango.c _gtk_pango_unichar_direction PangoDirection CtStrUtil::gtk_pango_unichar_direction(gunichar ch) { diff --git a/src/ct/ct_misc_utils.h b/src/ct/ct_misc_utils.h index 059fc1fb5..831ce36a4 100644 --- a/src/ct/ct_misc_utils.h +++ b/src/ct/ct_misc_utils.h @@ -175,6 +175,11 @@ PangoDirection get_pango_direction(const Gtk::TextIter& textIter); int get_words_count(const Glib::RefPtr& text_buffer); +const inline static size_t LINE_CONTENT_LIMIT{100u}; +Glib::ustring get_line_content(Glib::RefPtr text_buffer, const int match_end_offset); +Glib::ustring get_line_content(const Glib::ustring& text_multiline, const int match_end_offset); +Glib::ustring get_first_line_content(Glib::RefPtr text_buffer); + } // namespace CtTextIterUtil namespace CtStrUtil { diff --git a/src/ct/ct_table.cc b/src/ct/ct_table.cc index 9f4a35336..93e44126c 100644 --- a/src/ct/ct_table.cc +++ b/src/ct/ct_table.cc @@ -1,7 +1,7 @@ /* * ct_table.cc * - * Copyright 2009-2023 + * Copyright 2009-2024 * Giuseppe Penone * Evgenii Gurianov * @@ -65,6 +65,22 @@ void CtTableCommon::row_move_down(const size_t rowIdx) grab_focus(); } +void CtTableCommon::set_current_row_column(const size_t rowIdx, const size_t colIdx) +{ + if (rowIdx < get_num_rows()) { + _currentRow = rowIdx; + } + else { + spdlog::warn("?? {} row {} >= {}", __FUNCTION__, rowIdx, get_num_rows()); + } + if (colIdx < get_num_columns()) { + _currentColumn = colIdx; + } + else { + spdlog::warn("?? {} col {} >= {}", __FUNCTION__, colIdx, get_num_columns()); + } +} + bool CtTableCommon::on_table_button_press_event(GdkEventButton* event) { _pCtMainWin->get_ct_actions()->curr_table_anchor = this; @@ -593,11 +609,33 @@ void CtTableHeavy::grab_focus() const static_cast(_tableMatrix.at(current_row()).at(current_column()))->get_text_view().grab_focus(); } +void CtTableHeavy::set_selection_at_offset_n_delta(const int offset, const int delta) const +{ + curr_cell_text_view().set_selection_at_offset_n_delta(offset, delta); +} + CtTextView& CtTableHeavy::curr_cell_text_view() const { return static_cast(_tableMatrix.at(current_row()).at(current_column()))->get_text_view(); } +Glib::RefPtr CtTableHeavy::get_buffer(const size_t rowIdx, const size_t colIdx) const +{ + if (rowIdx < get_num_rows() and colIdx < get_num_columns()) { + return static_cast(_tableMatrix.at(rowIdx).at(colIdx))->get_buffer(); + } + return Glib::RefPtr{}; +} + +Glib::ustring CtTableHeavy::get_line_content(size_t rowIdx, size_t colIdx, int match_end_offset) const +{ + Glib::RefPtr pBuffer = get_buffer(rowIdx, colIdx); + if (pBuffer) { + return CtTextIterUtil::get_line_content(pBuffer, match_end_offset); + } + return "!?"; +} + void CtTableHeavy::_on_grid_set_focus_child(Gtk::Widget* pWidget) { for (size_t rowIdx = 0; rowIdx < get_num_rows(); ++rowIdx) { diff --git a/src/ct/ct_table.h b/src/ct/ct_table.h index 268727c19..4455cf9cf 100644 --- a/src/ct/ct_table.h +++ b/src/ct/ct_table.h @@ -1,7 +1,7 @@ /* * ct_table.h * - * Copyright 2009-2023 + * Copyright 2009-2024 * Giuseppe Penone * Evgenii Gurianov * @@ -69,14 +69,18 @@ class CtTableCommon : public CtAnchoredWidget // Serialise to csv format; The output CSV excel csv with double quotes around cells and newlines for each record virtual std::string to_csv() const = 0; + virtual Glib::ustring get_line_content(const size_t rowIdx, const size_t colIdx, const int match_end_offset) const = 0; + virtual void write_strings_matrix(std::vector>& rows) const = 0; virtual size_t get_num_rows() const = 0; virtual size_t get_num_columns() const = 0; + size_t current_row() const { return _currentRow < get_num_rows() ? _currentRow : 0; } size_t current_column() const { return _currentColumn < get_num_columns() ? _currentColumn : 0; } bool row_sort_asc() { return _row_sort(true/*sortAsc*/); } bool row_sort_desc() { return _row_sort(false/*sortAsc*/); } void row_move_down(const size_t rowIdx); + void set_current_row_column(const size_t rowIdx, const size_t colIdx); virtual void column_add(const size_t afterColIdx) = 0; virtual void column_delete(const size_t colIdx) = 0; @@ -91,6 +95,7 @@ class CtTableCommon : public CtAnchoredWidget virtual void grab_focus() const = 0; virtual void exit_cell_edit() const = 0; + virtual void set_selection_at_offset_n_delta(const int offset, const int delta) const = 0; bool on_table_button_press_event(GdkEventButton* event); void on_cell_populate_popup(Gtk::Menu* menu); @@ -136,6 +141,7 @@ class CtTableLight : public CtTableCommon void apply_syntax_highlighting(const bool /*forceReApply*/) override {} std::string to_csv() const override; + Glib::ustring get_line_content(const size_t rowIdx, const size_t colIdx, const int match_end_offset) const override; void set_modified_false() override {} CtAnchWidgType get_type() const override { return CtAnchWidgType::TableLight; } std::shared_ptr get_state() override; @@ -157,6 +163,7 @@ class CtTableLight : public CtTableCommon void grab_focus() const override; void exit_cell_edit() const override; + void set_selection_at_offset_n_delta(const int offset, const int delta) const override; protected: void _reset(CtTableMatrix& tableMatrix); @@ -192,11 +199,13 @@ class CtTableHeavy : public CtTableCommon void apply_syntax_highlighting(const bool forceReApply) override; std::string to_csv() const override; + Glib::ustring get_line_content(const size_t rowIdx, const size_t colIdx, const int match_end_offset) const override; void set_modified_false() override; CtAnchWidgType get_type() const override { return CtAnchWidgType::TableHeavy; } std::shared_ptr get_state() override; CtTextView& curr_cell_text_view() const; + Glib::RefPtr get_buffer(const size_t rowIdx, const size_t colIdx) const; void write_strings_matrix(std::vector>& rows) const override; size_t get_num_rows() const override { return _tableMatrix.size(); } @@ -215,6 +224,7 @@ class CtTableHeavy : public CtTableCommon void grab_focus() const override; void exit_cell_edit() const override {} + void set_selection_at_offset_n_delta(const int offset, const int delta) const override; protected: void _apply_styles_to_cells(const bool forceReApply); diff --git a/src/ct/ct_table_light.cc b/src/ct/ct_table_light.cc index dfa95bf7f..4cf879696 100644 --- a/src/ct/ct_table_light.cc +++ b/src/ct/ct_table_light.cc @@ -1,7 +1,7 @@ /* * ct_table_light.cc * - * Copyright 2009-2023 + * Copyright 2009-2024 * Giuseppe Penone * Evgenii Gurianov * @@ -461,6 +461,33 @@ void CtTableLight::exit_cell_edit() const _pManagedTreeView->set_cursor(Gtk::TreePath{std::to_string(current_row())}); } +void CtTableLight::set_selection_at_offset_n_delta(const int offset, const int delta) const +{ + if (not _pEditingCellEntry) { + spdlog::warn("!! {} !_pEditingCellEntry", __FUNCTION__); + return; + } + _pEditingCellEntry->select_region(offset, offset+delta); +} + +Glib::ustring CtTableLight::get_line_content(const size_t rowIdx, const size_t colIdx, const int match_end_offset) const +{ + Gtk::TreePath treePath{std::to_string(rowIdx)}; + Gtk::TreeIter treeIter = _pListStore->get_iter(treePath); + if (not treeIter) { + spdlog::warn("!! {} row {}", __FUNCTION__, rowIdx); + return "!?"; + } + Gtk::TreeRow treeRow = *treeIter; + const CtTableLightColumns& cols = get_columns(); + if (cols.columnsText.size() <= colIdx) { + spdlog::warn("!! {} col {}", __FUNCTION__, colIdx); + return "!?"; + } + Glib::ustring cellText = treeRow[cols.columnsText.at(colIdx)]; + return CtTextIterUtil::get_line_content(cellText, match_end_offset); +} + void CtTableLight::_on_treeview_event_after(GdkEvent* event) { if (event->type == GDK_BUTTON_PRESS and event->button.button == 1) { diff --git a/src/ct/ct_treestore.cc b/src/ct/ct_treestore.cc index ab479bad0..8b4f41d53 100644 --- a/src/ct/ct_treestore.cc +++ b/src/ct/ct_treestore.cc @@ -1431,6 +1431,7 @@ bool CtTreeStore::populate_summary_info(CtSummaryInfo& summaryInfo) case CtAnchWidgType::ImagePng: ++summaryInfo.images_num; break; case CtAnchWidgType::TableHeavy: ++summaryInfo.heavytables_num; break; case CtAnchWidgType::TableLight: ++summaryInfo.lighttables_num; break; + default: break; } } } diff --git a/src/ct/ct_types.h b/src/ct/ct_types.h index 774d01f92..3425d9f44 100644 --- a/src/ct/ct_types.h +++ b/src/ct/ct_types.h @@ -46,7 +46,7 @@ enum class CtDocType { None, XML, SQLite, MultiFile }; enum class CtDocEncrypt { None, True, False }; -enum class CtAnchWidgType { CodeBox, TableHeavy, TableLight, ImagePng, ImageAnchor, ImageLatex, ImageEmbFile }; +enum class CtAnchWidgType { None, CodeBox, TableHeavy, TableLight, ImagePng, ImageAnchor, ImageLatex, ImageEmbFile }; enum class CtPixTabCBox { Pixbuf, Table, CodeBox }; @@ -442,6 +442,9 @@ struct CtSearchState { std::string curr_find_pattern; bool from_find_iterated{false}; bool from_find_back{false}; + bool find_back_exclude_obj_offs_zero{false}; + size_t find_iter_anchlist_idx{0u}; + size_t find_iter_anchlist_size{0u}; bool first_useful_node{false}; int counted_nodes{0}; @@ -462,3 +465,14 @@ struct CtSearchState { Gtk::Dialog* pMatchStoreDialog{nullptr}; bool in_loading{false}; }; + +struct CtAnchMatch { + int start_offset; + Glib::ustring line_content; + CtAnchWidgType anch_type; + size_t anch_cell_idx; + int anch_offs_start; + int anch_offs_end; +}; + +using CtAnchMatchList = std::vector>; diff --git a/tests/tests_read_write.cpp b/tests/tests_read_write.cpp index 6d79abf07..183282c1b 100644 --- a/tests/tests_read_write.cpp +++ b/tests/tests_read_write.cpp @@ -660,6 +660,7 @@ void TestCtApp::_assert_tree_data(CtMainWin* pWin, const bool after_mods) "$a^2+b^2=c^2$\n" "\\end{document}", pImageLatex->get_latex_text().c_str()); } break; + default: break; } } }