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);
}
Exemple #2
0
/**
 *	Get the count of facets which share this vertex.
 *	Get a count of laths which represent the facets which share the vertex this
 *	lath represents.
 *
 *	@return	Pointer to an array of lath pointers.
 */
TqInt CqLath::cQvf() const
{
	TqInt len = 1;

	CqLath* pNext = cv();
	while(NULL != pNext && this != pNext)
	{
		len++;
		pNext = pNext->cv();
	}

	// If we hit a boundary, start again going backwards.
	if(NULL == pNext)
	{
		pNext = ccv();
		// We know we are going to hit a boundary in this direction as well so we can just look for that
		// case as a terminator.
		while(NULL != pNext)
		{
			len++;
			pNext = pNext->ccv();
		}
	}
	return(len);
}
Exemple #3
0
/**
 *	Get the number of vertices emanating from this vertex.
 *	Get a count of laths representing the vertices emanating from the vertex
 *	this lath represents.
 *
 *	@return	Count of laths.
 */
TqInt CqLath::cQvv() const
{
	TqInt c = 1; // Start with this
	// Laths representing the edges that radiate from the associated vertex are obtained by
	// following the clockwise vertex links around the vertex.
	CqLath* pNext = cv();
	const CqLath* pLast = this;
	while(NULL != pNext && this != pNext)
	{
		c++;
		pLast = pNext;
		pNext = pNext->cv();
	}

	// If we hit a boundary, add the ec of this boundary edge and start again going backwards.
	// @warning Adding ccf for the boundary edge means that the lath represents a different vertex.
	if(NULL == pNext)
	{
		pLast = this;
		pNext = ccv();
		// We know we are going to hit a boundary in this direction as well so we can just look for that
		// case as a terminator.
		while(NULL != pNext)
		{
			assert( pNext != this );
			c++;
			pLast = pNext;
			pNext = pNext->ccv();
		}
		// We have hit the boundary going the other way, so add the ccf of this boundary edge.
		c++;
	}
	return( c );
}
Exemple #4
0
/**
 *	Get the edges emanating from a vertex.
 *	Get a list of laths representing the edges which emanate from the vertex
 *	this lath represents.
 *
 *	@return	Pointer to an array of lath pointers.
 */
TqInt CqLath::cQve() const
{
	TqInt len = 1;

	CqLath* pNext = cv();
	const CqLath* pLast = this;
	while(NULL != pNext && this != pNext)
	{
		len++;
		pLast = pNext;
		pNext = pNext->cv();
	}

	// If we hit a boundary, add the ec of this boundary edge and start again going backwards.
	// @warning Adding ccf for the boundary edge means that the lath represents a different vertex.
	if(NULL == pNext)
	{
		pLast = this;
		pNext = ccv();
		// We know we are going to hit a boundary in this direction as well so we can just look for that
		// case as a terminator.
		while(NULL != pNext)
		{
			assert( pNext != this );
			len++;
			pLast = pNext;
			pNext = pNext->ccv();
		}
		// We have hit the boundary going the other way, so add the ccf of this boundary edge.
		len++;
	}
	return(len);
}
Exemple #5
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());
    }
  }
}
Exemple #6
0
/**
 *	Get the facets which share this vertex.
 *	Get a list of laths which represent the facets which share the vertex this
 *	lath represents.
 *
 *	@return	Pointer to an array of lath pointers.
 */
