[gnote] Allow user to decide if links are updated when renaming a note



commit 0e68e71973db18f79b309f8b4edac00b41f4353a
Author: Debarshi Ray <debarshir src gnome org>
Date:   Wed Feb 17 18:12:10 2010 +0200

    Allow user to decide if links are updated when renaming a note
    
    By default, when renaming a note to which other notes have links,
    display a dialog explaining the situation, showing a list of affected
    notes, and letting the user pick which notes (if any) will be updated.
    Notes that are not updated will have the link removed (not just
    broken).
    
    The dialog also allows the user to set a preference to always show the
    dialog, or always update link text to reference the new note name, or
    always leave the text alone and remove the link. This preference has
    also been added to the Editing tab of the Preferences dialog.
    
    Fixes: https://bugzilla.gnome.org/584789

 data/gnote.schemas.in     |   20 ++
 po/POTFILES.in            |    1 +
 src/Makefile.am           |    1 +
 src/note.cpp              |  141 +++++++++++++++
 src/note.hpp              |   10 +
 src/noterenamedialog.cpp  |  427 +++++++++++++++++++++++++++++++++++++++++++++
 src/noterenamedialog.hpp  |   84 +++++++++
 src/preferences.cpp       |    2 +
 src/preferences.hpp       |    2 +
 src/preferencesdialog.cpp |   78 ++++++++-
 src/preferencesdialog.hpp |    6 +
 src/watchers.cpp          |   26 +---
 12 files changed, 767 insertions(+), 31 deletions(-)
---
diff --git a/data/gnote.schemas.in b/data/gnote.schemas.in
index 1ee664d..c95f628 100644
--- a/data/gnote.schemas.in
+++ b/data/gnote.schemas.in
@@ -518,6 +518,26 @@
     </schema>
 
     <schema>
+      <key>/schemas/apps/gnote/note_rename_behavior</key>
+      <applyto>/apps/gnote/note_rename_behavior</applyto>
+      <owner>gnote</owner>
+      <type>int</type>
+      <default>0</default>
+      <locale name="C">
+         <short>Link Updating Behavior on Note Rename</short>
+         <long>
+          Integer value indicating if there is a preference to always perform
+          a specific link updating behavior when a note is renamed, instead of prompting
+          the user.  The values map to an internal enumeration.  0 indicates
+          that the user wishes to be prompted when renaming a note may impact links
+          that exist in other notes. 1 indicates that links should automatically
+          be removed. 2 indicates that link text should be updated to the new note name
+          so that it will continue linking to the renamed note.
+         </long>
+      </locale>
+    </schema>
+
+    <schema>
       <key>/schemas/apps/gnote/insert_timestamp/format</key>
       <applyto>/apps/gnote/insert_timestamp/format</applyto>
       <owner>gnote</owner>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 741d751..4b6fa36 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -7,6 +7,7 @@ src/actionmanager.cpp
 src/gnote.cpp
 src/preferencesdialog.cpp
 src/notemanager.cpp
+src/noterenamedialog.cpp
 src/notewindow.cpp
 src/note.cpp
 src/recentchanges.cpp
diff --git a/src/Makefile.am b/src/Makefile.am
index 3a1b8ef..b744bd4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -128,6 +128,7 @@ libgnote_a_SOURCES = \
 	notebuffer.hpp notebuffer.cpp \
 	noteeditor.hpp noteeditor.cpp \
 	notemanager.hpp notemanager.cpp \
+	noterenamedialog.hpp noterenamedialog.cpp \
 	notetag.hpp notetag.cpp \
 	note.hpp note.cpp \
 	notewindow.hpp notewindow.cpp \
diff --git a/src/note.cpp b/src/note.cpp
index 582aeaf..379ec81 100644
--- a/src/note.cpp
+++ b/src/note.cpp
@@ -40,6 +40,7 @@
 
 #include "note.hpp"
 #include "notemanager.hpp"
+#include "noterenamedialog.hpp"
 #include "notetag.hpp"
 #include "notewindow.hpp"
 #include "tagmanager.hpp"
