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
FileTree()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
~FileTree()104 FileTree::~FileTree()
105 {}
106
find_files(const std::string & dirname,const Glib::RefPtr<Glib::Regex> & pattern,bool recursive,bool hidden)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
get_file_count() const141 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
save_current_file()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
save_all_files()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
select_first_file()179 void FileTree::select_first_file()
180 {
181 if (sum_matches_ > 0)
182 expand_and_select(path_match_first_);
183 }
184
select_next_file(bool move_forward)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
get_bound_state()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
find_matches(const Glib::RefPtr<Glib::Regex> & pattern,bool multiple)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
get_match_count() const240 long FileTree::get_match_count() const
241 {
242 g_return_val_if_fail(sum_matches_ >= 0, 0);
243 return sum_matches_;
244 }
245
replace_all_matches(const Glib::ustring & substitution)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
get_modified_count() const274 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
on_style_updated()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
icon_cell_data_func(Gtk::CellRenderer * cell,const Gtk::TreeModel::iterator & iter)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
text_cell_data_func(Gtk::CellRenderer * cell,const Gtk::TreeModel::iterator & iter)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
select_func(const Glib::RefPtr<Gtk::TreeModel> & model,const Gtk::TreeModel::Path & path,bool)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
find_recursively(const std::string & dirname,FindData & find_data)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
find_add_file(const Glib::ustring & basename,const std::string & fullname,FindData & find_data)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
find_fill_dirstack(FindData & find_data)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
find_increment_file_count(FindData & find_data,int file_count)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
save_file_at_iter(const Gtk::TreeModel::iterator & iter,const Util::SharedPtr<MessageList> & error_list)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
find_matches_at_path_iter(const Gtk::TreeModel::Path & path,const Gtk::TreeModel::iterator & iter,FindMatchesData & find_data)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
replace_matches_at_path_iter(const Gtk::TreeModel::Path & path,const Gtk::TreeModel::iterator & iter,ReplaceMatchesData & replace_data)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
expand_and_select(const Gtk::TreeModel::Path & path)625 void FileTree::expand_and_select(const Gtk::TreeModel::Path& path)
626 {
627 expand_to_path(path);
628 set_cursor(path);
629 }
630
on_treestore_rows_reordered(const Gtk::TreeModel::Path & path,const Gtk::TreeModel::iterator & iter,int *)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
on_selection_changed()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
on_buffer_match_count_changed()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
on_buffer_modified_changed()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
on_buffer_undo_stack_push(UndoActionPtr undo_action)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
calculate_file_index(const Gtk::TreeModel::iterator & pos)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
propagate_match_count_change(const Gtk::TreeModel::iterator & pos,int difference)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
propagate_modified_change(const Gtk::TreeModel::iterator & pos,bool modified)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
load_file_with_fallback(const Gtk::TreeModel::iterator & iter,const FileInfoPtr & fileinfo)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
on_conf_value_changed(const Glib::ustring & key)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
942