Exemplo n.º 1
0
void WindowMerge::copy_master_to_edited_chapter(unsigned int bk, unsigned int ch, bool gui)
{
  // Only copy if the master and edited version differ. This saves a lot of git operations.
  vector <ustring> master_lines = project_retrieve_chapter(current_master_project, bk, ch);
  vector <ustring> edited_lines = project_retrieve_chapter(current_edited_project, bk, ch);
  bool master_is_edited = false;
  if (master_lines.size() == edited_lines.size()) {
    master_is_edited = true;
    for (unsigned int i = 0; i < master_lines.size(); i++) {
      if (master_lines[i] != edited_lines[i]) {
        master_is_edited = false;
      }
    }
  }
  CategorizeChapterVerse ccv(master_lines);
  if (master_is_edited) {
    if (gui) 
      gtkw_dialog_info(NULL, _("Both chapters are already the same"));
  } else {
    project_store_chapter(current_edited_project, bk, ccv);
    // A normal snapshot may be removed over time, so we need a persistent one to enable future merges.
    snapshots_shoot_chapter (current_master_project, bk, ch, 0, true);
    snapshots_shoot_chapter (current_edited_project, bk, ch, 0, true);
    if (gui) {
      ustring message = books_id_to_english(bk) + " " + convert_to_string(ch) + _(" was copied from project ") + current_master_project + _(" to project ") + current_edited_project;
      gtkw_dialog_info(NULL, message.c_str());
    }
  }
}
Exemplo n.º 2
0
void RevertDialog::on_okbutton()
// On pressing Ok, if the user has selected a change, give warning we're going
// to change the data, and if Yes is responded with, proceed with loading the change.
{
  // If no revision loaded, bail out.
  if (!revisionloaded)
    return;

  // Get current and previous text.
  ustring current_text_filename = project_data_filename_chapter(project, book_get(), chapter_get(), false);
  ReadText rt (current_text_filename, true, false);

  // If no change, bail out.
  if (vector_strings_equal (rt.lines, history_data)) {
    gtkw_dialog_info (NULL, _("No changes were applied"));
    return;
  }

  // Ask the user if he is sure to load the previous text. If not, bail out.
  int result = gtkw_dialog_question(revertdialog, _("Are you sure you wish to revert this chapter to the previous revision?"));
  if (result != GTK_RESPONSE_YES)
    return;

  // Go back to previous revision.
  vector < ustring > lines2;
  for (unsigned int i = 0; i < history_data.size(); i++)
    if (!history_data[i].empty())
      lines2.push_back(history_data[i]);
  CategorizeChapterVerse ccv(lines2);
  project_store_chapter(project, book_get(), ccv);
}
Exemplo n.º 3
0
void WindowMerge::on_button_next_previous(bool next)
// This function looks for the next (or previous) chapter that differs
// in the two projects.
{
  // Variables that control the loop.
  bool change_found = false;
  bool more_chapters_available = true;
  unsigned int new_chapter = chapter;
  unsigned int new_book = book;
  do {
    // Look for next (or previous) chapter.
    vector < unsigned int >chapters = project_get_chapters(current_edited_project, new_book);
    if (chapters.empty())
      break;
    unsigned int index = 0;
    for (unsigned int i = 0; i < chapters.size(); i++) {
      if (new_chapter == chapters[i])
        index = i;
    }
    if (next) {
      if (index == (chapters.size() - 1)) {
        if (!cross_book_boundaries(true, new_book, new_chapter)) {
          more_chapters_available = false;
        }
      } else {
        new_chapter = chapters[++index];
      }
    } else {
      if (index == 0) {
        if (!cross_book_boundaries(false, new_book, new_chapter)) {
          more_chapters_available = false;
        }
      } else {
        new_chapter = chapters[--index];
      }
    }
    // See whether this chapter differs.
    vector < ustring > master_chapter_data = project_retrieve_chapter(current_master_project, new_book, new_chapter);
    vector < ustring > edited_chapter_data = project_retrieve_chapter(current_edited_project, new_book, new_chapter);
    if (master_chapter_data.size() != edited_chapter_data.size())
      change_found = true;
    for (unsigned int i = 0; i < master_chapter_data.size(); i++) {
      if (!change_found) {
        if (master_chapter_data[i] != edited_chapter_data[i])
          change_found = true;
      }
    }
  } while (!change_found && more_chapters_available);
  // Deal with whether a change was found.
  if (change_found) {
    book = new_book;
    chapter = new_chapter;
    gtk_button_clicked(GTK_BUTTON(new_reference_button));
  } else {
    gtkw_dialog_info(NULL, _("No more differing chapters found"));
  }
}
Exemplo n.º 4
0
void WindowMerge::copy_master_to_edited_all()
{
  {
    vector <unsigned int> books = project_get_books(current_master_project);
    ProgressWindow progresswindow(_("Copying..."), false);
    progresswindow.set_iterate(0, 1, books.size());
    for (unsigned int bk = 0; bk < books.size(); bk++) {
      progresswindow.iterate();
      vector < unsigned int >chapters = project_get_chapters(current_master_project, books[bk]);
      for (unsigned int ch = 0; ch < chapters.size(); ch++) {
        copy_master_to_edited_chapter(books[bk], chapters[ch], false);
      }
    }
  }
  ustring message = _("All chapters of project ") + current_master_project + _(" were copied to project ") + current_edited_project;
  gtkw_dialog_info(NULL, message.c_str());
}
Exemplo n.º 5
0
void WordlistDialog::on_okbutton()
{
  // Save settings in the configuration.
  extern Settings *settings;
  settings->genconfig.wordlist_process_general_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_wordlist)));
  settings->genconfig.wordlist_general_asterisk_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_asterisk_general)));
  settings->genconfig.wordlist_general_asterisk_first_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_general_first_time)));
  settings->genconfig.wordlist_process_hebrew_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_hebrew_wordlist)));
  settings->genconfig.wordlist_hebrew_asterisk_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_asterisk_hebrew)));
  settings->genconfig.wordlist_hebrew_asterisk_first_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_hebrew_firsttime)));
  settings->genconfig.wordlist_process_greek_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_greek_wordlist)));
  settings->genconfig.wordlist_greek_asterisk_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_asterisk_greek)));
  settings->genconfig.wordlist_greek_asterisk_first_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_greek_first_time)));
  settings->genconfig.wordlist_process_index_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_index)));
  settings->genconfig.wordlist_index_asterisk_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_asterisk_index)));
  settings->genconfig.wordlist_index_asterisk_first_set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_index_first_time)));
  // Run the three word lists, and collect their messages.
  vector < ustring > messages;
  if (settings->genconfig.wordlist_process_general_get()) {
    Wordlist wordlist(wltGeneral);
    wordlist.run(messages);
  }
  if (settings->genconfig.wordlist_process_hebrew_get()) {
    Wordlist wordlist(wltHebrew);
    wordlist.run(messages);
  }
  if (settings->genconfig.wordlist_process_greek_get()) {
    Wordlist wordlist(wltGreek);
    wordlist.run(messages);
  }
  if (settings->genconfig.wordlist_process_index_get()) {
    Wordlist wordlist(wltIndex);
    wordlist.run(messages);
  }
  // Display messages.
  if (!messages.empty()) {
    ustring message;
    for (unsigned int i = 0; i < messages.size(); i++) {
      message.append(messages[i] + "\n");
    }
    gtkw_dialog_info(wordlistdialog, message.c_str());
  }
}
Exemplo n.º 6
0
void WindowMerge::approval_setup(const ustring & maindata, const ustring & mergedata)
{
  // Initialize the approval system's variables and gui.
  approve_master_project = current_master_project;
  approve_edited_project = current_edited_project;
  approve_book = book;
  approve_chapter = chapter;
  ustring label = _("Changes approval, ") + books_id_to_english(approve_book) + " " + convert_to_string(approve_chapter);
  gtk_label_set_text(GTK_LABEL(label_approve), label.c_str());
  gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook1), 1);
  approve_master_file = gw_build_filename(workingdirectory, "master");
  approve_merge_file = gw_build_filename(workingdirectory, "merged");

  // Save both sets of data to file.
  g_file_set_contents(approve_master_file.c_str(), merge_split_data(trim(maindata)).c_str(), -1, NULL);
  g_file_set_contents(approve_merge_file.c_str(), merge_split_data(trim(mergedata)).c_str(), -1, NULL);

  // Show differences in the GUI.
  approval_show_diff();

  // Info for user.
  gtkw_dialog_info(NULL, _("The chapters are ready for approving the individual changes"));
}
Exemplo n.º 7
0
void view_parallel_bible_pdf()
{
  // Log.
  gw_message(_("Printing Parallel Bible"));

  // Configuration
  extern Settings *settings;

  // Get the chapters and check them.
  vector < unsigned int >chapters = project_get_chapters(settings->genconfig.project_get(), settings->genconfig.book_get());
  if (chapters.empty()) {
    gtkw_dialog_info(NULL, books_id_to_english(settings->genconfig.book_get()) + _("does not exist in this project"));
    return;
  }
  // Progress system.
  ProgressWindow progresswindow(_("Parallel Bible"), true);
  progresswindow.set_text(_("Collecting verses"));
  progresswindow.set_iterate(0, 1, chapters.size());

  // Messages to be printed first.
  vector < ustring > messages;

  // All the projects to be put in this parallel Bible.
  // If the book exists in the project, add it, else give message.
  vector <ustring> project_s_raw;
  {
    vector <ustring> bibles = settings->genconfig.parallel_bible_projects_get();
    vector <bool> enabled = settings->genconfig.parallel_bible_enabled_get();
    if (bibles.size () == enabled.size()) {
      for (unsigned int i = 0; i < enabled.size(); i++) {
        if (enabled[i]) {
          project_s_raw.push_back (bibles[i]);
        }
      }
    }
  }
  vector < ustring > project_names;
  for (unsigned int i = 0; i < project_s_raw.size(); i++) {
    if (project_book_exists(project_s_raw[i], settings->genconfig.book_get())) {
      project_names.push_back(project_s_raw[i]);
    } else {
      messages.push_back(_("Project ") + project_s_raw[i] + _(" was requested to be included, but it does not contain ") + books_id_to_english(settings->genconfig.book_get()) + _(". It was left out."));
    }
  }

  // References to print.
  vector < Reference > references;

  // Portion selection.
  WithinReferencesRange inrange;
  {
    vector < unsigned int >portions_chapter_from, portions_chapter_to;
    vector < ustring > portions_verse_from, portions_verse_to;
    select_portion_get_values(settings->genconfig.project_get(), settings->genconfig.book_get(), settings->genconfig.parallel_bible_chapters_verses_get(), portions_chapter_from, portions_verse_from, portions_chapter_to, portions_verse_to);
    inrange.add_portion(settings->genconfig.book_get(), portions_chapter_from, portions_verse_from, portions_chapter_to, portions_verse_to);
    inrange.set_book(settings->genconfig.book_get());
  }

  // Go through the chapters.
  for (unsigned int ch = 0; ch < chapters.size(); ch++) {

    progresswindow.iterate();

    inrange.set_chapter(chapters[ch]);

    // Go through the verse numbers in this chapter.
    vector < ustring > verses = project_get_verses(settings->genconfig.project_get(), settings->genconfig.book_get(), chapters[ch]);
    for (unsigned int vs = 0; vs < verses.size(); vs++) {

      inrange.set_verse(verses[vs]);
      if (!inrange.in_range())
        continue;

      // See whether to print verses zero.
      if (!settings->genconfig.parallel_bible_include_verse_zero_get())
        if (verses[vs] == "0")
          continue;

      // Store the reference.
      Reference reference(settings->genconfig.book_get(), chapters[ch], verses[vs]);
      references.push_back(reference);
    }
  }

  // Hide progeressbar.
  progresswindow.hide();

  // Do the printing.
  ProjectMemory projectmemory(settings->genconfig.project_get(), true);
  view_parallel_references_pdf(projectmemory, &project_names, references, settings->genconfig.parallel_bible_keep_verses_together_get(), &messages, false);

  // Log: ready.
  gw_message(_("Ready printing the Parallel Bible"));
}
Exemplo n.º 8
0
void PrintProject2::print()
// Runs the project through xetex and shows it in a pdf viewer.
{
  // Scripture related data.
  // Possible exclusion of books.
  if (portionproject.empty())
    portionproject = myproject->name;
  scriptureportions = new ScripturePortions(portionproject);
  if (scriptureportions->books.empty()) {
    gtkw_dialog_info(NULL, _("There were no books to print\nSelect some books and try again"));
    return;
  }

  // Create the XeTeX object.
  XeTeX xetex (0);

  // Usfm style object.
  Usfm usfm (stylesheet_get_actual ());

  // Collect usfm code for all the books.
  for (unsigned int i = 0; i < scriptureportions->books.size(); i++) {

    // Book id.
    unsigned int book_id = books_english_to_id(scriptureportions->books[i]);

    // Ranges to include.
    WithinReferencesRange inrange;
    {
      vector <unsigned int> chapters_from, chapters_to;
      vector <ustring> verses_from, verses_to;
      select_portion_get_values(portionproject, book_id, scriptureportions->portions[i], chapters_from, verses_from, chapters_to, verses_to);
      inrange.add_portion(book_id, chapters_from, verses_from, chapters_to, verses_to);
    }
    inrange.set_book (book_id);
    inrange.set_chapter (0);
    inrange.set_verse ("0");

    // Open the book.
    vector <ustring> full_book_lines;
    for (unsigned int i2 = 0; i2 < myproject->data.size(); i2++) {
      if (myproject->data[i2].number == book_id) {
        full_book_lines = myproject->data[i2].get_data();
      }
    }

    // Take only those portions the user wishes to print.
    vector <ustring> book_lines;
    for (unsigned int i = 0; i < full_book_lines.size(); i++) {
      ustring line = full_book_lines[i];
      ustring marker = usfm_extract_marker(line);
      if (usfm.is_chapter_number(marker)) {
        inrange.set_chapter (convert_to_int(number_in_string(line)));
        inrange.set_verse ("0");
      }
      if (usfm.is_verse_number(marker)) {
        size_t position = line.find(" ");
        position = CLAMP(position, 0, line.length());
        ustring verse = line.substr(0, position);
        inrange.set_verse (verse);
      }
      if (inrange.in_range()) {
        book_lines.push_back (full_book_lines[i]);
      } else {
        if (marker == "id") {
          ParseLine parseline (full_book_lines[i]);
          if (!parseline.lines.empty ()) {
            book_lines.push_back (parseline.lines[0]);
          }
        }
      }
    }

    // Do text replacements on the lines.
    text_replacement(book_lines);
    // Add the lines to the XeTeX object.
    xetex.add_book (book_id, book_lines);
  }

  // Process the data.
  ustring pdf_file = xetex.run ();

  // Display the pdf file if there is one.
  // There would be none if the formatting process was cancelled by the user.
  if (!pdf_file.empty()) {
    pdfviewer_view (pdf_file);
  }
}
Exemplo n.º 9
0
void ImportAssistant::on_button_files ()
// Selection and processing of the files to import.
{
  // Set directory.
  ustring directory;
  if (!files_names.empty()) {
    directory = gw_path_get_dirname (files_names[0]);
  }

  // Processing variables.
  files_messages.clear();
  files_book_ids.clear();
  
  // Select files.
  {
    vector <ustring> files = gtkw_file_chooser_open_multiple (assistant, "", directory);
    if (!files.empty()) {
      files_names = files;
    }
  }

  // Ensure that there are only uncompressed files, or only one compressed file.
  vector <ustring> compressed_files;
  for (unsigned int i = 0; i < files_names.size(); i++) {
    if (compressed_archive_recognized (files_names[i]))
      compressed_files.push_back (files_names[i]);
  }
  if (!compressed_files.empty()) {
    if (compressed_files.size() > 1) {
      files_messages.push_back (_("You have selected more than one compressed file"));
    }
    if (compressed_files.size() == 1) {
      if (files_names.size() != 1) {
        files_messages.push_back (_("You have selected a mixture of normal and compressed files"));
      }
    }
  }

  // Optionally uncompress the archive and let the user select files from within it.
  if (files_messages.empty()) {
    if (compressed_files.size() == 1) {
      ustring unpack_directory = gw_build_filename (Directories->get_temp (), "uncompress");
      unix_rmdir (unpack_directory);
      uncompress (compressed_files[0], unpack_directory);
      gtkw_dialog_info (assistant, _("You will now be asked to select files from within the compressed archive"));
      files_names = gtkw_file_chooser_open_multiple (assistant, "", unpack_directory);
    }
  }

  // Check that all files are in Unicode.
  if (files_messages.empty()) {
    vector <ustring> unicode_files;
    vector <ustring> non_unicode_files;
    for (unsigned int i = 0; i < files_names.size(); i++) {
      ustring contents;
      gchar *s;
      g_file_get_contents(files_names[i].c_str(), &s, NULL, NULL);
      contents = s;
      g_free(s);
      if (contents.validate()) {
        unicode_files.push_back (files_names[i]);
      } else {
        non_unicode_files.push_back (files_names[i]);
      }
    }
    files_names = unicode_files;
    if (!non_unicode_files.empty()) {
      files_messages.push_back (_("The following files are not in the right Unicode format and are therefore not fit for import:"));
      for (unsigned int i = 0; i < non_unicode_files.size(); i++) {
        files_messages.push_back (non_unicode_files[i]);
      }
      files_messages.push_back (_("The online help provides more information about how to make these fit for use."));
    }
  }

  // Check that at least one file was selected.
  if (files_messages.empty()) {
    if (files_names.empty()) {
      files_messages.push_back (_("No files were selected"));
    }
  }

  // Specific checks for each import type. 
  if (files_messages.empty()) {
    switch (get_type ()) {
    case itBible:
      {
        switch (get_bible_type()) {
        case ibtUsfm:
          {
            import_check_usfm_files (files_names, files_book_ids, bible_name, files_messages);
            break;
          }
        case ibtBibleWorks:
          {
            import_check_bibleworks_file (files_names, files_book_ids, bible_name, files_messages);
            break;
          }
        case ibtOnlineBible:
          {
            break;
          }
        case ibtRawText:
          {
            break;
          }
        }
        break;
      }
    case itReferences:
      {
        break;
      }
    case itStylesheet:
      {
        break;
      }
    case itNotes:
      {
        break;
      }
    case itKeyterms:
      {
        break;
      }
    }
  }

  // Gui update.
  on_assistant_prepare (vbox_files);
}
Exemplo n.º 10
0
void WindowMerge::merge_edited_into_master(bool approve)
// This merges the edited data into the master data, and does error checking.
{
  // Bail out if there's nothing to merge.
  if (main_project_data == edited_project_data) {
    gtkw_dialog_info(NULL, _("Both the chapters already are the same"));
    return;
  }

  // Get the available snapshots of the master and edited projects.
  vector <unsigned int> masterseconds = snapshots_get_seconds (current_master_project, book, chapter);
  vector <unsigned int> editedseconds = snapshots_get_seconds (current_edited_project, book, chapter);

  // We need to look for the common ancestor.
  // It needs a fast routine that goes through the history as little as possible.

  // Make a combined set of the times and flags.
  vector <bool> combinedflags;
  vector <unsigned int> combinedseconds;
  for (unsigned int i = 0; i < masterseconds.size(); i++) {
    combinedflags.push_back (true);
    combinedseconds.push_back (masterseconds[i]);    
  }
  for (unsigned int i = 0; i < editedseconds.size(); i++) {
    combinedflags.push_back (false);
    combinedseconds.push_back (editedseconds[i]);    
  }
  // Sort the combined set on the time, most recent ones first.
  quick_sort (combinedseconds, combinedflags, 0, combinedseconds.size());
  {
    vector <bool> flags = combinedflags;
    vector <unsigned int> seconds = combinedseconds;
    combinedflags.clear();
    combinedseconds.clear();
    for (int i = flags.size() - 1; i >= 0; i--) {
      combinedflags.push_back (flags[i]);
      combinedseconds.push_back (seconds[i]);
    }
  }

  // Go through the history of both projects, extract the state in history,
  // and compare them in order to find the common ancestor.
  vector <ustring> mastertexts;
  vector <ustring> editedtexts;
  ustring common_ancestor;
  for (unsigned int i = 0; i < combinedseconds.size(); i++) {
    unsigned int second = combinedseconds[i];
    bool master = combinedflags[i];
    if (master) {
      mastertexts.push_back(snapshots_get_chapter(current_master_project, book, chapter, second));
    } else {
      editedtexts.push_back(snapshots_get_chapter(current_edited_project, book, chapter, second));
    }
    for (unsigned int m = 0; m < mastertexts.size(); m++) {
      for (unsigned int e = 0; e < editedtexts.size(); e++) {
        if (common_ancestor.empty()) {
          if (mastertexts[m] == editedtexts[e]) {
            common_ancestor = mastertexts[m];
          }
        }
      }
    }
    if (!common_ancestor.empty()) {
      break;
    }
  }  

  // If no common ancestor was found, give message and bail out.
  if (common_ancestor.empty()) {
    gtkw_dialog_error(NULL, _("Can't merge because a common ancestor was not found"));
    return;
  }
  // Do the merge in a temporal directory.
  workingdirectory = gw_build_filename(Directories->get_temp(), "merge");
  unix_rmdir(workingdirectory);
  gw_mkdir_with_parents(workingdirectory);

  /*
     Merge works with file1, file2 and file3.

     merge [ options ] file1 file2 file3

     merge incorporates all changes that lead from file2 to file3 into file1.
     The result ordinarily goes into file1.
     merge is useful for combining separate changes to an original. 
     Suppose file2 is the original, and both file1 and file3 are modifications of file2. 
     Then merge combines both changes.
   */
  ustring file1 = gw_build_filename(workingdirectory, "file1");
  ustring file2 = gw_build_filename(workingdirectory, "file2");
  ustring file3 = gw_build_filename(workingdirectory, "file3");

  /*
     merge has problems when two consecutive lines are changed, 
     one line in one file and the other line in the other file. 
     Therefore data is going to be cut on the spaces, 
     so that there is one word per line. 
     Each new line is indicated too so as to facilitate joining the loose bits again.
     Another advantage of this is that the merge operation becomes finer grained.
   */

  // Write the data for the common ancestor.
  g_file_set_contents(file2.c_str(), merge_split_data(common_ancestor).c_str(), -1, NULL);

  // Write the data for the main project.
  g_file_set_contents(file1.c_str(), merge_split_data(main_project_data).c_str(), -1, NULL);

  // Write the data for the edited project.
  g_file_set_contents(file3.c_str(), merge_split_data(edited_project_data).c_str(), -1, NULL);

  // Do the three-way merge.
  {
    GwSpawn spawn("merge");
    spawn.workingdirectory(workingdirectory);
    spawn.arg(file1);
    spawn.arg(file2);
    spawn.arg(file3);
    spawn.run();
  }

  // Read the result of the merge.
  ustring merge_result;
  {
    gchar *contents;
    g_file_get_contents(file1.c_str(), &contents, NULL, NULL);
    if (contents) {
      merge_result = contents;
      g_free(contents);
    }
  }

  // Make conflicts human readable.
  merge_result = merge_conflicts_2_human_readable_text(merge_result);

  // Join the bits again.
  merge_result = merge_join_data(merge_result);

  // If there are conflicts, resolve them.
  if (merge_result.find(merge_conflict_markup(1)) != string::npos) {
    MergeDialog dialog(merge_result);
    if (dialog.run() == GTK_RESPONSE_OK) {
      merge_result = dialog.reconciled_text;
    }
  }
  // If there are still conflicts, give a message and bail out.
  if (merge_result.find(merge_conflict_markup(1)) != string::npos) {
    gtkw_dialog_error(NULL, _("The chapters were not merged"));
    return;
  }

  if (approve) {

    // Setup the approval system.
    approval_setup(main_project_data, merge_result);

  } else {

    // Store the merge result in both chapters.  
    ParseLine parseline(merge_result);
    CategorizeChapterVerse ccv(parseline.lines);
    project_store_chapter(current_master_project, book, ccv);
    project_store_chapter(current_edited_project, book, ccv);
    // A normal snapshot may be removed over time, so we need a persistent one to enable future merges.
    snapshots_shoot_chapter (current_master_project, book, chapter, 0, true);
    snapshots_shoot_chapter (current_edited_project, book, chapter, 0, true);

    // Message ok.
    gtkw_dialog_info(NULL, _("The chapters were successfully merged"));

  }
}