"Fossies" - the Fresh Open Source Software Archive

Member "regexxer-0.10/src/filetree.cc" (6 Oct 2011, 28950 Bytes) of package /linux/privat/old/regexxer-0.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "filetree.cc" see the Fossies "Dox" file reference documentation.

    1 /*
    2  * Copyright (c) 2002-2007  Daniel Elstner  <daniel.kitta@gmail.com>
    3  *
    4  * This file is part of regexxer.
    5  *
    6  * regexxer is free software; you can redistribute it and/or modify
    7  * it under the terms of the GNU General Public License as published by
    8  * the Free Software Foundation; either version 2 of the License, or
    9  * (at your option) any later version.
   10  *
   11  * regexxer is distributed in the hope that it will be useful,
   12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14  * GNU General Public License for more details.
   15  *
   16  * You should have received a copy of the GNU General Public License
   17  * along with regexxer; if not, write to the Free Software Foundation,
   18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
   19  */
   20 
   21 #include "filetree.h"
   22 #include "filetreeprivate.h"
   23 #include "globalstrings.h"
   24 #include "stringutils.h"
   25 #include "translation.h"
   26 #include "settings.h"
   27 
   28 #include <glibmm.h>
   29 #include <gtkmm/stock.h>
   30 
   31 #include <config.h>
   32 
   33 namespace Regexxer
   34 {
   35 
   36 using namespace Regexxer::FileTreePrivate;
   37 
   38 /**** Regexxer::FileTree ***************************************************/
   39 
   40 FileTree::FileTree()
   41 :
   42   treestore_      (Gtk::TreeStore::create(FileTreeColumns::instance())),
   43   color_modified_ ("#DF421E"), // accent red
   44   sum_matches_    (0)
   45 {
   46   using namespace Gtk;
   47   using sigc::mem_fun;
   48 
   49   set_model(treestore_);
   50   const FileTreeColumns& model_columns = FileTreeColumns::instance();
   51 
   52   treestore_->set_default_sort_func(&default_sort_func);
   53   treestore_->set_sort_func(model_columns.collatekey, &collatekey_sort_func);
   54   treestore_->set_sort_column(TreeStore::DEFAULT_SORT_COLUMN_ID, SORT_ASCENDING);
   55 
   56   treestore_->signal_rows_reordered()
   57       .connect(mem_fun(*this, &FileTree::on_treestore_rows_reordered));
   58 
   59   {
   60     Column *const column = new Column(_("File"));
   61     append_column(*manage(column));
   62 
   63     CellRendererPixbuf *const cell_icon = new CellRendererPixbuf();
   64     column->pack_start(*manage(cell_icon), false);
   65 
   66     CellRendererText *const cell_filename = new CellRendererText();
   67     column->pack_start(*manage(cell_filename));
   68 
   69     column->add_attribute(cell_filename->property_text(), model_columns.filename);
   70     column->set_cell_data_func(*cell_icon,     mem_fun(*this, &FileTree::icon_cell_data_func));
   71     column->set_cell_data_func(*cell_filename, mem_fun(*this, &FileTree::text_cell_data_func));
   72 
   73     column->set_resizable(true);
   74     column->set_expand(true);
   75 
   76     column->set_sort_column(model_columns.collatekey);
   77   }
   78   {
   79     Column *const column = new Column(_("#"));
   80     append_column(*manage(column));
   81 
   82     CellRendererText *const cell_matchcount = new CellRendererText();
   83     column->pack_start(*manage(cell_matchcount));
   84 
   85     column->add_attribute(cell_matchcount->property_text(), model_columns.matchcount);
   86     column->set_cell_data_func(*cell_matchcount, mem_fun(*this, &FileTree::text_cell_data_func));
   87 
   88     column->set_alignment(1.0);
   89     cell_matchcount->property_xalign() = 1.0;
   90 
   91     column->set_sort_column(model_columns.matchcount);
   92   }
   93 
   94   set_search_column(model_columns.filename);
   95 
   96   const Glib::RefPtr<TreeSelection> selection = get_selection();
   97 
   98   selection->set_select_function(&FileTree::select_func);
   99   selection->signal_changed().connect(mem_fun(*this, &FileTree::on_selection_changed));
  100 
  101   Settings::instance()->signal_changed().connect(mem_fun(*this, &FileTree::on_conf_value_changed));
  102 }
  103 
  104 FileTree::~FileTree()
  105 {}
  106 
  107 void FileTree::find_files(const std::string& dirname, const Glib::RefPtr<Glib::Regex>& pattern,
  108                           bool recursive, bool hidden)
  109 {
  110   FindData find_data (pattern, recursive, hidden);
  111 
  112   const bool modified_count_changed = (toplevel_.modified_count != 0);
  113 
  114   treestore_->clear();
  115 
  116   toplevel_.file_count     = 0;
  117   toplevel_.modified_count = 0;
  118   sum_matches_ = 0;
  119 
  120   signal_bound_state_changed(); // emit
  121   signal_match_count_changed(); // emit
  122 
  123   if (modified_count_changed)
  124     signal_modified_count_changed(); // emit
  125 
  126   find_recursively(dirname, find_data);
  127 
  128   // Work around a strange misbehavior: the tree is kept sorted while the
  129   // file search is in progress, which causes the scroll offset to change
  130   // slightly.  This in turn confuses TreeView::set_cursor() -- the first
  131   // call after the tree was completely filled just doesn't scroll.
  132   if (toplevel_.file_count > 0)
  133     scroll_to_row(Gtk::TreeModel::Path(1u, 0));
  134 
  135   signal_bound_state_changed(); // emit
  136 
  137   if (!find_data.error_list->empty())
  138     throw Error(find_data.error_list);
  139 }
  140 
  141 int FileTree::get_file_count() const
  142 {
  143   g_return_val_if_fail(toplevel_.file_count >= 0, 0);
  144   return toplevel_.file_count;
  145 }
  146 
  147 void FileTree::save_current_file()
  148 {
  149   if (const Gtk::TreeModel::iterator selected = get_selection()->get_selected())
  150   {
  151     Util::SharedPtr<MessageList> error_list (new MessageList());
  152 
  153     {
  154       Util::ScopedBlock block (conn_modified_changed_);
  155       save_file_at_iter(selected, error_list);
  156     }
  157 
  158     if (!error_list->empty())
  159       throw Error(error_list);
  160   }
  161 }
  162 
  163 void FileTree::save_all_files()
  164 {
  165   Util::SharedPtr<MessageList> error_list (new MessageList());
  166 
  167   {
  168     Util::ScopedBlock block (conn_modified_changed_);
  169 
  170     treestore_->foreach_iter(sigc::bind(
  171         sigc::mem_fun(*this, &FileTree::save_file_at_iter),
  172         sigc::ref(error_list)));
  173   }
  174 
  175   if (!error_list->empty())
  176     throw Error(error_list);
  177 }
  178 
  179 void FileTree::select_first_file()
  180 {
  181   if (sum_matches_ > 0)
  182     expand_and_select(path_match_first_);
  183 }
  184 
  185 bool FileTree::select_next_file(bool move_forward)
  186 {
  187   if (Gtk::TreeModel::iterator iter = get_selection()->get_selected())
  188   {
  189     Gtk::TreeModel::Path collapse;
  190 
  191     if ((move_forward) ? next_match_file(iter, &collapse) : prev_match_file(iter, &collapse))
  192     {
  193       if (!collapse.empty())
  194         collapse_row(collapse);
  195 
  196       expand_and_select(Gtk::TreeModel::Path(iter));
  197       return true;
  198     }
  199   }
  200 
  201   return false;
  202 }
  203 
  204 BoundState FileTree::get_bound_state()
  205 {
  206   BoundState bound = BOUND_FIRST | BOUND_LAST;
  207 
  208   if (sum_matches_ > 0)
  209   {
  210     if (const Gtk::TreeModel::iterator iter = get_selection()->get_selected())
  211     {
  212       Gtk::TreeModel::Path path (iter);
  213 
  214       if (path > path_match_first_)
  215         bound &= ~BOUND_FIRST;
  216 
  217       if (path < path_match_last_)
  218         bound &= ~BOUND_LAST;
  219     }
  220   }
  221 
  222   return bound;
  223 }
  224 
  225 void FileTree::find_matches(const Glib::RefPtr<Glib::Regex>& pattern, bool multiple)
  226 {
  227   {
  228     Util::ScopedBlock  block_conn (conn_match_count_);
  229     ScopedBlockSorting block_sort (*this);
  230     FindMatchesData    find_data  (pattern, multiple);
  231 
  232     treestore_->foreach(sigc::bind(
  233         sigc::mem_fun(*this, &FileTree::find_matches_at_path_iter),
  234         sigc::ref(find_data)));
  235   }
  236 
  237   signal_bound_state_changed(); // emit
  238 }
  239 
  240 long FileTree::get_match_count() const
  241 {
  242   g_return_val_if_fail(sum_matches_ >= 0, 0);
  243   return sum_matches_;
  244 }
  245 
  246 void FileTree::replace_all_matches(const Glib::ustring& substitution)
  247 {
  248   {
  249     Util::ScopedBlock block_match_count      (conn_match_count_);
  250     Util::ScopedBlock block_modified_changed (conn_modified_changed_);
  251     Util::ScopedBlock block_undo_stack_push  (conn_undo_stack_push_);
  252     ScopedBlockSorting block_sort   (*this);
  253     ReplaceMatchesData replace_data (*this, substitution);
  254 
  255     treestore_->foreach(sigc::bind(
  256         sigc::mem_fun(*this, &FileTree::replace_matches_at_path_iter),
  257         sigc::ref(replace_data)));
  258 
  259     // Adjust the boundary range if the operation has been interrupted.
  260     if (sum_matches_ > 0)
  261     {
  262       Gtk::TreeModel::iterator first = treestore_->get_iter(path_match_first_);
  263 
  264       if ((*first)[FileTreeColumns::instance().matchcount] == 0 && next_match_file(first))
  265         path_match_first_ = first;
  266     }
  267 
  268     signal_undo_stack_push(replace_data.undo_stack); // emit
  269   }
  270 
  271   signal_bound_state_changed(); // emit
  272 }
  273 
  274 int FileTree::get_modified_count() const
  275 {
  276   g_return_val_if_fail(toplevel_.modified_count >= 0, 0);
  277   return toplevel_.modified_count;
  278 }
  279 
  280 /**** Regexxer::FileTree -- protected **************************************/
  281 
  282 void FileTree::on_style_updated()
  283 {
  284   pixbuf_directory_   = render_icon_pixbuf(Gtk::Stock::DIRECTORY,     Gtk::ICON_SIZE_MENU);
  285   pixbuf_file_        = render_icon_pixbuf(Gtk::Stock::FILE,          Gtk::ICON_SIZE_MENU);
  286   pixbuf_load_failed_ = render_icon_pixbuf(Gtk::Stock::MISSING_IMAGE, Gtk::ICON_SIZE_MENU);
  287 
  288   Gdk::RGBA rgba = get_style_context()->get_color(Gtk::STATE_FLAG_INSENSITIVE);
  289   color_load_failed_.set_rgb_p(rgba.get_red(), rgba.get_green(), rgba.get_blue());
  290 
  291   Gtk::TreeView::on_style_updated();
  292 }
  293 
  294 /**** Regexxer::FileTree -- private ****************************************/
  295 
  296 void FileTree::icon_cell_data_func(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter)
  297 {
  298   Gtk::CellRendererPixbuf& renderer = dynamic_cast<Gtk::CellRendererPixbuf&>(*cell);
  299   const FileInfoBasePtr    infobase = (*iter)[FileTreeColumns::instance().fileinfo];
  300 
  301   if (const FileInfoPtr fileinfo = shared_dynamic_cast<FileInfo>(infobase))
  302   {
  303     renderer.property_pixbuf() = (fileinfo->load_failed) ? pixbuf_load_failed_ : pixbuf_file_;
  304   }
  305   else if (shared_dynamic_cast<DirInfo>(infobase))
  306   {
  307     renderer.property_pixbuf() = pixbuf_directory_;
  308   }
  309   else
  310   {
  311     renderer.property_pixbuf().reset_value();
  312   }
  313 }
  314 
  315 void FileTree::text_cell_data_func(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter)
  316 {
  317   Gtk::CellRendererText& renderer = dynamic_cast<Gtk::CellRendererText&>(*cell);
  318   const FileInfoBasePtr  infobase = (*iter)[FileTreeColumns::instance().fileinfo];
  319 
  320   const Gdk::Color* color = 0;
  321 
  322   if (const FileInfoPtr fileinfo = shared_dynamic_cast<FileInfo>(infobase))
  323   {
  324     if (fileinfo->load_failed)
  325       color = &color_load_failed_;
  326     else if (fileinfo->buffer && fileinfo->buffer->get_modified())
  327       color = &color_modified_;
  328   }
  329   else if (const DirInfoPtr dirinfo = shared_dynamic_cast<DirInfo>(infobase))
  330   {
  331     if (dirinfo->modified_count > 0)
  332       color = &color_modified_;
  333   }
  334 
  335   if (color)
  336     renderer.property_foreground_gdk() = *color;
  337   else
  338     renderer.property_foreground_gdk().reset_value();
  339 
  340   if (color == &color_modified_)
  341     renderer.property_style() = Pango::STYLE_OBLIQUE;
  342   else
  343     renderer.property_style().reset_value();
  344 }
  345 
  346 // static
  347 bool FileTree::select_func(const Glib::RefPtr<Gtk::TreeModel>& model,
  348                            const Gtk::TreeModel::Path& path, bool)
  349 {
  350   // Don't allow selection of directory nodes.
  351   return (get_fileinfo_from_iter(model->get_iter(path)) != 0);
  352 }
  353 
  354 void FileTree::find_recursively(const std::string& dirname, FindData& find_data)
  355 {
  356   using namespace Glib;
  357 
  358   try
  359   {
  360     int file_count = 0;
  361     Dir dir (dirname);
  362 
  363     for (Dir::iterator pos = dir.begin(); pos != dir.end(); ++pos)
  364     {
  365       if (signal_pulse()) // emit
  366         break;
  367 
  368       const std::string filename = *pos;
  369 
  370       if (!find_data.hidden && *filename.begin() == '.')
  371         continue;
  372 
  373       const std::string fullname = build_filename(dirname, filename);
  374 
  375       if (file_test(fullname, FILE_TEST_IS_SYMLINK))
  376         continue; // ignore symbolic links
  377 
  378       if (find_data.recursive && file_test(fullname, FILE_TEST_IS_DIR))
  379       {
  380         // Put the directory name on the stack instead of creating a new node
  381         // immediately.  The corresponding node will be created on demand if
  382         // there's actually a matching file in the directory or one of its
  383         // subdirectories.
  384         ScopedPushDir pushdir (find_data.dirstack, fullname);
  385         find_recursively(fullname, find_data); // recurse
  386       }
  387       else if (file_test(fullname, FILE_TEST_IS_REGULAR))
  388       {
  389         const ustring basename = Glib::filename_display_basename(fullname);
  390 
  391         if (find_data.pattern->match(basename))
  392         {
  393           find_add_file(basename, fullname, find_data);
  394           ++file_count;
  395         }
  396       }
  397     }
  398 
  399     find_increment_file_count(find_data, file_count);
  400   }
  401   catch (const FileError& error)
  402   {
  403     // Collect errors but don't interrupt the search.
  404     find_data.error_list->push_back(error.what());
  405   }
  406 }
  407 
  408 void FileTree::find_add_file(const Glib::ustring& basename, const std::string& fullname,
  409                              FindData& find_data)
  410 {
  411   // Build the collate key with a leading '1' so that directories always
  412   // come first (they have a leading '0').  This is simpler and faster
  413   // than explicitely checking for directories in the sort function.
  414   std::string collate_key (1, '1');
  415   collate_key += basename.collate_key();
  416 
  417   const FileInfoBasePtr fileinfo (new FileInfo(fullname));
  418 
  419   Gtk::TreeModel::Row row;
  420 
  421   if (find_data.dirstack.empty())
  422   {
  423     row = *treestore_->prepend(); // new toplevel node
  424   }
  425   else
  426   {
  427     if (!find_data.dirstack.back().second)
  428       find_fill_dirstack(find_data); // build all directory nodes in the stack
  429 
  430     row = *treestore_->prepend(find_data.dirstack.back().second->children());
  431   }
  432 
  433   const FileTreeColumns& columns = FileTreeColumns::instance();
  434 
  435   row[columns.filename]   = basename;
  436   row[columns.collatekey] = collate_key;
  437   row[columns.fileinfo]   = fileinfo;
  438 }
  439 
  440 void FileTree::find_fill_dirstack(FindData& find_data)
  441 {
  442   const FileTreeColumns& columns = FileTreeColumns::instance();
  443 
  444   const DirStack::iterator pend  = find_data.dirstack.end();
  445   DirStack::iterator       pprev = pend;
  446 
  447   for (DirStack::iterator pdir = find_data.dirstack.begin(); pdir != pend; pprev = pdir++)
  448   {
  449     if (pdir->second) // node already created
  450       continue;
  451 
  452     const Glib::ustring dirname = Glib::filename_display_basename(pdir->first);
  453 
  454     // Build the collate key with a leading '0' so that directories always
  455     // come first.  This is simpler and faster than explicitely checking for
  456     // directories in the sort function.
  457     std::string collate_key (1, '0');
  458     collate_key += dirname.collate_key();
  459 
  460     const FileInfoBasePtr dirinfo (new DirInfo());
  461 
  462     if (pprev == pend)
  463       pdir->second = treestore_->prepend(); // new toplevel node
  464     else
  465       pdir->second = treestore_->prepend(pprev->second->children());
  466 
  467     Gtk::TreeModel::Row row = *pdir->second;
  468 
  469     row[columns.filename]   = dirname;
  470     row[columns.collatekey] = collate_key;
  471     row[columns.fileinfo]   = dirinfo;
  472   }
  473 }
  474 
  475 void FileTree::find_increment_file_count(FindData& find_data, int file_count)
  476 {
  477   if (file_count <= 0)
  478     return;
  479 
  480   const FileTreeColumns& columns = FileTreeColumns::instance();
  481 
  482   const DirStack::iterator pbegin = find_data.dirstack.begin();
  483   DirStack::iterator       pdir   = find_data.dirstack.end();
  484 
  485   while (pdir != pbegin)
  486   {
  487     const FileInfoBasePtr base = (*(--pdir)->second)[columns.fileinfo];
  488     shared_polymorphic_cast<DirInfo>(base)->file_count += file_count;
  489   }
  490 
  491   toplevel_.file_count += file_count;
  492   signal_file_count_changed(); // emit
  493 }
  494 
  495 bool FileTree::save_file_at_iter(const Gtk::TreeModel::iterator& iter,
  496                                  const Util::SharedPtr<MessageList>& error_list)
  497 {
  498   const FileInfoPtr fileinfo = get_fileinfo_from_iter(iter);
  499 
  500   if (fileinfo && fileinfo->buffer && fileinfo->buffer->get_modified())
  501   {
  502     try
  503     {
  504       save_file(fileinfo);
  505     }
  506     catch (const Glib::Error& error)
  507     {
  508       error_list->push_back(Util::compose(_("Failed to save file \342\200\234%1\342\200\235: %2"),
  509                                           Glib::filename_display_basename(fileinfo->fullname),
  510                                           error.what()));
  511     }
  512 
  513     if (!fileinfo->buffer->get_modified())
  514       propagate_modified_change(iter, false);
  515 
  516     if (fileinfo != last_selected_ && fileinfo->buffer->is_freeable())
  517       Glib::RefPtr<FileBuffer>().swap(fileinfo->buffer); // reduce memory footprint
  518   }
  519 
  520   return false;
  521 }
  522 
  523 bool FileTree::find_matches_at_path_iter(const Gtk::TreeModel::Path& path,
  524                                          const Gtk::TreeModel::iterator& iter,
  525                                          FindMatchesData& find_data)
  526 {
  527   if (signal_pulse()) // emit
  528     return true;
  529 
  530   if (const FileInfoPtr fileinfo = get_fileinfo_from_iter(iter))
  531   {
  532     if (!fileinfo->buffer)
  533       load_file_with_fallback(iter, fileinfo);
  534 
  535     if (fileinfo->load_failed)
  536       return false; // continue
  537 
  538     const Glib::RefPtr<FileBuffer> buffer = fileinfo->buffer;
  539     g_assert(buffer);
  540 
  541     Util::ScopedConnection conn (buffer->signal_pulse.connect(signal_pulse.make_slot()));
  542 
  543     const int old_match_count = buffer->get_match_count();
  544 
  545     // Optimize the common case and construct the feedback slot only if there
  546     // are actually any handlers connected to the signal.  find_matches() can
  547     // then check whether the slot is empty to avoid providing arguments that
  548     // are never going to be used.
  549     const int new_match_count =
  550         buffer->find_matches(find_data.pattern, find_data.multiple, (signal_feedback.empty())
  551                              ? sigc::slot<void, int, const Glib::ustring&>()
  552                              : sigc::bind(signal_feedback.make_slot(), fileinfo));
  553 
  554     if (new_match_count > 0)
  555     {
  556       if (!find_data.path_match_first_set)
  557       {
  558         find_data.path_match_first_set = true;
  559         path_match_first_ = path;
  560       }
  561 
  562       path_match_last_ = path;
  563     }
  564 
  565     if (new_match_count != old_match_count)
  566       propagate_match_count_change(iter, new_match_count - old_match_count);
  567 
  568     if (fileinfo != last_selected_ && buffer->is_freeable())
  569       Glib::RefPtr<FileBuffer>().swap(fileinfo->buffer); // reduce memory footprint
  570   }
  571 
  572   return false;
  573 }
  574 
  575 bool FileTree::replace_matches_at_path_iter(const Gtk::TreeModel::Path& path,
  576                                             const Gtk::TreeModel::iterator& iter,
  577                                             ReplaceMatchesData& replace_data)
  578 {
  579   if (signal_pulse()) // emit
  580     return true;
  581 
  582   const FileInfoPtr fileinfo = get_fileinfo_from_iter(iter);
  583 
  584   if (fileinfo && fileinfo->buffer)
  585   {
  586     const Glib::RefPtr<FileBuffer> buffer = fileinfo->buffer;
  587 
  588     const int match_count = buffer->get_match_count();
  589 
  590     if (match_count > 0)
  591     {
  592       path_match_first_ = path;
  593 
  594       if (fileinfo != last_selected_)
  595         replace_data.row_reference.reset(new TreeRowRef(treestore_, path));
  596       else
  597         replace_data.row_reference = last_selected_rowref_;
  598 
  599       const bool was_modified = buffer->get_modified();
  600 
  601       {
  602         // Redirect the buffer's signal_undo_stack_push() in order to create
  603         // a single user action object for all replacements in all buffers.
  604         // Note that the caller must block conn_undo_stack_push_ to avoid
  605         // double notification.
  606         Util::ScopedConnection conn1 (buffer->signal_undo_stack_push
  607                                         .connect(replace_data.slot_undo_stack_push));
  608         Util::ScopedConnection conn2 (buffer->signal_pulse.connect(signal_pulse.make_slot()));
  609 
  610         buffer->replace_all_matches(replace_data.substitution);
  611       }
  612 
  613       const bool is_modified = buffer->get_modified();
  614 
  615       if (was_modified != is_modified)
  616         propagate_modified_change(iter, is_modified);
  617 
  618       propagate_match_count_change(iter, buffer->get_match_count() - match_count);
  619     }
  620   }
  621 
  622   return false;
  623 }
  624 
  625 void FileTree::expand_and_select(const Gtk::TreeModel::Path& path)
  626 {
  627   expand_to_path(path);
  628   set_cursor(path);
  629 }
  630 
  631 void FileTree::on_treestore_rows_reordered(const Gtk::TreeModel::Path& path,
  632                                            const Gtk::TreeModel::iterator& iter, int*)
  633 {
  634   if (sum_matches_ > 0)
  635   {
  636     const FileTreeColumns& columns = FileTreeColumns::instance();
  637     bool bounds_changed = false;
  638 
  639     if (path.is_ancestor(path_match_first_))
  640     {
  641       Gtk::TreeModel::iterator first = iter->children().begin();
  642 
  643       while (first->children() && (*first)[columns.matchcount] > 0)
  644         first = first->children().begin();
  645 
  646       if ((*first)[columns.matchcount] == 0)
  647       {
  648         const bool found_first = next_match_file(first);
  649         g_return_if_fail(found_first);
  650       }
  651 
  652       path_match_first_ = first;
  653       bounds_changed = true;
  654     }
  655 
  656     if (path.is_ancestor(path_match_last_))
  657     {
  658       Gtk::TreeModel::iterator last = iter->children()[iter->children().size() - 1];
  659 
  660       while (last->children() && (*last)[columns.matchcount] > 0)
  661         last = last->children()[last->children().size() - 1];
  662 
  663       if ((*last)[columns.matchcount] == 0)
  664       {
  665         const bool found_last = prev_match_file(last);
  666         g_return_if_fail(found_last);
  667       }
  668 
  669       path_match_last_ = last;
  670       bounds_changed = true;
  671     }
  672 
  673     if (bounds_changed)
  674       signal_bound_state_changed(); // emit
  675   }
  676 }
  677 
  678 void FileTree::on_selection_changed()
  679 {
  680   last_selected_rowref_.reset();
  681 
  682   conn_match_count_     .disconnect();
  683   conn_modified_changed_.disconnect();
  684   conn_undo_stack_push_ .disconnect();
  685 
  686   FileInfoPtr fileinfo;
  687   int file_index = 0;
  688 
  689   if (const Gtk::TreeModel::iterator iter = get_selection()->get_selected())
  690   {
  691     const FileInfoBasePtr base = (*iter)[FileTreeColumns::instance().fileinfo];
  692 
  693     fileinfo   = shared_polymorphic_cast<FileInfo>(base);
  694     file_index = calculate_file_index(iter) + 1;
  695 
  696     if (!fileinfo->buffer)
  697       load_file_with_fallback(iter, fileinfo);
  698 
  699     if (!fileinfo->load_failed)
  700     {
  701       conn_match_count_ = fileinfo->buffer->signal_match_count_changed.
  702           connect(sigc::mem_fun(*this, &FileTree::on_buffer_match_count_changed));
  703 
  704       conn_modified_changed_ = fileinfo->buffer->signal_modified_changed().
  705           connect(sigc::mem_fun(*this, &FileTree::on_buffer_modified_changed));
  706 
  707       conn_undo_stack_push_ = fileinfo->buffer->signal_undo_stack_push.
  708           connect(sigc::mem_fun(*this, &FileTree::on_buffer_undo_stack_push));
  709     }
  710 
  711     last_selected_rowref_.reset(new TreeRowRef(treestore_, Gtk::TreeModel::Path(iter)));
  712   }
  713 
  714   if (last_selected_ && last_selected_ != fileinfo &&
  715       last_selected_->buffer && last_selected_->buffer->is_freeable())
  716   {
  717     Glib::RefPtr<FileBuffer>().swap(last_selected_->buffer); // reduce memory footprint
  718   }
  719 
  720   last_selected_ = fileinfo;
  721 
  722   signal_switch_buffer(fileinfo, file_index); // emit
  723   signal_bound_state_changed(); // emit
  724 }
  725 
  726 void FileTree::on_buffer_match_count_changed()
  727 {
  728   const FileTreeColumns& columns = FileTreeColumns::instance();
  729 
  730   // There has to be a selection since we receive signal_match_count_changed()
  731   // from the currently selected FileBuffer.
  732   Gtk::TreeModel::iterator iter = get_selection()->get_selected();
  733   g_return_if_fail(iter);
  734 
  735   const FileInfoBasePtr base = (*iter)[columns.fileinfo];
  736   const FileInfoPtr fileinfo = shared_polymorphic_cast<FileInfo>(base);
  737 
  738   g_return_if_fail(fileinfo->buffer);
  739 
  740   const int match_count     = fileinfo->buffer->get_match_count();
  741   const int old_match_count = (*iter)[columns.matchcount];
  742 
  743   if (match_count == old_match_count)
  744     return; // spurious emission -- do nothing
  745 
  746   const long old_sum_matches = sum_matches_;
  747   propagate_match_count_change(iter, match_count - old_match_count);
  748 
  749   if (old_sum_matches == 0)
  750   {
  751     path_match_first_ = iter;
  752     path_match_last_  = path_match_first_;
  753   }
  754   else if ((sum_matches_ > 0) && (old_match_count == 0 || match_count == 0))
  755   {
  756     // OK, this nightmarish-looking code below is all about adjusting the
  757     // [path_match_first_,path_match_last_] range.  Adjustment is necessary
  758     // if either a) match_count was previously 0 and iter is not already
  759     // in the range, or b) match_count dropped to 0 and iter is a boundary
  760     // of the range.
  761     //
  762     // Preconditions we definitely know at this point:
  763     // 1) old_sum_matches > 0
  764     // 2) sum_matches != old_sum_matches && sum_matches > 0
  765     // 3) old_match_count == 0 || match_count == 0
  766     // 4) old_match_count != match_count
  767 
  768     g_assert(old_sum_matches > 0);
  769     g_assert(old_match_count != match_count);
  770 
  771     // The range should've been set up already since old_sum_matches > 0.
  772     g_return_if_fail(path_match_first_ <= path_match_last_);
  773 
  774     Gtk::TreeModel::Path path (iter);
  775 
  776     if (old_match_count == 0)
  777     {
  778       g_assert(match_count > 0);
  779       g_return_if_fail(path != path_match_first_ && path != path_match_last_);
  780 
  781       // Expand the range if necessary.
  782       if (path < path_match_first_)
  783         path_match_first_ = path;
  784       else if (path > path_match_last_)
  785         path_match_last_ = path;
  786     }
  787     else
  788     {
  789       g_assert(match_count == 0);
  790       g_return_if_fail(path >= path_match_first_ && path <= path_match_last_);
  791 
  792       if (path == path_match_first_)
  793       {
  794         // Find the new start boundary of the range.
  795         const bool found_next = next_match_file(iter);
  796         g_return_if_fail(found_next);
  797 
  798         path_match_first_ = iter;
  799       }
  800       else if (path == path_match_last_)
  801       {
  802         // Find the new end boundary of the range.
  803         const bool found_prev = prev_match_file(iter);
  804         g_return_if_fail(found_prev);
  805 
  806         path_match_last_ = iter;
  807       }
  808     }
  809 
  810     signal_bound_state_changed(); // emit
  811   }
  812 }
  813 
  814 void FileTree::on_buffer_modified_changed()
  815 {
  816   const Gtk::TreeModel::iterator selected = get_selection()->get_selected();
  817   g_return_if_fail(selected);
  818 
  819   const FileInfoBasePtr base = (*selected)[FileTreeColumns::instance().fileinfo];
  820   const FileInfoPtr fileinfo = shared_polymorphic_cast<FileInfo>(base);
  821 
  822   g_return_if_fail(fileinfo == last_selected_);
  823   g_return_if_fail(fileinfo->buffer);
  824 
  825   propagate_modified_change(selected, fileinfo->buffer->get_modified());
  826 }
  827 
  828 void FileTree::on_buffer_undo_stack_push(UndoActionPtr undo_action)
  829 {
  830   g_return_if_fail(last_selected_rowref_);
  831 
  832   const UndoActionPtr action_shell (new BufferActionShell(*this, last_selected_rowref_, undo_action));
  833 
  834   signal_undo_stack_push(action_shell); // emit
  835 }
  836 
  837 int FileTree::calculate_file_index(const Gtk::TreeModel::iterator& pos)
  838 {
  839   int index = 0;
  840 
  841   Gtk::TreeModel::iterator iter = pos->parent();
  842 
  843   if (iter) // calculate the parent's index first if there is one
  844     index = calculate_file_index(iter); // recurse
  845 
  846   const FileTreeColumns& columns = FileTreeColumns::instance();
  847 
  848   for (iter = iter->children().begin(); iter != pos; ++iter)
  849   {
  850     const FileInfoBasePtr base = (*iter)[columns.fileinfo];
  851     g_return_val_if_fail(base, index);
  852 
  853     if (const DirInfoPtr dirinfo = shared_dynamic_cast<DirInfo>(base))
  854       index += dirinfo->file_count; // count whole directory in one step
  855     else
  856       ++index; // single file
  857   }
  858 
  859   return index;
  860 }
  861 
  862 void FileTree::propagate_match_count_change(const Gtk::TreeModel::iterator& pos, int difference)
  863 {
  864   const FileTreeColumns& columns = FileTreeColumns::instance();
  865 
  866   for (Gtk::TreeModel::iterator iter = pos; iter; iter = iter->parent())
  867   {
  868     const int match_count = (*iter)[columns.matchcount];
  869     (*iter)[columns.matchcount] = match_count + difference;
  870   }
  871 
  872   sum_matches_ += difference;
  873 
  874   signal_match_count_changed(); // emit
  875 }
  876 
  877 void FileTree::propagate_modified_change(const Gtk::TreeModel::iterator& pos, bool modified)
  878 {
  879   const int difference = (modified) ? 1 : -1;
  880   const FileTreeColumns& columns = FileTreeColumns::instance();
  881 
  882   Gtk::TreeModel::Path path (pos);
  883   treestore_->row_changed(path, pos);
  884 
  885   for (Gtk::TreeModel::iterator iter = pos->parent(); iter && path.up(); iter = iter->parent())
  886   {
  887     const FileInfoBasePtr base = (*iter)[columns.fileinfo];
  888     const DirInfoPtr dirinfo = shared_polymorphic_cast<DirInfo>(base);
  889 
  890     dirinfo->modified_count += difference;
  891 
  892     // Update the view only if the count flipped from 0 to 1 or vice versa.
  893     if (dirinfo->modified_count == int(modified))
  894       treestore_->row_changed(path, iter);
  895   }
  896 
  897   toplevel_.modified_count += difference;
  898 
  899   signal_modified_count_changed(); // emit
  900 }
  901 
  902 void FileTree::load_file_with_fallback(const Gtk::TreeModel::iterator& iter,
  903                                        const FileInfoPtr& fileinfo)
  904 {
  905   g_return_if_fail(!fileinfo->buffer);
  906 
  907   const bool old_load_failed = fileinfo->load_failed;
  908 
  909   try
  910   {
  911     load_file(fileinfo, fallback_encoding_);
  912   }
  913   catch (const Glib::Error& error)
  914   {
  915     fileinfo->buffer = FileBuffer::create_with_error_message(
  916         render_icon_pixbuf(Gtk::Stock::DIALOG_ERROR, Gtk::ICON_SIZE_DIALOG), error.what());
  917   }
  918   catch (const ErrorBinaryFile&)
  919   {
  920     const Glib::ustring filename = (*iter)[FileTreeColumns::instance().filename];
  921 
  922     fileinfo->buffer = FileBuffer::create_with_error_message(
  923         render_icon_pixbuf(Gtk::Stock::DIALOG_ERROR, Gtk::ICON_SIZE_DIALOG),
  924         Util::compose(_("\342\200\234%1\342\200\235 seems to be a binary file."), filename));
  925   }
  926 
  927   if (old_load_failed != fileinfo->load_failed)
  928   {
  929     // Trigger signal_row_changed() because the value of fileinfo->load_failed
  930     // changed, which means we have to change icon and color of the row.
  931     treestore_->row_changed(Gtk::TreeModel::Path(iter), iter);
  932   }
  933 }
  934 
  935 void FileTree::on_conf_value_changed(const Glib::ustring& key)
  936 {
  937   if (key == conf_key_fallback_encoding)
  938     fallback_encoding_ = Settings::instance()->get_string(key);
  939 }
  940 
  941 } // namespace Regexxer