mainwindow.cc 34.7 KB
Newer Older
1 2
/*
 * Copyright (c) 2002-2007  Daniel Elstner  <daniel.kitta@gmail.com>
3
 *
4
 * This file is part of regexxer.
5
 *
6 7 8 9
 * regexxer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
10
 *
11
 * regexxer is distributed in the hope that it will be useful,
12 13 14 15 16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
17 18
 * along with regexxer; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
 */
daniel_e's avatar
daniel_e committed
20 21

#include "mainwindow.h"
22
#include "filetree.h"
23
#include "globalstrings.h"
24
#include "prefdialog.h"
25
#include "statusline.h"
daniel_e's avatar
daniel_e committed
26
#include "stringutils.h"
27
#include "translation.h"
28
#include "settings.h"
daniel_e's avatar
daniel_e committed
29

30
#include <glib.h>
daniel_e's avatar
daniel_e committed
31
#include <gtkmm.h>
32
#include <gtksourceviewmm.h>
33
#include <algorithm>
34
#include <functional>
35
#include <iostream>
daniel_e's avatar
daniel_e committed
36 37 38 39 40 41

#include <config.h>

namespace
{

42
enum { BUSY_GUI_UPDATE_INTERVAL = 16 };
43

daniel_e's avatar
daniel_e committed
44 45
typedef Glib::RefPtr<Regexxer::FileBuffer> FileBufferPtr;

46
static const char *const selection_clipboard = "CLIPBOARD";
47

48 49 50
/*
 * List of authors to be displayed in the about dialog.
 */
51
static const char *const program_authors[] =
52 53
{
  "Daniel Elstner <daniel.kitta@gmail.com>",
Fabien Parent's avatar
Fabien Parent committed
54
  "Fabien Parent <parent.f@gmail.com>",
55 56 57
  "Murray Cumming <murrayc@murrayc.com>",
  0
};
daniel_e's avatar
daniel_e committed
58

59
static const char *const program_license =
60 61 62 63 64 65 66 67 68 69 70 71
  "regexxer is free software; you can redistribute it and/or modify "
  "it under the terms of the GNU General Public License as published by "
  "the Free Software Foundation; either version 2 of the License, or "
  "(at your option) any later version.\n"
  "\n"
  "regexxer is distributed in the hope that it will be useful, "
  "but WITHOUT ANY WARRANTY; without even the implied warranty of "
  "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
  "GNU General Public License for more details.\n"
  "\n"
  "You should have received a copy of the GNU General Public License "
  "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 88 89 90 91
{
  using namespace Gtk;

  const Glib::RefPtr<TextBuffer> buffer = TextBuffer::create();
  TextBuffer::iterator buffer_end = buffer->end();

92
  typedef std::list<Glib::ustring> ErrorList;
93 94
  const ErrorList& error_list = error.get_error_list();

95 96
  for (ErrorList::const_iterator perr = error_list.begin(); perr != error_list.end(); ++perr)
    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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
  frame->set_shadow_type(SHADOW_IN);

  ScrolledWindow *const scrollwin = new ScrolledWindow();
  frame->add(*manage(scrollwin));
  scrollwin->set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC);

  TextView *const textview = new TextView(buffer);
  scrollwin->add(*manage(textview));
  textview->set_editable(false);
  textview->set_cursor_visible(false);
  textview->set_wrap_mode(WRAP_WORD);
  textview->set_pixels_below_lines(8);

  set_default_size(400, 250);
  frame->show_all();
}

119
FileErrorDialog::~FileErrorDialog()
120 121
{}

122
static
123 124 125 126 127 128 129 130 131 132 133 134 135 136
void print_location(int linenumber, const Glib::ustring& subject, Regexxer::FileInfoPtr fileinfo)
{
  std::cout << fileinfo->fullname << ':' << linenumber + 1 << ':';

  std::string charset;

  if (Glib::get_charset(charset))
    std::cout << subject.raw(); // charset is UTF-8
  else
    std::cout << Glib::convert_with_fallback(subject.raw(), charset, "UTF-8");

  std::cout << std::endl;
}

daniel_e's avatar
daniel_e committed
137 138 139 140 141
} // anonymous namespace