@@ -753,6 +754,13 @@ namespace gnote {
 
   void Note::set_title(const std::string & new_title)
   {
+    set_title(new_title, false);
+  }
+
+
+  void Note::set_title(const std::string & new_title,
+                       bool from_user_action)
+  {
     if (m_data.data().title() != new_title) {
       if (m_window) {
         m_window->set_title(new_title);
@@ -761,6 +769,10 @@ namespace gnote {
       std::string old_title = m_data.data().title();
       m_data.data().title() = new_title;
 
+      if (from_user_action) {
+        process_rename_link_update(old_title);
+      }
+
       m_signal_renamed(shared_from_this(), old_title);
 
       queue_save (CONTENT_CHANGED); // TODO: Right place for this?
@@ -768,6 +780,135 @@ namespace gnote {
   }
 
 
+  void Note::process_rename_link_update(const std::string & old_title)
+  {
+    Note::List linking_notes;
+    const Note::List & manager_notes = m_manager.get_notes();
+    const Note::Ptr self = shared_from_this();
+
+    for (Note::List::const_iterator iter = manager_notes.begin();
+         manager_notes.end() != iter;
+         iter++) {
+      // Technically, containing text does not imply linking,
+      // but this is less work
+      const Note::Ptr note = *iter;
+      if (note != self && note->contains_text(old_title))
+        linking_notes.push_back(note);
+    }
+
+    if (!linking_notes.empty()) {
+      Preferences & preferences = Preferences::obj();
+      const NoteRenameBehavior behavior
+        = static_cast<NoteRenameBehavior>(
+            preferences.get<int>(Preferences::NOTE_RENAME_BEHAVIOR));
+
+      if (NOTE_RENAME_ALWAYS_SHOW_DIALOG == behavior) {
+        NoteRenameDialog dlg(linking_notes, old_title, self);
+        const int response = dlg.run();
+        const NoteRenameBehavior selected_behavior
+                                   = dlg.get_selected_behavior();
+        if (Gtk::RESPONSE_CANCEL != response
+            && NOTE_RENAME_ALWAYS_SHOW_DIALOG
+                 != selected_behavior) {
+          preferences.set<int>(Preferences::NOTE_RENAME_BEHAVIOR,
+                               selected_behavior);
+        }
+
+        const NoteRenameDialog::MapPtr notes = dlg.get_notes();
+
+        for (std::map<Note::Ptr, bool>::const_iterator iter
+               = notes->begin();
+             notes->end() != iter;
+             iter++) {
+          const std::pair<Note::Ptr, bool> p = *iter;
+          if (p.second && response == Gtk::RESPONSE_YES) // Rename
+            p.first->rename_links(old_title, self);
+          else
+            p.first->remove_links(old_title, self);
+        }
+        dlg.hide();
+      }
+      else if (NOTE_RENAME_ALWAYS_REMOVE_LINKS == behavior) {
+        for (Note::List::const_iterator iter = linking_notes.begin();
+             linking_notes.end() != iter;
+             iter++) {
+          (*iter)->remove_links(old_title, self);
+        }
+      }
+      else if (NOTE_RENAME_ALWAYS_RENAME_LINKS == behavior) {
+        for (Note::List::const_iterator iter = linking_notes.begin();
+             linking_notes.end() != iter;
+             iter++) {
+          (*iter)->rename_links(old_title, self);
+        }
+      }
+    }
+  }
+
+
+  bool Note::contains_text(const std::string & text)
+  {
+    const std::string text_lower = sharp::string_to_lower(text);
+    const std::string text_content_lower
+                        = sharp::string_to_lower(text_content());
+    return sharp::string_index_of(text_content_lower, text_lower) > -1;
+  }
+
+
+  void Note::rename_links(const std::string & old_title,
+                          const Ptr & renamed)
+  {
+    handle_link_rename(old_title, renamed, true);
+  }
+
+
+  void Note::remove_links(const std::string & old_title,
+                          const Ptr & renamed)
+  {
+    handle_link_rename(old_title, renamed, false);
+  }
+
+
+  void Note::handle_link_rename(const std::string & old_title,
+                                const Ptr & renamed,
+                                bool rename)
+  {
+    // Check again, things may have changed
+    if (!contains_text(old_title))
+      return;
+
+    const std::string old_title_lower
+                        = sharp::string_to_lower(old_title);
+
+    const NoteTag::Ptr link_tag = m_tag_table->get_link_tag();
+
+    // Replace existing links with the new title.
+    utils::TextTagEnumerator enumerator(m_buffer, link_tag);
+    while (enumerator.move_next()) {
+      const utils::TextRange & range(enumerator.current());
+      if (sharp::string_to_lower(range.text()) != old_title_lower)
+        continue;
+
+      if (!rename) {
+        DBG_OUT("Removing link tag from text %s",
+                range.text().c_str());
+        m_buffer->remove_tag(link_tag, range.start(), range.end());
+      }
+      else {
+        DBG_OUT("Replacing %s with %s",
+                range.text().c_str(),
+                renamed->get_title().c_str());
+        const Gtk::TextIter start_iter = range.start();
+        const Gtk::TextIter end_iter = range.end();
+        m_buffer->erase(start_iter, end_iter);
+        m_buffer->insert_with_tag(range.start(),
+                                  renamed->get_title(),
+                                  link_tag);
+      }
+    }
+  }
+
+
   void Note::rename_without_link_update(const std::string & newTitle)
   {
     if (m_data.data().title() != newTitle) {
diff --git a/src/note.hpp b/src/note.hpp
index d485d4a..7110157 100644
--- a/src/note.hpp
+++ b/src/note.hpp
@@ -157,6 +157,7 @@ public:
     }
   const std::string & get_title() const;
   void set_title(const std::string & new_tile);
+  void set_title(const std::string & new_title, bool from_user_action);
   void rename_without_link_update(const std::string & newTitle);
   const std::string & xml_content()
     {
@@ -223,6 +224,10 @@ public:
     { return m_signal_tag_removed; }
 
 private:
+  bool contains_text(const std::string & text);
+  void handle_link_rename(const std::string & old_title,
+                          const Ptr & renamed,
+                          bool rename);
   void on_buffer_changed();
   void on_buffer_tag_applied(const Glib::RefPtr<Gtk::TextTag> &tag, 
                              const Gtk::TextBuffer::iterator &, 
@@ -236,6 +241,11 @@ private:
   bool on_window_destroyed(GdkEventAny *ev);
   void on_save_timeout();
   void process_child_widget_queue();
+  void process_rename_link_update(const std::string & old_title);
+  void rename_links(const std::string & old_title,
+                    const Ptr & renamed);
+  void remove_links(const std::string & old_title,
+                    const Ptr & renamed);
 
   Note(NoteData * data, const std::string & filepath, NoteManager & manager);
 
diff --git a/src/noterenamedialog.cpp b/src/noterenamedialog.cpp
new file mode 100644
index 0000000..89e38d6
--- /dev/null
+++ b/src/noterenamedialog.cpp
@@ -0,0 +1,427 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2010 Debarshi Ray
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <algorithm>
+#include <functional>
+#include <utility>
+
+#include <glibmm/i18n.h>
+
+#include "notewindow.hpp"
+#include "noterenamedialog.hpp"
+
+namespace gnote {
+
+class ModelColumnRecord
+  : public Gtk::TreeModelColumnRecord
+{
+public:
+
+  ModelColumnRecord();
+  virtual ~ModelColumnRecord();
+
+  const Gtk::TreeModelColumn<bool> & get_column_selected() const;
+  gint get_column_selected_num() const;
+
+  const Gtk::TreeModelColumn<std::string> & get_column_title() const;
+  gint get_column_title_num() const;
+
+  const Gtk::TreeModelColumn<Note::Ptr> & get_column_note() const;
+  gint get_column_note_num() const;
+
+private:
+
+  enum {
+    COLUMN_BOOL = 0,
+    COLUMN_TITLE,
+    COLUMN_NOTE,
+    COLUMN_COUNT
+  };
+
+  Gtk::TreeModelColumn<bool> m_column_selected;
+  Gtk::TreeModelColumn<std::string> m_column_title;
+  Gtk::TreeModelColumn<Note::Ptr> m_column_note;
+};
+
+ModelColumnRecord::ModelColumnRecord()
+  : Gtk::TreeModelColumnRecord()
+  , m_column_selected()
+  , m_column_title()
+  , m_column_note()
+{
+    add(m_column_selected);
+    add(m_column_title);
+    add(m_column_note);
+}
+
+ModelColumnRecord::~ModelColumnRecord()
+{
+}
+
+const Gtk::TreeModelColumn<bool> & ModelColumnRecord::get_column_selected()
+                                     const
+{
+    return m_column_selected;
+}
+
+gint ModelColumnRecord::get_column_selected_num() const
+{
+    return COLUMN_BOOL;
+}
+
+const Gtk::TreeModelColumn<std::string> & ModelColumnRecord::get_column_title()
+                                            const
+{
+    return m_column_title;
+}
+
+gint ModelColumnRecord::get_column_title_num() const
+{
+    return COLUMN_TITLE;
+}
+
+const Gtk::TreeModelColumn<Note::Ptr> & ModelColumnRecord::get_column_note()
+                                          const
+{
+    return m_column_note;
+}
+
+gint ModelColumnRecord::get_column_note_num() const
+{
+    return COLUMN_NOTE;
+}
+
+class ModelFiller
+  : public std::unary_function<const Note::Ptr &, void>
+{
+public:
+
+  ModelFiller(const Glib::RefPtr<Gtk::ListStore> & list_store);
+  void operator()(const Note::Ptr & note);
+
+private:
+
+  Glib::RefPtr<Gtk::ListStore> m_list_store;
+};
+
+ModelFiller::ModelFiller(
+               const Glib::RefPtr<Gtk::ListStore> & list_store)
+  : std::unary_function<const Note::Ptr &, void>()
+  , m_list_store(list_store)
+{
+}
+
+void ModelFiller::operator()(const Note::Ptr & note)
+{
+  if (!note)
+    return;
+
+  ModelColumnRecord model_column_record;
+  const Gtk::TreeModel::iterator iter = m_list_store->append();
+  Gtk::TreeModel::Row row = *iter;
+
+  row[model_column_record.get_column_selected()] = true;
+  row[model_column_record.get_column_title()] = note->get_title();
+  row[model_column_record.get_column_note()] = note;
+}
+
+NoteRenameDialog::NoteRenameDialog(const Note::List & notes,
+                                   const std::string & old_title,
+                                   const Note::Ptr & renamed_note)
+  : Gtk::Dialog(_("Rename Note Links?"),
+                *renamed_note->get_window(),
+                false,
+                false)
+  , m_notes_model(Gtk::ListStore::create(ModelColumnRecord()))
+  , m_dont_rename_button(_("_Don't Rename Links"), true)
+  , m_rename_button(_("_Rename Links"), true)
+  , m_select_all_button(_("Select All"))
+  , m_select_none_button(_("Select None"))
+  , m_always_show_dlg_radio(_("Always show this _window"), true)
+  , m_always_rename_radio(_("Alwa_ys rename links"),
+                          true)
+  , m_never_rename_radio(_("Never rename _links"),
+                         true)
+  , m_notes_box(false, 5)
+{
+  set_default_response(Gtk::RESPONSE_CANCEL);
+  set_border_width(10);
+
+  Gtk::VBox * const vbox = get_vbox();
+
+  add_action_widget(m_rename_button, Gtk::RESPONSE_YES);
+  add_action_widget(m_dont_rename_button, Gtk::RESPONSE_NO);
+
+  std::for_each(notes.begin(), notes.end(),
+                ModelFiller(m_notes_model));
+
+  Gtk::Label * const label = Gtk::manage(new Gtk::Label());
+  label->set_use_markup(true);
+  label->set_markup(
+    Glib::ustring::compose(
+      _("Rename links in other notes from "
+        "\"<span underline=\"single\">%1</span>\" to"
+        "\"<span underline=\"single\">%2</span>\"?\n\n"
+        "If you do not rename the links, they will no longer link to "
+        "anything."),
+      old_title,
+      renamed_note->get_title()));
+  label->set_line_wrap(true);
+  vbox->pack_start(*label, false, true, 5);
+
+  Gtk::TreeView * const notes_view = Gtk::manage(
+                                       new Gtk::TreeView(
+                                             m_notes_model));
+  notes_view->set_size_request(-1, 200);
+  notes_view->signal_row_activated().connect(
+    sigc::bind(
+      sigc::mem_fun(*this,
+                    &NoteRenameDialog::on_notes_view_row_activated),
+      old_title));
+
+  ModelColumnRecord model_column_record;
+
+  Gtk::CellRendererToggle * const toggle_cell
+                                    = Gtk::manage(
+                                        new Gtk::CellRendererToggle);
+  toggle_cell->set_activatable(true);
+  toggle_cell->signal_toggled().connect(
+    sigc::mem_fun(*this,
+                  &NoteRenameDialog::on_toggle_cell_toggled));
+
+  {
+    Gtk::TreeViewColumn * const column = Gtk::manage(
+                                           new Gtk::TreeViewColumn(
+                                                 _("Rename Links"),
+                                                 *toggle_cell));
+    column->add_attribute(*toggle_cell,
+                          "active",
+                          model_column_record.get_column_selected());
+    column->set_sort_column_id(
+              model_column_record.get_column_selected());
+    column->set_resizable(true);
+    notes_view->append_column(*column);
+  }
+
+  {
+    Gtk::TreeViewColumn * const column
+      = Gtk::manage(
+          new Gtk::TreeViewColumn(
+                _("Note Title"),
+                model_column_record.get_column_title()));
+    column->set_sort_column_id(model_column_record.get_column_title());
+    column->set_resizable(true);
+    notes_view->append_column(*column);
+  }
+
+  m_select_all_button.signal_clicked().connect(
+    sigc::bind(
+      sigc::mem_fun(*this,
+                    &NoteRenameDialog::on_select_all_button_clicked),
+      true));
+
+  m_select_none_button.signal_clicked().connect(
+    sigc::bind(
+      sigc::mem_fun(*this,
+                    &NoteRenameDialog::on_select_all_button_clicked),
+      false));
+
+  Gtk::HButtonBox * const notes_button_box
+                            = Gtk::manage(new Gtk::HButtonBox(
+                                                Gtk::BUTTONBOX_END,
+                                                5));
+  notes_button_box->add(m_select_none_button);
+  notes_button_box->add(m_select_all_button);
+
+  Gtk::ScrolledWindow * const notes_scroll
+                                = Gtk::manage(
+                                         new Gtk::ScrolledWindow());
+  notes_scroll->add(*notes_view);
+
+  m_notes_box.pack_start(*notes_scroll, Gtk::PACK_EXPAND_WIDGET, 0);
+  m_notes_box.pack_start(*notes_button_box, false, true, 0);
+
+  Gtk::Expander * const advanced_expander
+                          = Gtk::manage(new Gtk::Expander(
+                                              _("Ad_vanced"), true));
+  Gtk::VBox * const expand_box = Gtk::manage(new Gtk::VBox(false, 0));
+  expand_box->pack_start(m_notes_box, Gtk::PACK_EXPAND_WIDGET, 0);
+
+  m_always_show_dlg_radio.signal_clicked().connect(
+    sigc::mem_fun(*this,
+                  &NoteRenameDialog::on_always_show_dlg_clicked));
+
+  Gtk::RadioButton::Group group = m_always_show_dlg_radio.get_group();
+
+  m_never_rename_radio.set_group(group);
+  m_never_rename_radio.signal_clicked().connect(
+    sigc::mem_fun(*this,
+                  &NoteRenameDialog::on_never_rename_clicked));
+
+  m_always_rename_radio.set_group(group);
+  m_always_rename_radio.signal_clicked().connect(
+    sigc::mem_fun(*this,
+                  &NoteRenameDialog::on_always_rename_clicked));
+
+  expand_box->pack_start(m_always_show_dlg_radio, false, true, 0);
+  expand_box->pack_start(m_never_rename_radio, false, true, 0);
+  expand_box->pack_start(m_always_rename_radio, false, true, 0);
+  advanced_expander->add(*expand_box);
+  vbox->pack_start(*advanced_expander, true, true, 5);
+
+  advanced_expander->property_expanded().signal_changed().connect(
+    sigc::bind(
+      sigc::mem_fun(*this,
+                    &NoteRenameDialog::on_advanced_expander_changed),
+      advanced_expander->property_expanded().get_value()));
+
+  set_focus(m_dont_rename_button);
+  vbox->show_all();
+}
+
+NoteRenameDialog::MapPtr NoteRenameDialog::get_notes() const
+{
+  const MapPtr notes(new std::map<Note::Ptr, bool>);
+
+  m_notes_model->foreach_iter(
+    sigc::bind(
+      sigc::mem_fun(
+        *this,
+        &NoteRenameDialog::on_notes_model_foreach_iter_accumulate),
+      notes));
+  return notes;
+}
+
+NoteRenameBehavior NoteRenameDialog::get_selected_behavior() const
+{
+  if (m_never_rename_radio.get_active())
+    return NOTE_RENAME_ALWAYS_REMOVE_LINKS;
+  else if (m_always_rename_radio.get_active())
+    return NOTE_RENAME_ALWAYS_RENAME_LINKS;
+
+  return NOTE_RENAME_ALWAYS_SHOW_DIALOG;
+}
+
+void NoteRenameDialog::on_advanced_expander_changed(bool expanded)
+{
+  set_resizable(expanded);
+}
+
+void NoteRenameDialog::on_always_rename_clicked()
+{
+  m_select_all_button.clicked();
+  m_notes_box.set_sensitive(false);
+  m_rename_button.set_sensitive(true);
+  m_dont_rename_button.set_sensitive(false);
+}
+
+void NoteRenameDialog::on_always_show_dlg_clicked()
+{
+  m_select_all_button.clicked();
+  m_notes_box.set_sensitive(true);
+  m_rename_button.set_sensitive(true);
+  m_dont_rename_button.set_sensitive(true);
+}
+
+void NoteRenameDialog::on_never_rename_clicked()
+{
+  m_select_none_button.clicked();
+  m_notes_box.set_sensitive(false);
+  m_rename_button.set_sensitive(false);
+  m_dont_rename_button.set_sensitive(true);
+}
+
+bool NoteRenameDialog::on_notes_model_foreach_iter_accumulate(
+                         const Gtk::TreeIter & iter,
+                         const MapPtr & notes) const
+{
+  ModelColumnRecord model_column_record;
+  const Gtk::TreeModel::Row row = *iter;
+
+  notes->insert(std::make_pair(
+                  row[model_column_record.get_column_note()],
+                  row[model_column_record.get_column_selected()]));
+  return false;
+}
+
+bool NoteRenameDialog::on_notes_model_foreach_iter_select(
+                         const Gtk::TreeIter & iter,
+                         bool select)
+{
+  ModelColumnRecord model_column_record;
+  Gtk::TreeModel::Row row = *iter;
+  row[model_column_record.get_column_selected()] = select;
+
+  return false;
+}
+
+void NoteRenameDialog::on_notes_view_row_activated(
+                         const Gtk::TreePath & p,
+                         Gtk::TreeView::Column *,
+                         const std::string & old_title)
+{
+  const Gtk::TreeModel::iterator iter = m_notes_model->get_iter(p);
+  if (!iter)
+    return;
+
+  ModelColumnRecord model_column_record;
+  Gtk::TreeModel::Row row = *iter;
+  const Note::Ptr note = row[model_column_record.get_column_note()];
+  if (!note)
+    return;
+
+  NoteWindow * const window = note->get_window();
+  if (!window)
+    return;
+
+  window->present();
+
+  NoteFindBar & find = window->get_find_bar();
+  find.show_all();
+  find.property_visible() = true;
+  find.set_search_text(Glib::ustring::compose("\"%1\"", old_title));
+}
+
+void NoteRenameDialog::on_select_all_button_clicked(bool select)
+{
+  m_notes_model->foreach_iter(
+    sigc::bind(
+      sigc::mem_fun(
+        *this,
+        &NoteRenameDialog::on_notes_model_foreach_iter_select),
+      select));
+}
+
+void NoteRenameDialog::on_toggle_cell_toggled(const std::string & p)
+{
+  const Gtk::TreeModel::iterator iter = m_notes_model->get_iter(p);
+  if (!iter)
+    return;
+
+  ModelColumnRecord model_column_record;
+  Gtk::TreeModel::Row row = *iter;
+  row[model_column_record.get_column_selected()]
+    = !row[model_column_record.get_column_selected()];
+}
+
+}
diff --git a/src/noterenamedialog.hpp b/src/noterenamedialog.hpp
new file mode 100644
index 0000000..1f2b19e
--- /dev/null
+++ b/src/noterenamedialog.hpp
@@ -0,0 +1,84 @@
+/*
+ * gnote
+ *
+ * Copyright (C) 2010 Debarshi Ray
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NOTE_RENAME_DIALOG_HPP_
+#define __NOTE_RENAME_DIALOG_HPP_
+
+#include <map>
+#include <string>
+#include <tr1/memory>
+
+#include <glibmm.h>
+#include <gtkmm.h>
+
+#include "note.hpp"
+
+namespace gnote {
+
+// Values should match with those in data/gnote.schemas.in
+enum NoteRenameBehavior {
+  NOTE_RENAME_ALWAYS_SHOW_DIALOG = 0,
+  NOTE_RENAME_ALWAYS_REMOVE_LINKS = 1,
+  NOTE_RENAME_ALWAYS_RENAME_LINKS = 2
+};
+
+class NoteRenameDialog
+  : public Gtk::Dialog
+{
+public:
+
+  typedef std::tr1::shared_ptr<std::map<Note::Ptr, bool> > MapPtr;
+
+  NoteRenameDialog(const Note::List & notes,
+                   const std::string & old_title,
+                   const Note::Ptr & renamed_note);
+  MapPtr get_notes() const;
+  NoteRenameBehavior get_selected_behavior() const;
+
+private:
+
+  void on_advanced_expander_changed(bool expanded);
+  void on_always_rename_clicked();
+  void on_always_show_dlg_clicked();
+  void on_never_rename_clicked();
+  bool on_notes_model_foreach_iter_accumulate(
+         const Gtk::TreeIter & iter,
+         const MapPtr & notes) const;
+  bool on_notes_model_foreach_iter_select(const Gtk::TreeIter & iter,
+                                          bool select);
+  void on_notes_view_row_activated(const Gtk::TreeModel::Path & p,
+                                   Gtk::TreeView::Column *,
+                                   const std::string & old_title);
+  void on_select_all_button_clicked(bool select);
+  void on_toggle_cell_toggled(const std::string & p);
+
+  Glib::RefPtr<Gtk::ListStore> m_notes_model;
+  Gtk::Button m_dont_rename_button;
+  Gtk::Button m_rename_button;
+  Gtk::Button m_select_all_button;
+  Gtk::Button m_select_none_button;
+  Gtk::RadioButton m_always_show_dlg_radio;
+  Gtk::RadioButton m_always_rename_radio;
+  Gtk::RadioButton m_never_rename_radio;
+  Gtk::VBox m_notes_box;
+};
+
+}
+
+#endif
diff --git a/src/preferences.cpp b/src/preferences.cpp
index c075b85..d956097 100644
--- a/src/preferences.cpp
+++ b/src/preferences.cpp
@@ -55,6 +55,8 @@ namespace gnote {
   const char * Preferences::SYNC_SELECTED_SERVICE_ADDIN = "/apps/gnote/sync/sync_selected_service_addin";
   const char * Preferences::SYNC_CONFIGURED_CONFLICT_BEHAVIOR = "/apps/gnote/sync/sync_conflict_behavior";
 
+  const char * Preferences::NOTE_RENAME_BEHAVIOR = "/apps/gnote/note_rename_behavior";
+
   const char * Preferences::INSERT_TIMESTAMP_FORMAT = "/apps/gnote/insert_timestamp/format";
     
   const char * Preferences::SEARCH_WINDOW_X_POS = "/apps/gnote/search_window_x_pos";
diff --git a/src/preferences.hpp b/src/preferences.hpp
index 3b9f1fb..7522a06 100644
--- a/src/preferences.hpp
+++ b/src/preferences.hpp
@@ -65,6 +65,8 @@ namespace gnote {
     static const char *SYNC_SELECTED_SERVICE_ADDIN;
     static const char *SYNC_CONFIGURED_CONFLICT_BEHAVIOR;
 
+    static const char *NOTE_RENAME_BEHAVIOR;
+
     static const char *INSERT_TIMESTAMP_FORMAT;
     
     static const char *SEARCH_WINDOW_X_POS;
diff --git a/src/preferencesdialog.cpp b/src/preferencesdialog.cpp
index 4079516..41979d7 100644
--- a/src/preferencesdialog.cpp
+++ b/src/preferencesdialog.cpp
@@ -78,6 +78,7 @@ namespace gnote {
     , syncAddinPrefsWidget(NULL)
     , resetSyncAddinButton(NULL)
     , saveSyncAddinButton(NULL)
+    , renameBehaviorCombo(NULL)
     , m_addin_manager(addinmanager)
   {
 //    set_icon(utils::get_icon("gnote"));
@@ -157,6 +158,10 @@ namespace gnote {
       add_action_widget (*button, Gtk::RESPONSE_CLOSE);
       set_default_response(Gtk::RESPONSE_CLOSE);
 
+    Preferences::obj().signal_setting_changed().connect(
+      sigc::mem_fun(
+        *this,
+        &PreferencesDialog::on_preferences_setting_changed));
   }
 
   void PreferencesDialog::enable_addin(bool enable)
@@ -250,22 +255,51 @@ namespace gnote {
 
       // Custom font...
 
+      Gtk::HBox * const font_box = manage(new Gtk::HBox(false, 0));
       check = manage(make_check_button (_("Use custom _font")));
-      options_list->pack_start (*check, false, false, 0);
+      font_box->pack_start(*check, Gtk::PACK_EXPAND_WIDGET, 0);
       font_peditor = new sharp::PropertyEditorBool(Preferences::ENABLE_CUSTOM_FONT, 
                                                      *check);
       font_peditor->setup();
 
-      align = manage(new Gtk::Alignment(0.5f, 0.5f, 0.4f, 1.0f));
-      align->show ();
-      options_list->pack_start (*align, false, false, 0);
-
       font_button = manage(make_font_button());
       font_button->set_sensitive(check->get_active());
-      align->add (*font_button);
+      font_box->pack_start(*font_button, Gtk::PACK_EXPAND_WIDGET, 0);
+      font_box->show_all();
+      options_list->pack_start(*font_box, false, false, 0);
 
       font_peditor->add_guard (font_button);
       
+      // Note renaming behavior
+      Gtk::HBox * const rename_behavior_box = manage(new Gtk::HBox(
+                                                           false, 0));
+      label = manage(make_label(_("When renaming a linked note: ")));
+      rename_behavior_box->pack_start(*label,
+                                      Gtk::PACK_EXPAND_WIDGET,
+                                      0);
+      renameBehaviorCombo = manage(new Gtk::ComboBoxText());
+      renameBehaviorCombo->append_text(_("Ask me what to do"));
+      renameBehaviorCombo->append_text(_("Never rename links"));
+      renameBehaviorCombo->append_text(_("Always rename links"));
+      Preferences & preferences = Preferences::obj();
+      int rename_behavior = preferences.get<int>(
+                              Preferences::NOTE_RENAME_BEHAVIOR);
+      if (0 > rename_behavior || 2 < rename_behavior) {
+        rename_behavior = 0;
+        preferences.set<int>(Preferences::NOTE_RENAME_BEHAVIOR,
+                             rename_behavior);
+      }
+      renameBehaviorCombo->set_active(rename_behavior);
+      renameBehaviorCombo->signal_changed().connect(
+        sigc::mem_fun(
+          *this,
+          &PreferencesDialog::on_rename_behavior_changed));
+      rename_behavior_box->pack_start(*renameBehaviorCombo,
+                                      Gtk::PACK_EXPAND_WIDGET,
+                                      0);
+      rename_behavior_box->show_all();
+      options_list->pack_start(*rename_behavior_box, false, false, 0);
+
       // New Note Template
       // Translators: This is 'New Note' Template, not New 'Note Template'
       label = manage(make_label (_("New Note Template")));
@@ -930,6 +964,38 @@ namespace gnote {
 
 
 
+  void  PreferencesDialog::on_preferences_setting_changed(
+                             Preferences * preferences,
+                             GConfEntry * entry)
+  {
+    const char * const key = gconf_entry_get_key(entry);
+
+    if (strcmp(key, Preferences::NOTE_RENAME_BEHAVIOR) == 0) {
+      const GConfValue * const value = gconf_entry_get_value(entry);
+      int rename_behavior = gconf_value_get_int(value);
+      if (0 > rename_behavior || 2 < rename_behavior) {
+        rename_behavior = 0;
+        preferences->set<int>(Preferences::NOTE_RENAME_BEHAVIOR,
+                              rename_behavior);
+      }
+      if (renameBehaviorCombo->get_active_row_number()
+            != rename_behavior) {
+        renameBehaviorCombo->set_active(rename_behavior);
+      }
+    }
+  }
+
+
+
+  void  PreferencesDialog::on_rename_behavior_changed()
+  {
+    Preferences::obj().set<int>(
+      Preferences::NOTE_RENAME_BEHAVIOR,
+      renameBehaviorCombo->get_active_row_number());
+  }
+
+
+
   AddinInfoDialog::AddinInfoDialog(const sharp::DynamicModule * module,
                                    Gtk::Dialog &parent)
     : Gtk::Dialog(module->name(), parent, false, true)
diff --git a/src/preferencesdialog.hpp b/src/preferencesdialog.hpp
index b13a7a4..a331843 100644
--- a/src/preferencesdialog.hpp
+++ b/src/preferencesdialog.hpp
@@ -30,6 +30,7 @@
 #include <gtkmm/treeiter.h>
 #include <gtkmm/liststore.h>
 #include <gtkmm/combobox.h>
+#include <gtkmm/comboboxtext.h>
 
 #include "sharp/addinstreemodel.hpp"
 
@@ -66,6 +67,10 @@ private:
   void on_reset_sync_addin_button();
   void on_save_sync_addin_button();
 
+  void on_preferences_setting_changed(Preferences * preferences,
+                                      GConfEntry * entry);
+  void on_rename_behavior_changed();
+
   sharp::DynamicModule * get_selected_addin();
   void on_addin_tree_selection_changed();
   void update_addin_buttons();
@@ -101,6 +106,7 @@ private:
   Gtk::Widget *syncAddinPrefsWidget;
   Gtk::Button *resetSyncAddinButton;
   Gtk::Button *saveSyncAddinButton;
+  Gtk::ComboBoxText *renameBehaviorCombo;
   AddinManager &m_addin_manager;
     
   Gtk::Button *font_button;
diff --git a/src/watchers.cpp b/src/watchers.cpp
index 5b3dca1..476d05b 100644
--- a/src/watchers.cpp
+++ b/src/watchers.cpp
@@ -229,7 +229,7 @@ namespace gnote {
     }
 
     DBG_OUT ("Renaming note from %s to %s", get_note()->get_title().c_str(), title.c_str());
-    get_note()->set_title(title);
+    get_note()->set_title(title, true);
     return true;
   }
 
@@ -734,30 +734,6 @@ namespace gnote {
     if (contains_text (renamed->get_title())) {
       highlight_note_in_block (renamed, get_buffer()->begin(), get_buffer()->end());
     }
-
-    if (!contains_text (old_title)) {
-      return;
-    }
-
-    std::string old_title_lower = sharp::string_to_lower(old_title);
-
-    // Replace existing links with the new title.
-    utils::TextTagEnumerator enumerator(get_buffer(), m_link_tag);
-    while(enumerator.move_next()) {
-      const utils::TextRange & range(enumerator.current());
-      if (sharp::string_to_lower(range.text()) != old_title_lower) {
-        continue;
-      }
-
-      DBG_OUT ("Replacing '%s' with '%s'",
-               range.text().c_str(), renamed->get_title().c_str());
-
-      Gtk::TextIter start_iter = range.start();
-      Gtk::TextIter end_iter = range.end();
-      end_iter = get_buffer()->erase (start_iter, end_iter);
-      start_iter = range.start();
-      get_buffer()->insert_with_tag(start_iter, renamed->get_title(), m_link_tag);
-    }
   }
 
   



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]