"Fossies" - the Fresh Open Source Software Archive 
Member "regexxer-0.10/src/mainwindow.cc" (6 Oct 2011, 34605 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 "mainwindow.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 "mainwindow.h"
22 #include "filetree.h"
23 #include "globalstrings.h"
24 #include "prefdialog.h"
25 #include "statusline.h"
26 #include "stringutils.h"
27 #include "translation.h"
28 #include "settings.h"
29
30 #include <glib.h>
31 #include <gtkmm.h>
32 #include <gtksourceviewmm.h>
33 #include <algorithm>
34 #include <functional>
35 #include <iostream>
36
37 #include <config.h>
38
39 namespace
40 {
41
42 enum { BUSY_GUI_UPDATE_INTERVAL = 16 };
43
44 typedef Glib::RefPtr<Regexxer::FileBuffer> FileBufferPtr;
45
46 static const char *const selection_clipboard = "CLIPBOARD";
47
48 /*
49 * List of authors to be displayed in the about dialog.
50 */
51 static const char *const program_authors[] =
52 {
53 "Daniel Elstner <daniel.kitta@gmail.com>",
54 "Fabien Parent <parent.f@gmail.com>",
55 "Murray Cumming <murrayc@murrayc.com>",
56 0
57 };
58
59 static const char *const program_license =
60 "regexxer is free software; you can redistribute it and/or modify "
61 "it under the terms of the GNU General Public License as published by "
62 "the Free Software Foundation; either version 2 of the License, or "
63 "(at your option) any later version.\n"
64 "\n"
65 "regexxer is distributed in the hope that it will be useful, "
66 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
67 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
68 "GNU General Public License for more details.\n"
69 "\n"
70 "You should have received a copy of the GNU General Public License "
71 "along with regexxer; if not, write to the Free Software Foundation, "
72 "Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA\n";
73
74 class FileErrorDialog : public Gtk::MessageDialog
75 {
76 public:
77 FileErrorDialog(Gtk::Window& parent, const Glib::ustring& message,
78 Gtk::MessageType type, const Regexxer::FileTree::Error& error);
79 virtual ~FileErrorDialog();
80 };
81
82 FileErrorDialog::FileErrorDialog(Gtk::Window& parent, const Glib::ustring& message,
83 Gtk::MessageType type, const Regexxer::FileTree::Error& error)
84 :
85 Gtk::MessageDialog(parent, message, false, type, Gtk::BUTTONS_OK, true)
86 {
87 using namespace Gtk;
88
89 const Glib::RefPtr<TextBuffer> buffer = TextBuffer::create();
90 TextBuffer::iterator buffer_end = buffer->end();
91
92 typedef std::list<Glib::ustring> ErrorList;
93 const ErrorList& error_list = error.get_error_list();
94
95 for (ErrorList::const_iterator perr = error_list.begin(); perr != error_list.end(); ++perr)
96 buffer_end = buffer->insert(buffer_end, *perr + '\n');
97
98 Box& box = *get_vbox();
99 Frame *const frame = new Frame();
100 box.pack_start(*manage(frame), PACK_EXPAND_WIDGET);
101 frame->set_border_width(6); // HIG spacing
102 frame->set_shadow_type(SHADOW_IN);
103
104 ScrolledWindow *const scrollwin = new ScrolledWindow();
105 frame->add(*manage(scrollwin));
106 scrollwin->set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC);
107
108 TextView *const textview = new TextView(buffer);
109 scrollwin->add(*manage(textview));
110 textview->set_editable(false);
111 textview->set_cursor_visible(false);
112 textview->set_wrap_mode(WRAP_WORD);
113 textview->set_pixels_below_lines(8);
114
115 set_default_size(400, 250);
116 frame->show_all();
117 }
118
119 FileErrorDialog::~FileErrorDialog()
120 {}
121
122 static
123 void print_location(int linenumber, const Glib::ustring& subject, Regexxer::FileInfoPtr fileinfo)
124 {
125 std::cout << fileinfo->fullname << ':' << linenumber + 1 << ':';
126
127 std::string charset;
128
129 if (Glib::get_charset(charset))
130 std::cout << subject.raw(); // charset is UTF-8
131 else
132 std::cout << Glib::convert_with_fallback(subject.raw(), charset, "UTF-8");
133
134 std::cout << std::endl;
135 }
136
137 } // anonymous namespace
138
139 namespace Regexxer
140 {
141
142 /**** Regexxer::InitState **************************************************/
143
144 InitState::InitState()
145 :
146 folder (),
147 pattern (),
148 regex (),
149 substitution (),
150 no_recursive (false),
151 hidden (false),
152 no_global (false),
153 ignorecase (false),
154 feedback (false),
155 no_autorun (false)
156 {}
157
158 InitState::~InitState()
159 {}
160
161 /**** Regexxer::MainWindow::BusyAction *************************************/
162
163 class MainWindow::BusyAction
164 {
165 private:
166 MainWindow& object_;
167
168 BusyAction(const BusyAction&);
169 BusyAction& operator=(const BusyAction&);
170
171 public:
172 explicit BusyAction(MainWindow& object)
173 : object_ (object) { object_.busy_action_enter(); }
174
175 ~BusyAction() { object_.busy_action_leave(); }
176 };
177
178 /**** Regexxer::MainWindow *************************************************/
179
180 MainWindow::MainWindow()
181 :
182 toolbar_ (0),
183 table_file_ (0),
184 button_folder_ (0),
185 combo_entry_pattern_ (Gtk::manage(new Gtk::ComboBoxText(true))),
186 button_recursive_ (0),
187 button_hidden_ (0),
188 entry_regex_ (0),
189 entry_regex_completion_stack_(10, Settings::instance()->get_string_array(conf_key_regex_patterns)),
190 entry_regex_completion_ (Gtk::EntryCompletion::create()),
191 entry_substitution_ (0),
192 entry_substitution_completion_stack_(10, Settings::instance()->get_string_array(conf_key_substitution_patterns)),
193 entry_substitution_completion_ (Gtk::EntryCompletion::create()),
194 button_multiple_ (0),
195 button_caseless_ (0),
196 filetree_ (Gtk::manage(new FileTree())),
197 scrollwin_filetree_ (0),
198 scrollwin_textview_ (0),
199 textview_ (Gtk::manage(new Gsv::View())),
200 entry_preview_ (0),
201 statusline_ (Gtk::manage(new StatusLine())),
202 busy_action_running_ (false),
203 busy_action_cancel_ (false),
204 busy_action_iteration_ (0),
205 undo_stack_ (new UndoStack())
206 {
207 load_xml();
208
209 entry_regex_ = comboboxentry_regex_->get_entry();
210 entry_substitution_ = comboboxentry_substitution_->get_entry();
211
212 textview_->set_buffer(FileBuffer::create());
213 window_->set_title(PACKAGE_NAME);
214
215 vbox_main_->pack_start(*statusline_, Gtk::PACK_SHRINK);
216 scrollwin_filetree_->add(*filetree_);
217 table_file_->attach(*combo_entry_pattern_, 1, 2, 1, 2, Gtk::EXPAND | Gtk::FILL, Gtk::FILL);
218
219 scrollwin_textview_->add(*textview_);
220
221 statusline_->show_all();
222 filetree_->show_all();
223 combo_entry_pattern_->show_all();
224 scrollwin_textview_->show_all();
225
226 connect_signals();
227 }
228
229 MainWindow::~MainWindow()
230 {}
231
232 void MainWindow::initialize(const InitState& init)
233 {
234 Glib::RefPtr<Gio::Settings> settings = Settings::instance();
235 int width = settings->get_int(conf_key_window_width);
236 int height = settings->get_int(conf_key_window_height);
237
238 int x = settings->get_int(conf_key_window_position_x);
239 int y = settings->get_int(conf_key_window_position_y);
240
241 bool maximized = settings->get_boolean(conf_key_window_maximized);
242
243 window_->resize(width, height);
244 window_->move(x, y);
245 if (maximized)
246 window_->maximize();
247
248 textview_->set_show_line_numbers(settings->get_boolean(conf_key_show_line_numbers));
249 textview_->set_highlight_current_line(settings->get_boolean(conf_key_highlight_current_line));
250 textview_->set_auto_indent(settings->get_boolean(conf_key_auto_indentation));
251 textview_->set_draw_spaces(static_cast<Gsv::DrawSpacesFlags>
252 (settings->get_flags(conf_key_draw_spaces)));
253
254 std::string folder;
255
256 if (!init.folder.empty())
257 folder = init.folder.front();
258 if (!Glib::path_is_absolute(folder))
259 folder = Glib::build_filename(Glib::get_current_dir(), folder);
260
261 const bool folder_exists = button_folder_->set_current_folder(folder);
262
263 combo_entry_pattern_->get_entry()->set_text((init.pattern.empty()) ? Glib::ustring(1, '*') : init.pattern);
264
265 entry_regex_->set_text(init.regex);
266 entry_regex_->set_completion(entry_regex_completion_);
267 entry_substitution_->set_text(init.substitution);
268 entry_substitution_->set_completion(entry_substitution_completion_);
269
270 comboboxentry_regex_->set_model(entry_regex_completion_stack_.get_completion_model());
271 comboboxentry_regex_->set_entry_text_column(entry_regex_completion_stack_.get_completion_column());
272 comboboxentry_substitution_->set_model(entry_substitution_completion_stack_.get_completion_model());
273 comboboxentry_substitution_->set_entry_text_column(entry_substitution_completion_stack_.get_completion_column());
274
275 entry_regex_completion_->set_model(entry_regex_completion_stack_.get_completion_model());
276 entry_regex_completion_->set_text_column(entry_regex_completion_stack_.get_completion_column());
277 entry_regex_completion_->set_inline_completion(true);
278 entry_regex_completion_->set_popup_completion(false);
279
280 entry_substitution_completion_->set_model(entry_substitution_completion_stack_.get_completion_model());
281 entry_substitution_completion_->set_text_column(entry_substitution_completion_stack_.get_completion_column());
282 entry_substitution_completion_->set_inline_completion(true);
283 entry_substitution_completion_->set_popup_completion(false);
284
285 button_recursive_->set_active(!init.no_recursive);
286 button_hidden_ ->set_active(init.hidden);
287 button_multiple_ ->set_active(!init.no_global);
288 button_caseless_ ->set_active(init.ignorecase);
289
290 combo_entry_pattern_->set_entry_text_column(0);
291 const std::list<Glib::ustring> patterns =
292 settings->get_string_array(conf_key_files_patterns);
293 for (std::list<Glib::ustring>::const_iterator pattern = patterns.begin();
294 pattern != patterns.end();
295 ++pattern)
296 {
297 combo_entry_pattern_->append(*pattern);
298 }
299
300 if (init.feedback)
301 filetree_->signal_feedback.connect(&print_location);
302
303 Glib::RefPtr<Gtk::StyleContext> style_context =
304 toolbar_->get_style_context ();
305 if (style_context)
306 {
307 style_context->add_class (GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
308 }
309
310 // Strangely, folder_exists seems to be always true, probably because the
311 // file chooser works asynchronously but the GLib main loop isn't running
312 // yet. As a work-around, explicitely check whether the directory exists
313 // on the file system as well.
314 if (folder_exists && !init.no_autorun
315 && !init.folder.empty() && !init.pattern.empty()
316 && Glib::file_test(folder, Glib::FILE_TEST_IS_DIR))
317 {
318 Glib::signal_idle().connect(sigc::mem_fun(*this, &MainWindow::autorun_idle));
319 }
320 }
321
322 /**** Regexxer::MainWindow -- private **************************************/
323
324 void MainWindow::load_xml()
325 {
326 const Glib::RefPtr<Gtk::Builder> xml = Gtk::Builder::create_from_file(ui_mainwindow_filename);
327
328 Gtk::Window* mainwindow = 0;
329 xml->get_widget("mainwindow", mainwindow);
330 window_.reset(mainwindow);
331
332 xml->get_widget("toolbar", toolbar_);
333 xml->get_widget("button_folder", button_folder_);
334 xml->get_widget("button_recursive", button_recursive_);
335 xml->get_widget("button_hidden", button_hidden_);
336 xml->get_widget("comboboxentry_regex", comboboxentry_regex_);
337 xml->get_widget("comboboxentry_substitution", comboboxentry_substitution_);
338 xml->get_widget("button_multiple", button_multiple_);
339 xml->get_widget("button_caseless", button_caseless_);
340 xml->get_widget("scrollwin_textview", scrollwin_textview_);
341 xml->get_widget("entry_preview", entry_preview_);
342 xml->get_widget("vbox_main", vbox_main_);
343 xml->get_widget("scrollwin_filetree", scrollwin_filetree_);
344 xml->get_widget("table_file", table_file_);
345
346 controller_.load_xml(xml);
347 }
348
349 void MainWindow::connect_signals()
350 {
351 using sigc::bind;
352 using sigc::mem_fun;
353
354 window_->signal_hide ().connect(mem_fun(*this, &MainWindow::on_hide));
355 window_->signal_style_updated().connect(mem_fun(*this, &MainWindow::on_style_updated));
356 window_->signal_delete_event ().connect(mem_fun(*this, &MainWindow::on_delete_event));
357
358 combo_entry_pattern_->get_entry()->signal_activate().connect(controller_.find_files.slot());
359 combo_entry_pattern_->signal_changed ().connect(mem_fun(*this, &MainWindow::on_entry_pattern_changed));
360
361 entry_regex_ ->signal_activate().connect(controller_.find_matches.slot());
362 entry_substitution_->signal_activate().connect(controller_.find_matches.slot());
363 entry_substitution_->signal_changed ().connect(mem_fun(*this, &MainWindow::update_preview));
364
365 controller_.save_file .connect(mem_fun(*this, &MainWindow::on_save_file));
366 controller_.save_all .connect(mem_fun(*this, &MainWindow::on_save_all));
367 controller_.undo .connect(mem_fun(*this, &MainWindow::on_undo));
368 controller_.cut .connect(mem_fun(*this, &MainWindow::on_cut));
369 controller_.copy .connect(mem_fun(*this, &MainWindow::on_copy));
370 controller_.paste .connect(mem_fun(*this, &MainWindow::on_paste));
371 controller_.erase .connect(mem_fun(*this, &MainWindow::on_erase));
372 controller_.preferences .connect(mem_fun(*this, &MainWindow::on_preferences));
373 controller_.quit .connect(mem_fun(*this, &MainWindow::on_quit));
374 controller_.about .connect(mem_fun(*this, &MainWindow::on_about));
375 controller_.find_files .connect(mem_fun(*this, &MainWindow::on_find_files));
376 controller_.find_matches.connect(mem_fun(*this, &MainWindow::on_exec_search));
377 controller_.next_file .connect(bind(mem_fun(*this, &MainWindow::on_go_next_file), true));
378 controller_.prev_file .connect(bind(mem_fun(*this, &MainWindow::on_go_next_file), false));
379 controller_.next_match .connect(bind(mem_fun(*this, &MainWindow::on_go_next), true));
380 controller_.prev_match .connect(bind(mem_fun(*this, &MainWindow::on_go_next), false));
381 controller_.replace .connect(mem_fun(*this, &MainWindow::on_replace));
382 controller_.replace_file.connect(mem_fun(*this, &MainWindow::on_replace_file));
383 controller_.replace_all .connect(mem_fun(*this, &MainWindow::on_replace_all));
384
385 Settings::instance()->signal_changed().connect(mem_fun(*this, &MainWindow::on_conf_value_changed));
386
387 statusline_->signal_cancel_clicked.connect(
388 mem_fun(*this, &MainWindow::on_busy_action_cancel));
389
390 filetree_->signal_switch_buffer.connect(
391 mem_fun(*this, &MainWindow::on_filetree_switch_buffer));
392
393 filetree_->signal_bound_state_changed.connect(
394 mem_fun(*this, &MainWindow::on_bound_state_changed));
395
396 filetree_->signal_file_count_changed.connect(
397 mem_fun(*this, &MainWindow::on_filetree_file_count_changed));
398
399 filetree_->signal_match_count_changed.connect(
400 mem_fun(*this, &MainWindow::on_filetree_match_count_changed));
401
402 filetree_->signal_modified_count_changed.connect(
403 mem_fun(*this, &MainWindow::on_filetree_modified_count_changed));
404
405 filetree_->signal_pulse.connect(
406 mem_fun(*this, &MainWindow::on_busy_action_pulse));
407
408 filetree_->signal_undo_stack_push.connect(
409 mem_fun(*this, &MainWindow::on_undo_stack_push));
410 }
411
412 bool MainWindow::autorun_idle()
413 {
414 controller_.find_files.activate();
415
416 if (!busy_action_cancel_ && entry_regex_->get_text_length() > 0)
417 controller_.find_matches.activate();
418
419 return false;
420 }
421
422 void MainWindow::on_hide()
423 {
424 on_busy_action_cancel();
425
426 // Kill the dialogs if they're mapped right now. This isn't strictly
427 // necessary since they'd be deleted in the destructor anyway. But if we
428 // have to do a lot of cleanup the dialogs would stay open for that time,
429 // which doesn't look neat.
430 {
431 // Play safe and transfer ownership, and let the dtor do the delete.
432 const std::auto_ptr<Gtk::Dialog> temp (about_dialog_);
433 }
434 {
435 const std::auto_ptr<PrefDialog> temp (pref_dialog_);
436 }
437 }
438
439 void MainWindow::on_style_updated()
440 {
441 FileBuffer::pango_context_changed(window_->get_pango_context());
442 }
443
444 bool MainWindow::on_delete_event(GdkEventAny*)
445 {
446 bool quit = confirm_quit_request();
447 save_window_state();
448 return !quit;
449 }
450
451 void MainWindow::on_cut()
452 {
453 if (textview_->is_focus())
454 {
455 if (const Glib::RefPtr<Gtk::TextBuffer> buffer = textview_->get_buffer())
456 buffer->cut_clipboard(textview_->get_clipboard(selection_clipboard),
457 textview_->get_editable());
458 }
459 else
460 {
461 const int noEntries = 3;
462 Gtk::Entry *entries[noEntries] = { combo_entry_pattern_->get_entry(), entry_regex_,
463 entry_substitution_ };
464 for (int i = 0; i < 3; i++)
465 {
466 if (entries[i]->is_focus())
467 {
468 ((Gtk::Editable *)entries[i])->cut_clipboard();
469 return ;
470 }
471 }
472 }
473 }
474
475 void MainWindow::on_copy()
476 {
477 if (textview_->is_focus())
478 {
479 if (const Glib::RefPtr<Gtk::TextBuffer> buffer = textview_->get_buffer())
480 buffer->copy_clipboard(textview_->get_clipboard(selection_clipboard));
481 }
482 else
483 {
484 const int noEntries = 3;
485 Gtk::Entry *entries[noEntries] = { combo_entry_pattern_->get_entry(), entry_regex_,
486 entry_substitution_ };
487 for (int i = 0; i < 3; i++)
488 {
489 if (entries[i]->is_focus())
490 {
491 ((Gtk::Editable *)entries[i])->copy_clipboard();
492 return ;
493 }
494 }
495 }
496 }
497
498 void MainWindow::on_paste()
499 {
500 if (textview_->is_focus())
501 {
502 if (const Glib::RefPtr<Gtk::TextBuffer> buffer = textview_->get_buffer())
503 buffer->paste_clipboard(textview_->get_clipboard(selection_clipboard),
504 textview_->get_editable());
505 }
506 else
507 {
508 const int noEntries = 3;
509 Gtk::Entry *entries[noEntries] = { combo_entry_pattern_->get_entry(), entry_regex_,
510 entry_substitution_ };
511 for (int i = 0; i < 3; i++)
512 {
513 if (entries[i]->is_focus())
514 {
515 ((Gtk::Editable *)entries[i])->paste_clipboard();
516 return ;
517 }
518 }
519 }
520 }
521
522 void MainWindow::on_erase()
523 {
524 if (const Glib::RefPtr<Gtk::TextBuffer> buffer = textview_->get_buffer())
525 buffer->erase_selection(true, textview_->get_editable());
526 }
527
528 void MainWindow::on_quit()
529 {
530 if (confirm_quit_request())
531 {
532 save_window_state();
533 window_->hide();
534 }
535 }
536
537 void MainWindow::save_window_state()
538 {
539 int x = 0;
540 int y = 0;
541
542 int width = 0;
543 int height = 0;
544
545 window_->get_position(x, y);
546 window_->get_size(width, height);
547 bool maximized = (window_->get_window()->get_state() & Gdk::WINDOW_STATE_MAXIMIZED);
548
549 Glib::RefPtr<Gio::Settings> settings = Settings::instance();
550
551 settings->set_int(conf_key_window_position_x, x);
552 settings->set_int(conf_key_window_position_y, y);
553 settings->set_boolean(conf_key_window_maximized, maximized);
554
555 if (!maximized)
556 {
557 settings->set_int(conf_key_window_width, width);
558 settings->set_int(conf_key_window_height, height);
559 }
560 }
561
562 bool MainWindow::confirm_quit_request()
563 {
564 if (filetree_->get_modified_count() == 0)
565 return true;
566
567 Gtk::MessageDialog dialog (*window_,
568 _("Some files haven\342\200\231t been saved yet.\nQuit anyway?"),
569 false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, true);
570
571 dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
572 dialog.add_button(Gtk::Stock::QUIT, Gtk::RESPONSE_OK);
573
574 return (dialog.run() == Gtk::RESPONSE_OK);
575 }
576
577 void MainWindow::on_find_files()
578 {
579 if (filetree_->get_modified_count() > 0)
580 {
581 Gtk::MessageDialog dialog (*window_,
582 _("Some files haven\342\200\231t been saved yet.\nContinue anyway?"),
583 false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK_CANCEL, true);
584
585 if (dialog.run() != Gtk::RESPONSE_OK)
586 return;
587 }
588
589 std::string folder = button_folder_->get_filename();
590
591 if (folder.empty())
592 folder = Glib::get_current_dir();
593
594 g_return_if_fail(Glib::path_is_absolute(folder));
595
596 undo_stack_clear();
597
598 BusyAction busy (*this);
599
600 try
601 {
602 Glib::RefPtr<Glib::Regex> pattern = Glib::Regex::create(
603 Util::shell_pattern_to_regex(combo_entry_pattern_->get_entry()->get_text()),
604 Glib::REGEX_DOTALL);
605
606 filetree_->find_files(folder, pattern,
607 button_recursive_->get_active(),
608 button_hidden_->get_active());
609 }
610 catch (const Glib::RegexError&)
611 {
612 Gtk::MessageDialog dialog (*window_, _("The file search pattern is invalid."),
613 false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
614 dialog.run();
615 }
616 catch (const FileTree::Error& error)
617 {
618 FileErrorDialog dialog (*window_, _("The following errors occurred during search:"),
619 Gtk::MESSAGE_WARNING, error);
620 dialog.run();
621 }
622
623 statusline_->set_file_count(filetree_->get_file_count());
624 }
625
626 void MainWindow::on_exec_search()
627 {
628 BusyAction busy (*this);
629
630 const Glib::ustring regex = entry_regex_->get_text();
631 const bool caseless = button_caseless_->get_active();
632 const bool multiple = button_multiple_->get_active();
633
634 entry_regex_completion_stack_.push(regex);
635
636 Settings::instance()->set_string_array(conf_key_regex_patterns, entry_regex_completion_stack_.get_stack());
637
638 try
639 {
640 Glib::RefPtr<Glib::Regex> pattern =
641 Glib::Regex::create(regex, (caseless) ? Glib::REGEX_CASELESS
642 : static_cast<Glib::RegexCompileFlags>(0));
643
644 filetree_->find_matches(pattern, multiple);
645 }
646 catch (const Glib::RegexError& error)
647 {
648 Gtk::MessageDialog dialog (*window_, error.what(), false,
649 Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
650 dialog.run();
651
652 return;
653 }
654
655 if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
656 {
657 statusline_->set_match_count(buffer->get_original_match_count());
658 statusline_->set_match_index(buffer->get_match_index());
659 }
660
661 if (filetree_->get_match_count() > 0)
662 {
663 // Scrolling has to be post-poned after the redraw, otherwise we might
664 // not end up where we want to. So do that by installing an idle handler.
665
666 Glib::signal_idle().connect(
667 sigc::mem_fun(*this, &MainWindow::after_exec_search),
668 Glib::PRIORITY_HIGH_IDLE + 25); // slightly less than redraw (+20)
669 }
670 }
671
672 bool MainWindow::after_exec_search()
673 {
674 filetree_->select_first_file();
675 on_go_next(true);
676
677 return false;
678 }
679
680 void MainWindow::on_filetree_switch_buffer(FileInfoPtr fileinfo, int file_index)
681 {
682 const FileBufferPtr old_buffer = FileBufferPtr::cast_static(textview_->get_buffer());
683
684 if (fileinfo && fileinfo->buffer == old_buffer)
685 return;
686
687 if (old_buffer)
688 {
689 std::for_each(buffer_connections_.begin(), buffer_connections_.end(),
690 std::mem_fun_ref(&sigc::connection::disconnect));
691
692 buffer_connections_.clear();
693 old_buffer->forget_current_match();
694 }
695
696 if (fileinfo)
697 {
698 const FileBufferPtr buffer = fileinfo->buffer;
699 g_return_if_fail(buffer);
700
701 textview_->set_buffer(buffer);
702 textview_->set_editable(!fileinfo->load_failed);
703 textview_->set_cursor_visible(!fileinfo->load_failed);
704
705 if (!fileinfo->load_failed)
706 {
707 buffer_connections_.push_back(buffer->signal_modified_changed().
708 connect(sigc::mem_fun(*this, &MainWindow::on_buffer_modified_changed)));
709
710 buffer_connections_.push_back(buffer->signal_bound_state_changed.
711 connect(sigc::mem_fun(*this, &MainWindow::on_bound_state_changed)));
712
713 buffer_connections_.push_back(buffer->signal_preview_line_changed.
714 connect(sigc::mem_fun(*this, &MainWindow::update_preview)));
715 }
716
717 set_title_filename(fileinfo->fullname);
718
719 controller_.replace_file.set_enabled(buffer->get_match_count() > 0);
720 controller_.save_file.set_enabled(buffer->get_modified());
721 controller_.edit_actions.set_enabled(!fileinfo->load_failed);
722
723 statusline_->set_match_count(buffer->get_original_match_count());
724 statusline_->set_match_index(buffer->get_match_index());
725 statusline_->set_file_encoding(fileinfo->encoding);
726 }
727 else
728 {
729 textview_->set_buffer(FileBuffer::create());
730 textview_->set_editable(false);
731 textview_->set_cursor_visible(false);
732
733 window_->set_title(PACKAGE_NAME);
734
735 controller_.replace_file.set_enabled(false);
736 controller_.save_file.set_enabled(false);
737 controller_.edit_actions.set_enabled(false);
738
739 statusline_->set_match_count(0);
740 statusline_->set_match_index(0);
741 statusline_->set_file_encoding("");
742 }
743
744 statusline_->set_file_index(file_index);
745 update_preview();
746 }
747
748 void MainWindow::on_bound_state_changed()
749 {
750 BoundState bound = filetree_->get_bound_state();
751
752 controller_.prev_file.set_enabled((bound & BOUND_FIRST) == 0);
753 controller_.next_file.set_enabled((bound & BOUND_LAST) == 0);
754
755 if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
756 bound &= buffer->get_bound_state();
757
758 controller_.prev_match.set_enabled((bound & BOUND_FIRST) == 0);
759 controller_.next_match.set_enabled((bound & BOUND_LAST) == 0);
760 }
761
762 void MainWindow::on_filetree_file_count_changed()
763 {
764 const int file_count = filetree_->get_file_count();
765
766 statusline_->set_file_count(file_count);
767 controller_.find_matches.set_enabled(file_count > 0);
768 }
769
770 void MainWindow::on_filetree_match_count_changed()
771 {
772 controller_.replace_all.set_enabled(filetree_->get_match_count() > 0);
773
774 if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
775 controller_.replace_file.set_enabled(buffer->get_match_count() > 0);
776 }
777
778 void MainWindow::on_filetree_modified_count_changed()
779 {
780 controller_.save_all.set_enabled(filetree_->get_modified_count() > 0);
781 }
782
783 void MainWindow::on_buffer_modified_changed()
784 {
785 controller_.save_file.set_enabled(textview_->get_buffer()->get_modified());
786 }
787
788 void MainWindow::on_go_next_file(bool move_forward)
789 {
790 filetree_->select_next_file(move_forward);
791 on_go_next(move_forward);
792 }
793
794 bool MainWindow::do_scroll(const Glib::RefPtr<Gtk::TextMark> mark)
795 {
796 if (!mark)
797 return false;
798
799 textview_->scroll_to(mark, 0.125);
800 return false;
801 }
802
803 void MainWindow::on_go_next(bool move_forward)
804 {
805 if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
806 {
807 if (const Glib::RefPtr<Gtk::TextMark> mark = buffer->get_next_match(move_forward))
808 {
809 Glib::signal_idle ().connect (sigc::bind<const Glib::RefPtr<Gtk::TextMark> >
810 (sigc::mem_fun (*this, &MainWindow::do_scroll), mark));
811 statusline_->set_match_index(buffer->get_match_index());
812 return;
813 }
814 }
815
816 if (filetree_->select_next_file(move_forward))
817 {
818 on_go_next(move_forward); // recursive call
819 }
820 }
821
822 void MainWindow::on_replace()
823 {
824 if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
825 {
826 const Glib::ustring substitution = entry_substitution_->get_text();
827 entry_substitution_completion_stack_.push(substitution);
828 Settings::instance()->set_string_array(conf_key_substitution_patterns, entry_substitution_completion_stack_.get_stack());
829 buffer->replace_current_match(substitution);
830 on_go_next(true);
831 }
832 }
833
834 void MainWindow::on_replace_file()
835 {
836 if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
837 {
838 const Glib::ustring substitution = entry_substitution_->get_text();
839 entry_substitution_completion_stack_.push(substitution);
840 Settings::instance()->set_string_array(conf_key_substitution_patterns, entry_substitution_completion_stack_.get_stack());
841 buffer->replace_all_matches(substitution);
842 statusline_->set_match_index(0);
843 }
844 }
845
846 void MainWindow::on_replace_all()
847 {
848 BusyAction busy (*this);
849
850 const Glib::ustring substitution = entry_substitution_->get_text();
851 entry_substitution_completion_stack_.push(substitution);
852 Settings::instance()->set_string_array(conf_key_substitution_patterns, entry_substitution_completion_stack_.get_stack());
853 filetree_->replace_all_matches(substitution);
854 statusline_->set_match_index(0);
855 }
856
857 void MainWindow::on_save_file()
858 {
859 try
860 {
861 filetree_->save_current_file();
862 }
863 catch (const FileTree::Error& error)
864 {
865 const std::list<Glib::ustring>& error_list = error.get_error_list();
866 g_assert(error_list.size() == 1);
867
868 Gtk::MessageDialog dialog (*window_, error_list.front(), false,
869 Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
870 dialog.run();
871 }
872 }
873
874 void MainWindow::on_save_all()
875 {
876 try
877 {
878 filetree_->save_all_files();
879 }
880 catch (const FileTree::Error& error)
881 {
882 FileErrorDialog dialog (*window_, _("The following errors occurred during save:"),
883 Gtk::MESSAGE_ERROR, error);
884 dialog.run();
885 }
886 }
887
888 void MainWindow::on_undo_stack_push(UndoActionPtr action)
889 {
890 undo_stack_->push(action);
891 controller_.undo.set_enabled(true);
892 }
893
894 void MainWindow::on_undo()
895 {
896 if (textview_->is_focus())
897 {
898 BusyAction busy (*this);
899 undo_stack_->undo_step(sigc::mem_fun(*this, &MainWindow::on_busy_action_pulse));
900 controller_.undo.set_enabled(!undo_stack_->empty());
901 }
902 }
903
904 void MainWindow::undo_stack_clear()
905 {
906 controller_.undo.set_enabled(false);
907 undo_stack_.reset(new UndoStack());
908 }
909
910 void MainWindow::on_entry_pattern_changed()
911 {
912 controller_.find_files.set_enabled(combo_entry_pattern_->get_entry()->get_text_length() > 0);
913 }
914
915 void MainWindow::update_preview()
916 {
917 if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
918 {
919 Glib::ustring preview;
920 const int pos = buffer->get_line_preview(entry_substitution_->get_text(), preview);
921
922 entry_preview_->set_text(preview);
923 controller_.replace.set_enabled(pos >= 0);
924
925 // Beware, strange code ahead!
926 //
927 // The goal is to scroll the preview entry so that it shows the entire
928 // replaced text if possible. In order to do that we first move the cursor
929 // to 0, forcing scrolling to the left boundary. Then we set the cursor to
930 // the end of the replaced text, thus forcing the entry widget to scroll
931 // again. The replacement should then be entirely visible provided that it
932 // fits into the entry.
933 //
934 // The problem is that Gtk::Entry doesn't update its scroll position
935 // immediately but in an idle handler, thus any calls to set_position()
936 // but the last one have no effect at all.
937 //
938 // To workaround that, we install an idle handler that's executed just
939 // after the entry updated its scroll position, but before redrawing is
940 // done.
941
942 entry_preview_->set_position(0);
943
944 if (pos > 0)
945 {
946 using namespace sigc;
947
948 Glib::signal_idle().connect(
949 bind_return(bind(mem_fun(*entry_preview_, &Gtk::Editable::set_position), pos), false),
950 Glib::PRIORITY_HIGH_IDLE + 17); // between scroll update (+ 15) and redraw (+ 20)
951 }
952 }
953 }
954
955 void MainWindow::set_title_filename(const std::string& filename)
956 {
957 Glib::ustring title = Glib::filename_display_basename(filename);
958
959 title += " (";
960 title += Util::filename_short_display_name(Glib::path_get_dirname(filename));
961 title += ") \342\200\223 " PACKAGE_NAME; // U+2013 EN DASH
962
963 window_->set_title(title);
964 }
965
966 void MainWindow::busy_action_enter()
967 {
968 g_return_if_fail(!busy_action_running_);
969
970 controller_.match_actions.set_enabled(false);
971
972 statusline_->pulse_start();
973
974 busy_action_running_ = true;
975 busy_action_cancel_ = false;
976 busy_action_iteration_ = 0;
977 }
978
979 void MainWindow::busy_action_leave()
980 {
981 g_return_if_fail(busy_action_running_);
982
983 busy_action_running_ = false;
984
985 statusline_->pulse_stop();
986
987 controller_.match_actions.set_enabled(true);
988 }
989
990 bool MainWindow::on_busy_action_pulse()
991 {
992 g_return_val_if_fail(busy_action_running_, true);
993
994 if (!busy_action_cancel_ && (++busy_action_iteration_ % BUSY_GUI_UPDATE_INTERVAL) == 0)
995 {
996 statusline_->pulse();
997
998 const Glib::RefPtr<Glib::MainContext> context = Glib::MainContext::get_default();
999
1000 do {}
1001 while (context->iteration(false) && !busy_action_cancel_);
1002 }
1003
1004 return busy_action_cancel_;
1005 }
1006
1007 void MainWindow::on_busy_action_cancel()
1008 {
1009 if (busy_action_running_)
1010 busy_action_cancel_ = true;
1011 }
1012
1013 void MainWindow::on_about()
1014 {
1015 if (about_dialog_.get())
1016 {
1017 about_dialog_->present();
1018 }
1019 else
1020 {
1021 std::auto_ptr<Gtk::AboutDialog> dialog (new Gtk::AboutDialog());
1022 std::vector<Glib::ustring> authors;
1023 for (int i = 0; program_authors[i]; i++)
1024 authors.push_back(program_authors[i]);
1025
1026 dialog->set_version(PACKAGE_VERSION);
1027 dialog->set_logo_icon_name(PACKAGE_TARNAME);
1028 dialog->set_comments(_("Search and replace using regular expressions"));
1029 dialog->set_copyright("Copyright \302\251 2002-2007 Daniel Elstner\n"
1030 "Copyright \302\251 2009-2011 Fabien Parent");
1031 dialog->set_website("http://regexxer.sourceforge.net/");
1032
1033 dialog->set_authors(authors);
1034 dialog->set_translator_credits(_("translator-credits"));
1035 dialog->set_license(program_license);
1036 dialog->set_wrap_license(true);
1037
1038 dialog->set_transient_for(*window_);
1039 dialog->show();
1040 dialog->signal_response().connect(sigc::mem_fun(*this, &MainWindow::on_about_dialog_response));
1041
1042 about_dialog_ = dialog;
1043 }
1044 }
1045
1046 void MainWindow::on_about_dialog_response(int)
1047 {
1048 // Play safe and transfer ownership, and let the dtor do the delete.
1049 const std::auto_ptr<Gtk::Dialog> temp (about_dialog_);
1050 }
1051
1052 void MainWindow::on_preferences()
1053 {
1054 if (pref_dialog_.get())
1055 {
1056 pref_dialog_->get_dialog()->present();
1057 }
1058 else
1059 {
1060 std::auto_ptr<PrefDialog> dialog (new PrefDialog(*window_));
1061
1062 dialog->get_dialog()->signal_hide()
1063 .connect(sigc::mem_fun(*this, &MainWindow::on_pref_dialog_hide));
1064 dialog->get_dialog()->show();
1065
1066 pref_dialog_ = dialog;
1067 }
1068 }
1069
1070 void MainWindow::on_pref_dialog_hide()
1071 {
1072 // Play safe and transfer ownership, and let the dtor do the delete.
1073 const std::auto_ptr<PrefDialog> temp (pref_dialog_);
1074 }
1075
1076 void MainWindow::on_conf_value_changed(const Glib::ustring& key)
1077 {
1078 if (key == conf_key_textview_font)
1079 {
1080 std::string style = "GtkTextView { font: ";
1081 style += Settings::instance()->get_string(key);
1082 style += "}";
1083 Glib::RefPtr<Gtk::CssProvider> css = Gtk::CssProvider::create();
1084 css->load_from_data(style);
1085
1086 textview_ ->get_style_context()->add_provider(css, 1);
1087 entry_preview_->get_style_context()->add_provider(css, 1);
1088 }
1089 }
1090
1091 } // namespace Regexxer