void CqLath::Qvf(std::vector<const CqLath*>& Result) const
{
	TqInt len = cQvf();

	const CqLath* pNext = cv();

	Result.resize(len);
	TqInt index = 0;

	// Laths representing the edges that radiate from the associated vertex are obtained by
	// following the clockwise vertex links around the vertex.
	const CqLath *pTmpLath = this;
	Result[index++] = pTmpLath;

	while(NULL != pNext && this != pNext)
	{
		Result[index++] = pNext;
		pNext = pNext->cv();
	}

	// If we hit a boundary, start again going backwards.
	if(NULL == pNext)
	{
		pNext = ccv();
		// We know we are going to hit a boundary in this direction as well so we can just look for that
		// case as a terminator.
		while(NULL != pNext)
		{
			Result[index++] = pNext;
			pNext = pNext->ccv();
		}
	}
}
Exemple #7
0
bool
get_ccv_feat(const char *file, feature_ccv &feat)
{
        try {
                cv::Mat colorImage = cv::imread(file, 1);
                if(colorImage.empty())
                        return false;

                ccv(colorImage, feat);
        } catch (...) {
                return false;
        }

        return true;
}
Exemple #8
0
/**
 *	Get the edges emanating from a vertex.
 *	Get a list of laths representing the edges which emanate from the vertex
 *	this lath represents.
 *
 *	@return	Pointer to an array of lath pointers.
 */