namespace Regexxer
{

142 143 144 145
/**** Regexxer::InitState **************************************************/

InitState::InitState()
:
146 147
  folder        (),
  pattern       (),
148 149
  regex         (),
  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 159 160
{}

InitState::~InitState()
{}

161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
/**** Regexxer::MainWindow::BusyAction *************************************/

class MainWindow::BusyAction
{
private:
  MainWindow& object_;

  BusyAction(const BusyAction&);
  BusyAction& operator=(const BusyAction&);

public:
  explicit BusyAction(MainWindow& object)
    : object_ (object) { object_.busy_action_enter(); }

  ~BusyAction() { object_.busy_action_leave(); }
};

/**** Regexxer::MainWindow *************************************************/

daniel_e's avatar
daniel_e committed
180 181
MainWindow::MainWindow()
:
182
  toolbar_                (0),
183
  grid_file_              (0),
184
  button_folder_          (0),
185
  combo_entry_pattern_    (Gtk::manage(new Gtk::ComboBoxText(true))),
186 187 188
  button_recursive_       (0),
  button_hidden_          (0),
  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 195
  button_multiple_        (0),
  button_caseless_        (0),
196 197
  filetree_               (Gtk::manage(new FileTree())),
  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 203 204
  busy_action_running_    (false),
  busy_action_cancel_     (false),
  busy_action_iteration_  (0),
205
  undo_stack_             (new UndoStack())
daniel_e's avatar
daniel_e committed
206
{
207
  load_xml();
208

209 210
  entry_regex_ = comboboxentry_regex_->get_entry();
  entry_substitution_ = comboboxentry_substitution_->get_entry();
211

212
  textview_->set_buffer(FileBuffer::create());
213
  window_->set_title(PACKAGE_NAME);
daniel_e's avatar
daniel_e committed
214

215 216
  vbox_main_->pack_start(*statusline_, Gtk::PACK_SHRINK);
  scrollwin_filetree_->add(*filetree_);
217
  grid_file_->attach(*combo_entry_pattern_, 1, 1, 1, 1);
218

219
  scrollwin_textview_->add(*textview_);
220

221 222 223
  statusline_->show_all();
  filetree_->show_all();
  combo_entry_pattern_->show_all();
224
  scrollwin_textview_->show_all();
225

226
  connect_signals();
daniel_e's avatar
daniel_e committed
227 228 229 230 231
}

MainWindow::~MainWindow()
{}

Fabien Parent's avatar
Fabien Parent committed
232 233
void MainWindow::initialize(const Glib::RefPtr<Gtk::Application>& application,
                            const InitState& init)
234
{
235 236 237 238 239 240 241 242 243
  Glib::RefPtr<Gio::Settings> settings = Settings::instance();
  int width = settings->get_int(conf_key_window_width);
  int height = settings->get_int(conf_key_window_height);

  int x = settings->get_int(conf_key_window_position_x);
  int y = settings->get_int(conf_key_window_position_y);

  bool maximized = settings->get_boolean(conf_key_window_maximized);

Fabien Parent's avatar
Fabien Parent committed
244 245 246 247 248 249
  application_ = application;
  application->signal_startup().connect
      (sigc::mem_fun(*this, &MainWindow::on_startup));
  application->register_application();

  window_->set_application(application_);
250 251 252 253 254
  window_->resize(width, height);
  window_->move(x, y);
  if (maximized)
    window_->maximize();

255 256 257
  textview_->set_show_line_numbers(settings->get_boolean(conf_key_show_line_numbers));
  textview_->set_highlight_current_line(settings->get_boolean(conf_key_highlight_current_line));
  textview_->set_auto_indent(settings->get_boolean(conf_key_auto_indentation));
258
  textview_->set_draw_spaces(static_cast<Gsv::DrawSpacesFlags>
259 260
                              (settings->get_flags(conf_key_draw_spaces)));

261 262
  std::string folder;

263 264
  if (!init.folder.empty())
    folder = init.folder.front();
265 266 267 268
  if (!Glib::path_is_absolute(folder))
    folder = Glib::build_filename(Glib::get_current_dir(), folder);

  const bool folder_exists = button_folder_->set_current_folder(folder);
269

270
  combo_entry_pattern_->get_entry()->set_text((init.pattern.empty()) ? Glib::ustring(1, '*') : init.pattern);
271

272 273
  entry_regex_->set_text(init.regex);
  entry_regex_->set_completion(entry_regex_completion_);
274
  entry_substitution_->set_text(init.substitution);
275 276 277
  entry_substitution_->set_completion(entry_substitution_completion_);

  comboboxentry_regex_->set_model(entry_regex_completion_stack_.get_completion_model());
278
  comboboxentry_regex_->set_entry_text_column(entry_regex_completion_stack_.get_completion_column());
279
  comboboxentry_substitution_->set_model(entry_substitution_completion_stack_.get_completion_model());
280
  comboboxentry_substitution_->set_entry_text_column(entry_substitution_completion_stack_.get_completion_column());
281

282 283 284 285
  entry_regex_completion_->set_model(entry_regex_completion_stack_.get_completion_model());
  entry_regex_completion_->set_text_column(entry_regex_completion_stack_.get_completion_column());
  entry_regex_completion_->set_inline_completion(true);
  entry_regex_completion_->set_popup_completion(false);
286

287 288 289 290
  entry_substitution_completion_->set_model(entry_substitution_completion_stack_.get_completion_model());
  entry_substitution_completion_->set_text_column(entry_substitution_completion_stack_.get_completion_column());
  entry_substitution_completion_->set_inline_completion(true);
  entry_substitution_completion_->set_popup_completion(false);
291

292 293 294 295
  button_recursive_->set_active(!init.no_recursive);
  button_hidden_   ->set_active(init.hidden);
  button_multiple_ ->set_active(!init.no_global);
  button_caseless_ ->set_active(init.ignorecase);
296

297
  combo_entry_pattern_->set_entry_text_column(0);
298 299 300 301 302 303
  const std::list<Glib::ustring> patterns =
      settings->get_string_array(conf_key_files_patterns);
  for (std::list<Glib::ustring>::const_iterator pattern = patterns.begin();
       pattern != patterns.end();
       ++pattern)
  {
304
    combo_entry_pattern_->append(*pattern);
305
  }
306

307
  if (init.feedback)
308 309
    filetree_->signal_feedback.connect(&print_location);

Fabien Parent's avatar
Fabien Parent committed
310 311 312 313 314 315 316
  Glib::RefPtr<Gtk::StyleContext> style_context =
      toolbar_->get_style_context ();
  if (style_context)
  {
    style_context->add_class (GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
  }

317 318 319 320
  // Strangely, folder_exists seems to be always true, probably because the
  // file chooser works asynchronously but the GLib main loop isn't running
  // yet.  As a work-around, explicitely check whether the directory exists
  // on the file system as well.
321 322
  if (folder_exists && !init.no_autorun
      && !init.folder.empty() && !init.pattern.empty()
323 324
      && Glib::file_test(folder, Glib::FILE_TEST_IS_DIR))
  {
325
    Glib::signal_idle().connect(sigc::mem_fun(*this, &MainWindow::autorun_idle));
326
  }
327 328
}

329 330
/**** Regexxer::MainWindow -- private **************************************/

Fabien Parent's avatar
Fabien Parent committed
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
void MainWindow::on_startup()
{
  static struct
  {
    const char* const name;
    sigc::slot<void> slot;
  } appmenu_actions[] =
  {
    {"preferences", sigc::mem_fun(*this, &MainWindow::on_preferences)},
    {"about", sigc::mem_fun(*this, &MainWindow::on_about)},
    {"quit", sigc::mem_fun(*this, &MainWindow::on_quit)},
    {0},
  };

  Glib::RefPtr<Gtk::Builder> builder =
      Gtk::Builder::create_from_file(ui_appmenu_filename);

  for (int i = 0; appmenu_actions[i].name; i++)
  {
    Glib::RefPtr<Gio::SimpleAction> action =
        Gio::SimpleAction::create(appmenu_actions[i].name);
    application_->add_action(action);
    action->signal_activate().connect(sigc::hide(appmenu_actions[i].slot));
  }

  Glib::RefPtr<Gio::MenuModel> appmenu =
      Glib::RefPtr<Gio::MenuModel>::cast_static(builder->get_object("appmenu"));
  application_->set_app_menu(appmenu);
}

361
void MainWindow::load_xml()
362
{
363
  const Glib::RefPtr<Gtk::Builder> xml = Gtk::Builder::create_from_file(ui_mainwindow_filename);
364

Fabien Parent's avatar
Fabien Parent committed
365
  Gtk::ApplicationWindow* mainwindow = 0;
366 367
  xml->get_widget("mainwindow", mainwindow);
  window_.reset(mainwindow);
368

369
  xml->get_widget("toolbar",             toolbar_);
370
  xml->get_widget("button_folder",       button_folder_);
371 372
  xml->get_widget("button_recursive",    button_recursive_);
  xml->get_widget("button_hidden",       button_hidden_);
373 374
  xml->get_widget("comboboxentry_regex",         comboboxentry_regex_);
  xml->get_widget("comboboxentry_substitution",  comboboxentry_substitution_);
375 376
  xml->get_widget("button_multiple",     button_multiple_);
  xml->get_widget("button_caseless",     button_caseless_);
377
  xml->get_widget("scrollwin_textview",  scrollwin_textview_);
378
  xml->get_widget("entry_preview",       entry_preview_);
379 380
  xml->get_widget("vbox_main",           vbox_main_);
  xml->get_widget("scrollwin_filetree",  scrollwin_filetree_);
381
  xml->get_widget("grid_file",           grid_file_);
382

383
  controller_.load_xml(xml);
384 385
}

386
void MainWindow::connect_signals()
daniel_e's avatar
daniel_e committed
387
{
388 389
  using sigc::bind;
  using sigc::mem_fun;
daniel_e's avatar
daniel_e committed
390

391
  window_->signal_hide         ().connect(mem_fun(*this, &MainWindow::on_hide));
392
  window_->signal_style_updated().connect(mem_fun(*this, &MainWindow::on_style_updated));
393
  window_->signal_delete_event ().connect(mem_fun(*this, &MainWindow::on_delete_event));
daniel_e's avatar
daniel_e committed
394

395 396
  combo_entry_pattern_->get_entry()->signal_activate().connect(controller_.find_files.slot());
  combo_entry_pattern_->signal_changed ().connect(mem_fun(*this, &MainWindow::on_entry_pattern_changed));
daniel_e's avatar
daniel_e committed
397

398 399 400 401 402 403 404
  entry_regex_       ->signal_activate().connect(controller_.find_matches.slot());
  entry_substitution_->signal_activate().connect(controller_.find_matches.slot());
  entry_substitution_->signal_changed ().connect(mem_fun(*this, &MainWindow::update_preview));

  controller_.save_file   .connect(mem_fun(*this, &MainWindow::on_save_file));
  controller_.save_all    .connect(mem_fun(*this, &MainWindow::on_save_all));
  controller_.undo        .connect(mem_fun(*this, &MainWindow::on_undo));
405 406 407 408
  controller_.cut         .connect(mem_fun(*this, &MainWindow::on_cut));
  controller_.copy        .connect(mem_fun(*this, &MainWindow::on_copy));
  controller_.paste       .connect(mem_fun(*this, &MainWindow::on_paste));
  controller_.erase       .connect(mem_fun(*this, &MainWindow::on_erase));
409 410 411 412 413 414 415 416 417 418
  controller_.find_files  .connect(mem_fun(*this, &MainWindow::on_find_files));
  controller_.find_matches.connect(mem_fun(*this, &MainWindow::on_exec_search));
  controller_.next_file   .connect(bind(mem_fun(*this, &MainWindow::on_go_next_file), true));
  controller_.prev_file   .connect(bind(mem_fun(*this, &MainWindow::on_go_next_file), false));
  controller_.next_match  .connect(bind(mem_fun(*this, &MainWindow::on_go_next), true));
  controller_.prev_match  .connect(bind(mem_fun(*this, &MainWindow::on_go_next), false));
  controller_.replace     .connect(mem_fun(*this, &MainWindow::on_replace));
  controller_.replace_file.connect(mem_fun(*this, &MainWindow::on_replace_file));
  controller_.replace_all .connect(mem_fun(*this, &MainWindow::on_replace_all));

419
  Settings::instance()->signal_changed().connect(mem_fun(*this, &MainWindow::on_conf_value_changed));
420 421 422

  statusline_->signal_cancel_clicked.connect(
      mem_fun(*this, &MainWindow::on_busy_action_cancel));
423

424 425
  filetree_->signal_switch_buffer.connect(
      mem_fun(*this, &MainWindow::on_filetree_switch_buffer));
daniel_e's avatar
daniel_e committed
426

427 428
  filetree_->signal_bound_state_changed.connect(
      mem_fun(*this, &MainWindow::on_bound_state_changed));
daniel_e's avatar
daniel_e committed
429

430 431
  filetree_->signal_file_count_changed.connect(
      mem_fun(*this, &MainWindow::on_filetree_file_count_changed));
daniel_e's avatar
daniel_e committed
432

433 434
  filetree_->signal_match_count_changed.connect(
      mem_fun(*this, &MainWindow::on_filetree_match_count_changed));
daniel_e's avatar
daniel_e committed
435

436 437
  filetree_->signal_modified_count_changed.connect(
      mem_fun(*this, &MainWindow::on_filetree_modified_count_changed));
438

439 440
  filetree_->signal_pulse.connect(
      mem_fun(*this, &MainWindow::on_busy_action_pulse));
daniel_e's avatar
daniel_e committed
441

442 443
  filetree_->signal_undo_stack_push.connect(
      mem_fun(*this, &MainWindow::on_undo_stack_push));
daniel_e's avatar
daniel_e committed
444 445
}

446 447 448 449 450 451 452 453 454 455
bool MainWindow::autorun_idle()
{
  controller_.find_files.activate();

  if (!busy_action_cancel_ && entry_regex_->get_text_length() > 0)
    controller_.find_matches.activate();

  return false;
}

456
void MainWindow::on_hide()
daniel_e's avatar
daniel_e committed
457
{
458
  on_busy_action_cancel();
daniel_e's avatar
daniel_e committed
459

460 461 462 463 464 465 466 467 468 469 470 471
  // Kill the dialogs if they're mapped right now.  This isn't strictly
  // necessary since they'd be deleted in the destructor anyway.  But if we
  // have to do a lot of cleanup the dialogs would stay open for that time,
  // which doesn't look neat.
  {
    // Play safe and transfer ownership, and let the dtor do the delete.
    const std::auto_ptr<Gtk::Dialog> temp (about_dialog_);
  }
  {
    const std::auto_ptr<PrefDialog> temp (pref_dialog_);
  }
}
472

473
void MainWindow::on_style_updated()
474 475 476
{
  FileBuffer::pango_context_changed(window_->get_pango_context());
}
daniel_e's avatar
daniel_e committed
477

478 479
bool MainWindow::on_delete_event(GdkEventAny*)
{
480 481 482
  bool quit = confirm_quit_request();
  save_window_state();
  return !quit;
daniel_e's avatar
daniel_e committed
483 484
}

485 486
void MainWindow::on_cut()
{
487 488 489 490 491 492 493 494 495
  if (textview_->is_focus())
  {
    if (const Glib::RefPtr<Gtk::TextBuffer> buffer = textview_->get_buffer())
      buffer->cut_clipboard(textview_->get_clipboard(selection_clipboard),
                            textview_->get_editable());
  }
  else
  {
    const int noEntries = 3;
496
    Gtk::Entry *entries[noEntries] = { combo_entry_pattern_->get_entry(), entry_regex_,
497 498 499 500 501 502 503 504 505 506
                                       entry_substitution_ };
    for (int i = 0; i < 3; i++)
    {
      if (entries[i]->is_focus())
      {
        ((Gtk::Editable *)entries[i])->cut_clipboard();
        return ;
      }
    }
  }
507 508 509 510
}

void MainWindow::on_copy()
{
511 512 513 514 515 516 517 518
  if (textview_->is_focus())
  {
    if (const Glib::RefPtr<Gtk::TextBuffer> buffer = textview_->get_buffer())
      buffer->copy_clipboard(textview_->get_clipboard(selection_clipboard));
  }
  else
  {
    const int noEntries = 3;
519
    Gtk::Entry *entries[noEntries] = { combo_entry_pattern_->get_entry(), entry_regex_,
520 521 522 523 524 525 526 527 528 529
                                       entry_substitution_ };
    for (int i = 0; i < 3; i++)
    {
      if (entries[i]->is_focus())
      {
        ((Gtk::Editable *)entries[i])->copy_clipboard();
        return ;
      }
    }
  }
530 531 532 533
}

void MainWindow::on_paste()
{
534 535 536 537 538 539 540 541 542
  if (textview_->is_focus())
  {
    if (const Glib::RefPtr<Gtk::TextBuffer> buffer = textview_->get_buffer())
      buffer->paste_clipboard(textview_->get_clipboard(selection_clipboard),
                              textview_->get_editable());
  }
  else
  {
    const int noEntries = 3;
543
    Gtk::Entry *entries[noEntries] = { combo_entry_pattern_->get_entry(), entry_regex_,
544 545 546 547 548 549 550 551 552 553
                                       entry_substitution_ };
    for (int i = 0; i < 3; i++)
    {
      if (entries[i]->is_focus())
      {
        ((Gtk::Editable *)entries[i])->paste_clipboard();
        return ;
      }
    }
  }
554 555 556 557 558 559 560 561
}

void MainWindow::on_erase()
{
  if (const Glib::RefPtr<Gtk::TextBuffer> buffer = textview_->get_buffer())
    buffer->erase_selection(true, textview_->get_editable());
}

562 563
void MainWindow::on_quit()
{
564
  if (confirm_quit_request())
565 566
  {
    save_window_state();
567
    window_->hide();
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
  }
}

void MainWindow::save_window_state()
{
  int x = 0;
  int y = 0;

  int width = 0;
  int height = 0;

  window_->get_position(x, y);
  window_->get_size(width, height);
  bool maximized = (window_->get_window()->get_state() & Gdk::WINDOW_STATE_MAXIMIZED);

  Glib::RefPtr<Gio::Settings> settings = Settings::instance();

  settings->set_int(conf_key_window_position_x, x);
  settings->set_int(conf_key_window_position_y, y);
  settings->set_boolean(conf_key_window_maximized, maximized);

  if (!maximized)
  {
    settings->set_int(conf_key_window_width, width);
    settings->set_int(conf_key_window_height, height);
  }
594 595 596 597
}

bool MainWindow::confirm_quit_request()
{
598
  if (filetree_->get_modified_count() == 0)
599 600
    return true;

601 602
  Gtk::MessageDialog dialog (*window_,
                             _("Some files haven\342\200\231t been saved yet.\nQuit anyway?"),
603
                             false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, true);
604 605 606 607 608 609 610

  dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
  dialog.add_button(Gtk::Stock::QUIT,   Gtk::RESPONSE_OK);

  return (dialog.run() == Gtk::RESPONSE_OK);
}

daniel_e's avatar
daniel_e committed
611 612
void MainWindow::on_find_files()
{
613
  if (filetree_->get_modified_count() > 0)
614
  {
615 616
    Gtk::MessageDialog dialog (*window_,
                               _("Some files haven\342\200\231t been saved yet.\nContinue anyway?"),
617
                               false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK_CANCEL, true);
618

619
    if (dialog.run() != Gtk::RESPONSE_OK)
620 621 622
      return;
  }

623
  std::string folder = button_folder_->get_filename();
624

625
  if (folder.empty())
626 627 628
    folder = Glib::get_current_dir();

  g_return_if_fail(Glib::path_is_absolute(folder));
629 630

  undo_stack_clear();
631

632 633
  BusyAction busy (*this);

634 635
  try
  {
636 637 638
    Glib::RefPtr<Glib::Regex> pattern = Glib::Regex::create(
        Util::shell_pattern_to_regex(combo_entry_pattern_->get_entry()->get_text()),
        Glib::REGEX_DOTALL);
639

640 641 642
    filetree_->find_files(folder, pattern,
                          button_recursive_->get_active(),
                          button_hidden_->get_active());
643
  }
644
  catch (const Glib::RegexError&)
645
  {
646 647
    Gtk::MessageDialog dialog (*window_, _("The file search pattern is invalid."),
                               false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
648 649
    dialog.run();
  }
650
  catch (const FileTree::Error& error)
651
  {
652 653
    FileErrorDialog dialog (*window_, _("The following errors occurred during search:"),
                            Gtk::MESSAGE_WARNING, error);
654 655
    dialog.run();
  }
656

657
  statusline_->set_file_count(filetree_->get_file_count());
daniel_e's avatar
daniel_e committed
658 659 660 661
}

void MainWindow::on_exec_search()
{
662 663
  BusyAction busy (*this);

664 665 666
  const Glib::ustring regex = entry_regex_->get_text();
  const bool caseless = button_caseless_->get_active();
  const bool multiple = button_multiple_->get_active();
667

668
  entry_regex_completion_stack_.push(regex);
669 670 671

  Settings::instance()->set_string_array(conf_key_regex_patterns, entry_regex_completion_stack_.get_stack());

daniel_e's avatar
daniel_e committed
672 673
  try
  {
674 675 676
    Glib::RefPtr<Glib::Regex> pattern =
        Glib::Regex::create(regex, (caseless) ? Glib::REGEX_CASELESS
                                              : static_cast<Glib::RegexCompileFlags>(0));
677

678
    filetree_->find_matches(pattern, multiple);
daniel_e's avatar
daniel_e committed
679
  }
680
  catch (const Glib::RegexError& error)
daniel_e's avatar
daniel_e committed
681
  {
682 683
    Gtk::MessageDialog dialog (*window_, error.what(), false,
                               Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
684 685
    dialog.run();

daniel_e's avatar
daniel_e committed
686 687 688
    return;
  }

689
  if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
690 691 692 693 694
  {
    statusline_->set_match_count(buffer->get_original_match_count());
    statusline_->set_match_index(buffer->get_match_index());
  }

695
  if (filetree_->get_match_count() > 0)
daniel_e's avatar
daniel_e committed
696
  {
697 698 699 700
    // Scrolling has to be post-poned after the redraw, otherwise we might
    // not end up where we want to.  So do that by installing an idle handler.

    Glib::signal_idle().connect(
701
        sigc::mem_fun(*this, &MainWindow::after_exec_search),
702
        Glib::PRIORITY_HIGH_IDLE + 25); // slightly less than redraw (+20)
daniel_e's avatar
daniel_e committed
703 704 705
  }
}

706
bool MainWindow::after_exec_search()
707
{
708
  filetree_->select_first_file();
709
  on_go_next(true);
710

711
  return false;
712 713
}

714
void MainWindow::on_filetree_switch_buffer(FileInfoPtr fileinfo, int file_index)
daniel_e's avatar
daniel_e committed
715
{
716
  const FileBufferPtr old_buffer = FileBufferPtr::cast_static(textview_->get_buffer());
daniel_e's avatar
daniel_e committed
717

718
  if (fileinfo && fileinfo->buffer == old_buffer)
daniel_e's avatar
daniel_e committed
719 720
    return;

721
  if (old_buffer)
daniel_e's avatar
daniel_e committed
722
  {
723
    std::for_each(buffer_connections_.begin(), buffer_connections_.end(),
724
                  std::mem_fun_ref(&sigc::connection::disconnect));
daniel_e's avatar
daniel_e committed
725

726
    buffer_connections_.clear();
daniel_e's avatar
daniel_e committed
727 728 729
    old_buffer->forget_current_match();
  }

730
  if (fileinfo)
daniel_e's avatar
daniel_e committed
731
  {
732
    const FileBufferPtr buffer = fileinfo->buffer;
733
    g_return_if_fail(buffer);
734 735

    textview_->set_buffer(buffer);
736
    textview_->set_editable(!fileinfo->load_failed);
737
    textview_->set_cursor_visible(!fileinfo->load_failed);
738

739
    if (!fileinfo->load_failed)
740 741
    {
      buffer_connections_.push_back(buffer->signal_modified_changed().
742
          connect(sigc::mem_fun(*this, &MainWindow::on_buffer_modified_changed)));
743

744
      buffer_connections_.push_back(buffer->signal_bound_state_changed.
745
          connect(sigc::mem_fun(*this, &MainWindow::on_bound_state_changed)));
746

747
      buffer_connections_.push_back(buffer->signal_preview_line_changed.
748
          connect(sigc::mem_fun(*this, &MainWindow::update_preview)));
749
    }
daniel_e's avatar
daniel_e committed
750

751
    set_title_filename(fileinfo->fullname);
daniel_e's avatar
daniel_e committed
752

753 754
    controller_.replace_file.set_enabled(buffer->get_match_count() > 0);
    controller_.save_file.set_enabled(buffer->get_modified());
755
    controller_.edit_actions.set_enabled(!fileinfo->load_failed);
756 757 758

    statusline_->set_match_count(buffer->get_original_match_count());
    statusline_->set_match_index(buffer->get_match_index());
759
    statusline_->set_file_encoding(fileinfo->encoding);
daniel_e's avatar
daniel_e committed
760 761 762 763
  }
  else
  {
    textview_->set_buffer(FileBuffer::create());
764
    textview_->set_editable(false);
765
    textview_->set_cursor_visible(false);
766

767
    window_->set_title(PACKAGE_NAME);
daniel_e's avatar
daniel_e committed
768

769 770
    controller_.replace_file.set_enabled(false);
    controller_.save_file.set_enabled(false);
771
    controller_.edit_actions.set_enabled(false);
772 773 774

    statusline_->set_match_count(0);
    statusline_->set_match_index(0);
775
    statusline_->set_file_encoding("");
daniel_e's avatar
daniel_e committed
776 777
  }

778
  statusline_->set_file_index(file_index);
daniel_e's avatar
daniel_e committed
779 780 781
  update_preview();
}

782
void MainWindow::on_bound_state_changed()
783
{
784
  BoundState bound = filetree_->get_bound_state();
785

786 787
  controller_.prev_file.set_enabled((bound & BOUND_FIRST) == 0);
  controller_.next_file.set_enabled((bound & BOUND_LAST)  == 0);
788

789
  if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
790 791
    bound &= buffer->get_bound_state();

792 793
  controller_.prev_match.set_enabled((bound & BOUND_FIRST) == 0);
  controller_.next_match.set_enabled((bound & BOUND_LAST)  == 0);
794 795
}

796
void MainWindow::on_filetree_file_count_changed()
797
{
798 799 800 801
  const int file_count = filetree_->get_file_count();

  statusline_->set_file_count(file_count);
  controller_.find_matches.set_enabled(file_count > 0);
802 803
}

804
void MainWindow::on_filetree_match_count_changed()
805
{
806
  controller_.replace_all.set_enabled(filetree_->get_match_count() > 0);
807 808 809

  if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
    controller_.replace_file.set_enabled(buffer->get_match_count() > 0);
810 811
}

812
void MainWindow::on_filetree_modified_count_changed()
813
{
814
  controller_.save_all.set_enabled(filetree_->get_modified_count() > 0);
815 816
}

817 818
void MainWindow::on_buffer_modified_changed()
{
819
  controller_.save_file.set_enabled(textview_->get_buffer()->get_modified());
820 821
}

daniel_e's avatar
daniel_e committed
822 823
void MainWindow::on_go_next_file(bool move_forward)
{
824
  filetree_->select_next_file(move_forward);
daniel_e's avatar
daniel_e committed
825 826 827
  on_go_next(move_forward);
}

828 829
bool MainWindow::do_scroll(const Glib::RefPtr<Gtk::TextMark> mark)
{
830 831 832
  if (!mark)
    return false;

833 834 835 836
  textview_->scroll_to(mark, 0.125);
  return false;
}

daniel_e's avatar
daniel_e committed
837 838
void MainWindow::on_go_next(bool move_forward)
{
839
  if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
daniel_e's avatar
daniel_e committed
840
  {
841
    if (const Glib::RefPtr<Gtk::TextMark> mark = buffer->get_next_match(move_forward))
daniel_e's avatar
daniel_e committed
842
    {
843 844
      Glib::signal_idle ().connect (sigc::bind<const Glib::RefPtr<Gtk::TextMark> >
            (sigc::mem_fun (*this, &MainWindow::do_scroll), mark));
845
      statusline_->set_match_index(buffer->get_match_index());
daniel_e's avatar
daniel_e committed
846 847 848 849