"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