void CqLath::Qve(std::vector<const CqLath*>& Result) const
{
	TqInt len = cQve();

	const CqLath* pNext = cv();
	const CqLath* pLast = this;

	Result.resize(len);
	TqInt index = 0;
	// Laths representing the edges that radiate from the associated vertex are obtained by
	// following the clockwise vertex links around the vertex.
	const CqLath *pTmpLath = this;
	Result[index++] = pTmpLath;

	while(NULL != pNext && this != pNext)
	{
		Result[index++] = pNext;
		pLast = pNext;
		pNext = pNext->cv();
	}

	// If we hit a boundary, add the ec of this boundary edge and start again going backwards.
	// @warning Adding ccf for the boundary edge means that the lath represents a different vertex.
	if(NULL == pNext)
	{
		pLast = this;
		pNext = ccv();
		// We know we are going to hit a boundary in this direction as well so we can just look for that
		// case as a terminator.
		while(NULL != pNext)
		{
			Result[index++] = pNext;
			pLast = pNext;
			pNext = pNext->ccv();
		}
		// We have hit the boundary going the other way, so add the ccf of this boundary edge.
		Result[index++] = pLast->cf();
	}
}
Exemple #9
0
void WindowMerge::button_ready_clicked()
// Called when ready approving.
{
  // Store the merge result in main project's chapter.
  gchar *contents;
  g_file_get_contents(approve_master_file.c_str(), &contents, NULL, NULL);
  ustring text(contents);
  g_free(contents);
  text = merge_join_data(text);
  ParseLine parseline(text);
  CategorizeChapterVerse ccv(parseline.lines);
  project_store_chapter(approve_master_project, approve_book, ccv);

  // Store persistent snapshots to enable future merges.
  snapshots_shoot_chapter (approve_master_project, approve_book, approve_chapter, 0, true);
  snapshots_shoot_chapter (approve_edited_project, approve_book, approve_chapter, 0, true);

  // GUI.
  gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook1), 0);

  // Reload the editors.
  gtk_button_clicked(GTK_BUTTON(reload_editors_button));
}
Exemple #10
0
void search_string_basic(const ustring & project, bool use_book_selection, unsigned int currentchapter, vector <Reference> &results)
/*
Basic search for a string in the text.
project: project to search.
use_book_selection: whether to limit searches to the selected books only.
chapter: currently opened chapter in the editor.
results: will contain the search results.
*/
{
  // Settings.
  extern Settings *settings;
  // Case sensitive
  bool casesensitive = settings->session.search_case_sensitive;
  // The string to search for. 
  // We need to normalize the search expression when comparing strings.
  ustring localsearchword(settings->session.searchword);
  localsearchword = localsearchword.normalize();
  if (!casesensitive)
    localsearchword = localsearchword.casefold();
  // Book and chapter selection.
  bool search_current_chapter = settings->session.search_current_chapter;
  set <unsigned int> selectedbooks = settings->session.selected_books;
  // Get all books.
  vector <unsigned int> books = project_get_books(project);
  // Progress.
  ProgressWindow progresswindow(_("Searching"), false);
  progresswindow.set_iterate(0, 1, books.size());
  // Go through all books.
  for (unsigned int bk = 0; bk < books.size(); bk++) {
    progresswindow.iterate();
    unsigned int book = books[bk];
    // Handle possible case of book selection.
    if (use_book_selection) {
      if (selectedbooks.find(book) == selectedbooks.end()) {
        continue;
      }
    }
    // Get all chapters and go through them.
    vector <unsigned int> chapters = project_get_chapters(project, book);
    for (unsigned int ch = 0; ch < chapters.size(); ch++) {
      unsigned int chapter = chapters[ch];
      // Do we search this chapter?
      if (search_current_chapter) {
        if (chapter != currentchapter)
          continue;
      }
      // Take a quick peek at the chapter if the word is there.
      ustring chapterfilename = project_data_filename_chapter(project, book, chapter, false);
      gchar *quickcontent;
      bool wordfound = false;
      if (g_file_get_contents(chapterfilename.c_str(), &quickcontent, NULL, NULL)) {
        gchar *foundlocation;
        if (casesensitive) {
          foundlocation = g_strrstr(quickcontent, localsearchword.c_str());
        } else {
          gchar *casefoldedcontent = g_utf8_casefold(quickcontent, -1);
          foundlocation = g_strrstr(casefoldedcontent, localsearchword.c_str());
          if (casefoldedcontent)
            g_free(casefoldedcontent);
        }
        if (foundlocation)
          wordfound = true;
      }
      if (quickcontent)
        g_free(quickcontent);
      if (wordfound) {
        ReadText rt(chapterfilename, true, false);
        CategorizeChapterVerse ccv(rt.lines);
        for (unsigned int i = 0; i < ccv.verse.size(); i++) {
          if (!casesensitive)
            ccv.line[i] = ccv.line[i].casefold();
          if (ccv.line[i].find(localsearchword) != string::npos) {
            Reference reference(book, chapter, ccv.verse[i]);
            results.push_back(reference);
          }
        }
      }
    }
  }
}
Exemple #11
0
vector < Reference > search_in_bibledit()
// Advanced searching in Bibledit.
{
  // Configuration / session
  extern Settings *settings;
  // Set some variables in memory for higher speed.
  bool casesensitive = settings->session.search_case_sensitive;
  bool search_current_book = settings->session.search_current_book;
  bool search_current_chapter = settings->session.search_current_chapter;
  bool search_globbing = settings->session.search_globbing;
  bool search_start_word_match = settings->session.search_start_word_match;
  bool search_end_word_match = settings->session.search_end_word_match;
  set < unsigned int >selected_books = settings->session.selected_books;
  AreaType areatype = settings->session.area_type;
  bool area_id = settings->session.area_id;
  bool area_intro = settings->session.area_intro;
  bool area_heading = settings->session.area_heading;
  bool area_chapter = settings->session.area_chapter;
  bool area_study = settings->session.area_study;
  bool area_notes = settings->session.area_notes;
  bool area_xref = settings->session.area_xref;
  bool area_verse = settings->session.area_verse;

  // Progress information.
  ProgressWindow progresswindow(_("Searching"), true);

  // The string to search for. 
  // Note any apostrophies need to be doubled for SQLite.
  // We need to normalize the search expression when comparing strings.
  ustring localsearchword(settings->session.searchword.normalize());
  if (!casesensitive)
    localsearchword = localsearchword.casefold();
  ustring localsearchword2(localsearchword);

  // Storage for references: search results.
  vector < Reference > results;

  // Get our position in the text.
  ustring project = settings->genconfig.project_get();
  ustring book = books_id_to_english(settings->genconfig.book_get());
  unsigned int chapter = convert_to_int(settings->genconfig.chapter_get());

  // Go through each book in the project. Progress information.
  vector < unsigned int >availablebooks = project_get_books(project);
  progresswindow.set_iterate(0, 1, availablebooks.size());
  for (unsigned int bk = 0; bk < availablebooks.size(); bk++) {
    progresswindow.iterate();
    if (progresswindow.cancel) {
      return results;
    }
    // If the book is not to be searched, skip it.
    if (search_current_book)
      if (book != books_id_to_english(availablebooks[bk]))
        continue;
    if (selected_books.find(availablebooks[bk]) == selected_books.end())
      continue;

    try {
      // Go through each chapter in the book.
      vector < unsigned int >chapters = project_get_chapters(project, availablebooks[bk]);
      for (unsigned int ch = 0; ch < chapters.size(); ch++) {

        // Do we search this chapter?
        if (search_current_chapter)
          if (chapter != chapters[ch])
            continue;

        // Read the chapter.
        vector < ustring > lines = project_retrieve_chapter(project, availablebooks[bk], chapters[ch]);
        CategorizeChapterVerse ccv(lines);

        // Go through the verses.
        for (unsigned int i = 0; i < ccv.verse.size(); i++) {

          // Verse number.
          ustring verse = ccv.verse[i];

          // Handle casesensitive and area selection.
          // Assemble text to search through.
          ustring input(ccv.line[i]);
          if (!casesensitive)
            input = input.casefold();
          ustring text = search_in_bibledit_assemble_line(input, areatype, area_id, area_intro, area_heading, area_chapter, area_study, area_notes, area_xref, area_verse);

          // Use glob-style pattern matching or straight match.
          if (search_globbing) {
            ustring patternword = "*" + localsearchword + "*";
            if (!g_pattern_match_simple(patternword.c_str(), text.c_str()))
              continue;
          } else {
            if (text.find(localsearchword) == string::npos)
              continue;
          }

          // Do the word boundary matching.
          if (!search_in_bibledit_word_boundaries_match(text, localsearchword, search_start_word_match, search_end_word_match, search_globbing))
            continue;

          // This verse "passed" all tests: a search result.  
          Reference reference(availablebooks[bk], chapters[ch], verse);
          results.push_back(reference);
        }
      }
    }
    catch(exception & ex) {
      gw_critical(ex.what());
    }
  }

  // Give the results.
  return results;
}
Exemple #12
0
void import_bibleworks_text_file (const ustring& file, const ustring& bible, vector <ustring>& messages)
// Imports a bibleworks text file.
{
  // Read the file.
  ReadText rt (file, true, false);

  // If there's nothing to import, bail out.
  if (rt.lines.empty()) {
    messages.push_back ("The file is empty");
  }

  // Divide the input into separate bits for each book.
  vector <VectorUstring> bookdata;
  if (messages.empty()) {
    try {
      ustring previousbook;
      vector < ustring > booklines;
      de_byte_order_mark (rt.lines[0]);
      for (unsigned int i = 0; i < rt.lines.size(); i++) {
        ustring currentbook = rt.lines[i].substr(0, 3);
        if (i == 0) {
          previousbook = currentbook;
        }
        if (currentbook != previousbook) {
          bookdata.push_back (booklines);
          booklines.clear();
          previousbook = currentbook;
        }
        booklines.push_back(rt.lines[i]);
      }
      bookdata.push_back (booklines);
    }
    catch(exception & ex) {
      messages.push_back(ex.what());
    }
  }

  // Import each book.
  if (messages.empty ()) {
    ProgressWindow progresswindow ("Importing", false);
    progresswindow.set_iterate (0, 1, bookdata.size());
    for (unsigned int i = 0; i < bookdata.size(); i++) {
      progresswindow.iterate ();
      try {

        // Input and output data.
        vector <ustring> rawlines = bookdata[i];
        vector <ustring> usfmlines;

        // Get the name of the book.
        unsigned int book_id = books_bibleworks_to_id(rawlines[0].substr(0, 3));
        if (book_id == 0) {
          messages.push_back ("Unknown book: " + rawlines[0]);
          return;
        }

        // Store USFM id.
        ustring usfmid = books_id_to_paratext (book_id);
        usfmlines.push_back("\\id " + usfmid);

        // Convert the BibleWorks lines to USFM code.
        ustring previouschapter = "0";
        for (unsigned int i = 0; i < rawlines.size(); i++) {
          // Convert chapter information.
          ustring line = rawlines[i];
          line.erase(0, 4);
          ustring currentchapter = number_in_string(line);
          line.erase(0, currentchapter.length() + 1);
          if (currentchapter != previouschapter) {
            usfmlines.push_back("\\c " + currentchapter);
            usfmlines.push_back("\\p");
            previouschapter = currentchapter;
          }
          // Convert verse data.
          usfmlines.push_back("\\v " + line);
        }

        // Store into the Bible.
        CategorizeChapterVerse ccv(usfmlines);
        project_store_book(bible, book_id, ccv);
        
      }
      catch(exception & ex) {
        messages.push_back(ex.what());
      }
    }
  }

}
Exemple #13
0
void Wordlist::run(vector < ustring > &allmessages)
{
  // No project given: bail out.
  if (project.empty()) {
    message(_("No project"));
    return;
  }
  // Pass 1: Collect words and handle asterisks.

  // Go through the books.
  vector < unsigned int >books = project_get_books(project);
  progresswindow->set_iterate(0, 1, books.size());
  for (unsigned int bk = 0; bk < books.size(); bk++) {
    progresswindow->iterate();

    // Go through the chapters.
    vector < unsigned int >chapters = project_get_chapters(project, books[bk]);
    for (unsigned int ch = 0; ch < chapters.size(); ch++) {

      // Go through the lines of the chapter, and process them.
      vector <ustring> lines = project_retrieve_chapter(project, books[bk], chapters[ch]);
      set <ustring> section_entries;
      bool chapter_content_was_changed = false;
      for (unsigned int i = 0; i < lines.size(); i++) {
        ustring line(lines[i]);
        process_line(line, section_entries);
        if (line != lines[i]) {
          chapter_content_was_changed = true;
          lines[i] = line;
        }
      }
      if (chapter_content_was_changed) {
        CategorizeChapterVerse ccv(lines);
        project_store_chapter(project, books[bk], ccv);
      }
    }
  }
  // Informative messages.
  message(_("Total entries: ") + convert_to_string(wordcount));
  message(_("Unique entries: ") + convert_to_string((unsigned int)words.size()));

  // Pass 2: Insert word lists.

  // Only proceed if there is something to insert.
  if (!words.empty()) {

    // Whether a list was inserted.
    bool inserted = false;

    // Go through the books.
    progresswindow->set_iterate(0, 1, books.size());
    for (unsigned int bk = 0; bk < books.size(); bk++) {
      progresswindow->iterate();

      // Go through the chapters.
      vector < unsigned int >chapters = project_get_chapters(project, books[bk]);
      for (unsigned int ch = 0; ch < chapters.size(); ch++) {

        // Go through the lines of the chapter to look for the position to insert the list.
        unsigned int opener_offset = 0;
        unsigned int closer_offset = 0;
        vector < ustring > lines = project_retrieve_chapter(project, books[bk], chapters[ch]);
        for (unsigned int ln = 0; ln < lines.size(); ln++) {
          if (lines[ln].find(list_opener) == 0) {
            opener_offset = ln;
          }
          if (lines[ln].find(list_closer) == 0) {
            closer_offset = ln;
          }
        }

        // If something like a position was found, process that.
        if (opener_offset || closer_offset) {
          if (opener_offset && closer_offset && (opener_offset < closer_offset)) {
            insert_list(lines, opener_offset, closer_offset);
            CategorizeChapterVerse ccv(lines);
            project_store_chapter(project, books[bk], ccv);
            message(_("Word list inserted in ") + books_id_to_english(books[bk]) + " " + convert_to_string(chapters[ch]));
            inserted = true;
          } else {
            message(_("Invalid word list location in ") + books_id_to_english(books[bk]) + " " + convert_to_string(chapters[ch]));
          }
        }
      }
    }

    // Message if the list was not inserted.
    if (!inserted)
      message(_("No place found to insert the word list"));
  }
  // Store messages.
  for (unsigned int i = 0; i < messages.size(); i++) {
    allmessages.push_back(messages[i]);
  }
}
Exemple #14
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"));

  }
}