"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