Commit 5a0bf562 authored by Aurimas Černius's avatar Aurimas Černius

Use separate builtin app plugin for link management on create/delete/rename

parent d2c9df11
/*
* gnote
*
* Copyright (C) 2010-2017,2019 Aurimas Cernius
* Copyright (C) 2010-2017,2019-2020 Aurimas Cernius
* Copyright (C) 2009, 2010 Debarshi Ray
* Copyright (C) 2009 Hubert Figuiere
*
......@@ -72,6 +72,31 @@ namespace gnote {
} \
} while(0)
#define SETUP_APP_ADDIN(key, KEY, klass) \
do { \
if(key == KEY) { \
Glib::RefPtr<Gio::Settings> settings = m_preferences \
.get_schema_settings(Preferences::SCHEMA_GNOTE); \
if(settings->get_boolean(key)) { \
auto iter = m_app_addins.find(typeid(klass).name()); \
if(iter != m_app_addins.end()) { \
iter->second->initialize(); \
} \
else { \
auto addin = klass::create(); \
m_app_addins.insert(std::make_pair(typeid(klass).name(), addin)); \
addin->initialize(m_gnote, m_note_manager); \
} \
} \
else { \
auto addin = m_app_addins.find(typeid(klass).name()); \
if(addin != m_app_addins.end()) { \
addin->second->shutdown(); \
} \
} \
} \
} while(0)
namespace {
template <typename AddinType>
Glib::ustring get_id_for_addin(const AbstractAddin & addin, const std::map<Glib::ustring, AddinType*> & addins)
......@@ -270,6 +295,7 @@ namespace {
REGISTER_BUILTIN_NOTE_ADDIN(NoteUrlWatcher);
}
if(settings->get_boolean(Preferences::ENABLE_AUTO_LINKS)) {
REGISTER_APP_ADDIN(AppLinkWatcher);
REGISTER_BUILTIN_NOTE_ADDIN(NoteLinkWatcher);
}
if(settings->get_boolean(Preferences::ENABLE_WIKIWORDS)) {
......@@ -573,6 +599,7 @@ namespace {
{
SETUP_NOTE_ADDIN(key, Preferences::ENABLE_URL_LINKS, NoteUrlWatcher);
SETUP_NOTE_ADDIN(key, Preferences::ENABLE_AUTO_LINKS, NoteLinkWatcher);
SETUP_APP_ADDIN(key, Preferences::ENABLE_AUTO_LINKS, AppLinkWatcher);
SETUP_NOTE_ADDIN(key, Preferences::ENABLE_WIKIWORDS, NoteWikiWatcher);
}
......
/*
* gnote
*
* Copyright (C) 2010-2015,2017,2019 Aurimas Cernius
* Copyright (C) 2010-2015,2017,2019-2020 Aurimas Cernius
* Copyright (C) 2010 Debarshi Ray
* Copyright (C) 2009 Hubert Figuiere
*
......@@ -756,217 +756,256 @@ namespace gnote {
////////////////////////////////////////////////////////////////////////
bool NoteLinkWatcher::s_text_event_connected = false;
NoteAddin * NoteLinkWatcher::create()
ApplicationAddin *AppLinkWatcher::create()
{
return new NoteLinkWatcher;
return new AppLinkWatcher;
}
void NoteLinkWatcher::initialize ()
AppLinkWatcher::AppLinkWatcher()
: m_initialized(false)
{
m_on_note_deleted_cid = manager().signal_note_deleted.connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_note_deleted));
m_on_note_added_cid = manager().signal_note_added.connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_note_added));
m_on_note_renamed_cid = manager().signal_note_renamed.connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_note_renamed));
m_link_tag = get_note()->get_tag_table()->get_link_tag();
m_broken_link_tag = get_note()->get_tag_table()->get_broken_link_tag();
}
void AppLinkWatcher::initialize()
{
if(m_initialized) {
return;
}
m_initialized = true;
m_on_note_deleted_cid = note_manager().signal_note_deleted.connect(
sigc::mem_fun(*this, &AppLinkWatcher::on_note_deleted));
m_on_note_added_cid = note_manager().signal_note_added.connect(
sigc::mem_fun(*this, &AppLinkWatcher::on_note_added));
m_on_note_renamed_cid = note_manager().signal_note_renamed.connect(
sigc::mem_fun(*this, &AppLinkWatcher::on_note_renamed));
}
void NoteLinkWatcher::shutdown ()
void AppLinkWatcher::shutdown()
{
m_initialized = false;
m_on_note_deleted_cid.disconnect();
m_on_note_added_cid.disconnect();
m_on_note_renamed_cid.disconnect();
}
bool AppLinkWatcher::initialized()
{
return m_initialized;
}
void NoteLinkWatcher::on_note_opened ()
void AppLinkWatcher::on_note_added(const NoteBase::Ptr & added)
{
// NOTE: This avoid multiple link opens
// now that notes always perform TagTable
// sharing. This is because if the TagTable is shared,
// we will connect to the same Tag's event source each
// time a note is opened, and get called multiple times
// for each button press. Fixes bug #305813.
if (!s_text_event_connected) {
m_link_tag->signal_activate().connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_link_tag_activated));
m_broken_link_tag->signal_activate().connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_link_tag_activated));
s_text_event_connected = true;
for(auto & note : note_manager().get_notes()) {
if(added == note) {
continue;
}
if(!contains_text(note, added->get_title())) {
continue;
}
// Highlight previously unlinked text
auto n = std::static_pointer_cast<Note>(note);
auto buffer = n->get_buffer();
highlight_in_block(note_manager(), n, buffer->begin(), buffer->end());
}
get_buffer()->signal_insert().connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_insert_text));
get_buffer()->signal_apply_tag().connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_apply_tag));
get_buffer()->signal_erase().connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_delete_range));
}
bool NoteLinkWatcher::contains_text(const Glib::ustring & text)
void AppLinkWatcher::on_note_deleted(const NoteBase::Ptr & deleted)
{
Glib::ustring body = get_note()->text_content().lowercase();
Glib::ustring match = text.lowercase();
auto link_tag = std::static_pointer_cast<Note>(deleted)->get_tag_table()->get_link_tag();
auto broken_link_tag = std::static_pointer_cast<Note>(deleted)->get_tag_table()->get_broken_link_tag();
return body.find(match) != Glib::ustring::npos;
}
for(auto & note : note_manager().get_notes()) {
if(deleted == note) {
continue;
}
if(!contains_text(note, deleted->get_title())) {
continue;
}
void NoteLinkWatcher::on_note_added(const NoteBase::Ptr & added)
{
if (added == get_note()) {
return;
}
Glib::ustring old_title_lower = deleted->get_title().lowercase();
auto buffer = std::static_pointer_cast<Note>(note)->get_buffer();
if (!contains_text (added->get_title())) {
return;
}
// Turn all link:internal to link:broken for the deleted note.
utils::TextTagEnumerator enumerator(buffer, link_tag);
while(enumerator.move_next()) {
const utils::TextRange & range(enumerator.current());
if(enumerator.current().text().lowercase() != old_title_lower)
continue;
// Highlight previously unlinked text
highlight_in_block (get_buffer()->begin(), get_buffer()->end());
buffer->remove_tag(link_tag, range.start(), range.end());
buffer->apply_tag(broken_link_tag, range.start(), range.end());
}
}
}
void NoteLinkWatcher::on_note_deleted(const NoteBase::Ptr & deleted)
void AppLinkWatcher::on_note_renamed(const NoteBase::Ptr & renamed, const Glib::ustring & /*old_title*/)
{
if (deleted == get_note()) {
return;
}
for(auto & note : note_manager().get_notes()) {
if(renamed == note) {
continue;
}
if (!contains_text (deleted->get_title())) {
return;
// Highlight previously unlinked text
if(contains_text(note, renamed->get_title())) {
auto n = std::static_pointer_cast<Note>(note);
auto buffer = n->get_buffer();
highlight_note_in_block(note_manager(), n, std::static_pointer_cast<Note>(renamed), buffer->begin(), buffer->end());
}
}
}
Glib::ustring old_title_lower = deleted->get_title().lowercase();
bool AppLinkWatcher::contains_text(const NoteBase::Ptr & note, const Glib::ustring & text)
{
Glib::ustring body = std::static_pointer_cast<Note>(note)->text_content().lowercase();
Glib::ustring match = text.lowercase();
// Turn all link:internal to link:broken for the deleted note.
utils::TextTagEnumerator enumerator(get_buffer(), m_link_tag);
while (enumerator.move_next()) {
const utils::TextRange & range(enumerator.current());
if (enumerator.current().text().lowercase() != old_title_lower)
continue;
return body.find(match) != Glib::ustring::npos;
}
get_buffer()->remove_tag (m_link_tag, range.start(), range.end());
get_buffer()->apply_tag (m_broken_link_tag, range.start(), range.end());
void AppLinkWatcher::highlight_in_block(NoteManagerBase & note_manager, const Note::Ptr & note, const Gtk::TextIter & start, const Gtk::TextIter & end)
{
TrieHit<NoteBase::WeakPtr>::ListPtr hits = note_manager.find_trie_matches(start.get_slice(end));
for(TrieHit<NoteBase::WeakPtr>::List::const_iterator iter = hits->begin(); iter != hits->end(); ++iter) {
do_highlight(note_manager, note, **iter, start, end);
}
}
void NoteLinkWatcher::on_note_renamed(const NoteBase::Ptr& renamed, const Glib::ustring& /*old_title*/)
void AppLinkWatcher::highlight_note_in_block(NoteManagerBase & note_manager, const Note::Ptr & note, const NoteBase::Ptr & find_note, const Gtk::TextIter & start, const Gtk::TextIter & end)
{
if (renamed == get_note()) {
return;
}
Glib::ustring buffer_text = start.get_text(end).lowercase();
Glib::ustring find_title_lower = find_note->get_title().lowercase();
int idx = 0;
// Highlight previously unlinked text
if (contains_text (renamed->get_title())) {
highlight_note_in_block(std::static_pointer_cast<Note>(renamed), get_buffer()->begin(), get_buffer()->end());
while (true) {
idx = buffer_text.find(find_title_lower, idx);
if (idx < 0)
break;
TrieHit<NoteBase::WeakPtr> hit(idx, idx + find_title_lower.length(), find_title_lower, find_note);
do_highlight(note_manager, note, hit, start, end);
idx += find_title_lower.length();
}
}
void NoteLinkWatcher::do_highlight(const TrieHit<NoteBase::WeakPtr> & hit,
const Gtk::TextIter & start,
const Gtk::TextIter &)
void AppLinkWatcher::do_highlight(NoteManagerBase & note_manager, const Note::Ptr & note, const TrieHit<NoteBase::WeakPtr> & hit, const Gtk::TextIter & start, const Gtk::TextIter &)
{
// Some of these checks should be replaced with fixes to
// TitleTrie.FindMatches, probably.
if (hit.value().expired()) {
if(hit.value().expired()) {
DBG_OUT("DoHighlight: null pointer error for '%s'." , hit.key().c_str());
return;
}
if (!manager().find(hit.key())) {
DBG_OUT ("DoHighlight: '%s' links to non-existing note." ,
hit.key().c_str());
if(!note_manager.find(hit.key())) {
DBG_OUT("DoHighlight: '%s' links to non-existing note." , hit.key().c_str());
return;
}
NoteBase::Ptr hit_note(hit.value());
if (hit.key().lowercase() != hit_note->get_title().lowercase()) { // == 0 if same string
DBG_OUT ("DoHighlight: '%s' links wrongly to note '%s'." ,
hit.key().c_str(),
hit_note->get_title().c_str());
if(hit.key().lowercase() != hit_note->get_title().lowercase()) { // == 0 if same string
DBG_OUT("DoHighlight: '%s' links wrongly to note '%s'." , hit.key().c_str(), hit_note->get_title().c_str());
return;
}
if (hit_note == get_note())
if(hit_note == note)
return;
Gtk::TextIter title_start = start;
title_start.forward_chars (hit.start());
title_start.forward_chars(hit.start());
Gtk::TextIter title_end = start;
title_end.forward_chars (hit.end());
title_end.forward_chars(hit.end());
// Only link against whole words/phrases
if ((!title_start.starts_word () && !title_start.starts_sentence ()) ||
if((!title_start.starts_word() && !title_start.starts_sentence()) ||
(!title_end.ends_word() && !title_end.ends_sentence())) {
return;
}
// Don't create links inside URLs
if(get_note()->get_tag_table()->has_link_tag(title_start)) {
if(note->get_tag_table()->has_link_tag(title_start)) {
return;
}
DBG_OUT ("Matching Note title '%s' at %d-%d...",
hit.key().c_str(), hit.start(), hit.end());
DBG_OUT("Matching Note title '%s' at %d-%d...", hit.key().c_str(), hit.start(), hit.end());
get_note()->get_tag_table()->foreach(
[this, title_start, title_end](const Glib::RefPtr<Gtk::TextTag> & tag) {
remove_link_tag(tag, title_start, title_end);
auto link_tag = note->get_tag_table()->get_link_tag();
note->get_tag_table()->foreach(
[note, title_start, title_end](const Glib::RefPtr<Gtk::TextTag> & tag) {
remove_link_tag(note, tag, title_start, title_end);
});
get_buffer()->apply_tag (m_link_tag, title_start, title_end);
note->get_buffer()->apply_tag(link_tag, title_start, title_end);
}
void NoteLinkWatcher::remove_link_tag(const Glib::RefPtr<Gtk::TextTag> & tag,
const Gtk::TextIter & start, const Gtk::TextIter & end)
void AppLinkWatcher::remove_link_tag(const Note::Ptr & note, const Glib::RefPtr<Gtk::TextTag> & tag, const Gtk::TextIter & start, const Gtk::TextIter & end)
{
NoteTag::Ptr note_tag = NoteTag::Ptr::cast_dynamic(tag);
if (note_tag && note_tag->can_activate()) {
get_buffer()->remove_tag(note_tag, start, end);
if(note_tag && note_tag->can_activate()) {
note->get_buffer()->remove_tag(note_tag, start, end);
}
}
void NoteLinkWatcher::highlight_note_in_block (const NoteBase::Ptr & find_note,
const Gtk::TextIter & start,
const Gtk::TextIter & end)
////////////////////////////////////////////////////////////////////////
bool NoteLinkWatcher::s_text_event_connected = false;
NoteAddin * NoteLinkWatcher::create()
{
Glib::ustring buffer_text = start.get_text(end).lowercase();
Glib::ustring find_title_lower = find_note->get_title().lowercase();
int idx = 0;
return new NoteLinkWatcher;
}
while (true) {
idx = buffer_text.find(find_title_lower, idx);
if (idx < 0)
break;
TrieHit<NoteBase::WeakPtr> hit(idx, idx + find_title_lower.length(),
find_title_lower, find_note);
do_highlight (hit, start, end);
void NoteLinkWatcher::initialize ()
{
m_link_tag = get_note()->get_tag_table()->get_link_tag();
m_broken_link_tag = get_note()->get_tag_table()->get_broken_link_tag();
}
idx += find_title_lower.length();
}
void NoteLinkWatcher::shutdown ()
{
}
void NoteLinkWatcher::on_note_opened ()
{
// NOTE: This avoid multiple link opens
// now that notes always perform TagTable
// sharing. This is because if the TagTable is shared,
// we will connect to the same Tag's event source each
// time a note is opened, and get called multiple times
// for each button press. Fixes bug #305813.
if (!s_text_event_connected) {
m_link_tag->signal_activate().connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_link_tag_activated));
m_broken_link_tag->signal_activate().connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_link_tag_activated));
s_text_event_connected = true;
}
get_buffer()->signal_insert().connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_insert_text));
get_buffer()->signal_apply_tag().connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_apply_tag));
get_buffer()->signal_erase().connect(
sigc::mem_fun(*this, &NoteLinkWatcher::on_delete_range));
}
void NoteLinkWatcher::do_highlight(const TrieHit<NoteBase::WeakPtr> & hit, const Gtk::TextIter & start, const Gtk::TextIter & end)
{
AppLinkWatcher::do_highlight(manager(), get_note(), hit, start, end);
}
void NoteLinkWatcher::highlight_in_block(const Gtk::TextIter & start,
const Gtk::TextIter & end)
{
TrieHit<NoteBase::WeakPtr>::ListPtr hits = manager().find_trie_matches (start.get_slice (end));
for(TrieHit<NoteBase::WeakPtr>::List::const_iterator iter = hits->begin();
iter != hits->end(); ++iter) {
do_highlight (**iter, start, end);
}
AppLinkWatcher::highlight_in_block(manager(), get_note(), start, end);
}
void NoteLinkWatcher::unhighlight_in_block(const Gtk::TextIter & start,
......
/*
* gnote
*
* Copyright (C) 2010-2015,2017,2019 Aurimas Cernius
* Copyright (C) 2010-2015,2017,2019-2020 Aurimas Cernius
* Copyright (C) 2009 Hubert Figuiere
*
* This program is free software: you can redistribute it and/or modify
......@@ -39,6 +39,7 @@ extern "C" {
#include <gtkmm/textiter.h>
#include <gtkmm/texttag.h>
#include "applicationaddin.hpp"
#include "noteaddin.hpp"
#include "triehit.hpp"
#include "utils.hpp"
......@@ -47,6 +48,7 @@ namespace gnote {
class Preferences;
class NoteEditor;
class NoteManagerBase;
class NoteTag;
class NoteRenameWatcher
......@@ -177,6 +179,33 @@ namespace gnote {
};
class AppLinkWatcher
: public ApplicationAddin
{
public:
static ApplicationAddin *create();
static void highlight_in_block(NoteManagerBase &, const Note::Ptr &, const Gtk::TextIter &, const Gtk::TextIter &);
static void do_highlight(NoteManagerBase &, const Note::Ptr &, const TrieHit<NoteBase::WeakPtr> &, const Gtk::TextIter & ,const Gtk::TextIter &);
static void remove_link_tag(const Note::Ptr & note, const Glib::RefPtr<Gtk::TextTag> & tag, const Gtk::TextIter & start, const Gtk::TextIter & end);
AppLinkWatcher();
virtual void initialize() override;
virtual void shutdown() override;
virtual bool initialized() override;
private:
static bool contains_text(const NoteBase::Ptr & note, const Glib::ustring & text);
static void highlight_note_in_block(NoteManagerBase &, const Note::Ptr &, const NoteBase::Ptr &, const Gtk::TextIter &, const Gtk::TextIter &);
void on_note_added(const NoteBase::Ptr &);
void on_note_deleted(const NoteBase::Ptr &);
void on_note_renamed(const NoteBase::Ptr&, const Glib::ustring&);
bool m_initialized;
sigc::connection m_on_note_deleted_cid;
sigc::connection m_on_note_added_cid;
sigc::connection m_on_note_renamed_cid;
};
class NoteLinkWatcher
: public NoteAddin
{
......@@ -187,21 +216,13 @@ namespace gnote {
virtual void on_note_opened() override;
private:
bool contains_text(const Glib::ustring & text);
void on_note_added(const NoteBase::Ptr &);
void on_note_deleted(const NoteBase::Ptr &);
void on_note_renamed(const NoteBase::Ptr&, const Glib::ustring&);
void do_highlight(const TrieHit<NoteBase::WeakPtr> & , const Gtk::TextIter &,const Gtk::TextIter &);
void highlight_note_in_block (const NoteBase::Ptr &, const Gtk::TextIter &,
const Gtk::TextIter &);
void highlight_in_block(const Gtk::TextIter &,const Gtk::TextIter &);
void unhighlight_in_block(const Gtk::TextIter &,const Gtk::TextIter &);
void on_delete_range(const Gtk::TextIter &,const Gtk::TextIter &);
void on_insert_text(const Gtk::TextIter &, const Glib::ustring &, int);
void on_apply_tag(const Glib::RefPtr<Gtk::TextBuffer::Tag> & tag,
const Gtk::TextIter & start, const Gtk::TextIter &end);
void remove_link_tag(const Glib::RefPtr<Gtk::TextTag> & tag,
const Gtk::TextIter & start, const Gtk::TextIter & end);
bool open_or_create_link(const NoteEditor &, const Gtk::TextIter &,const Gtk::TextIter &);
bool on_link_tag_activated(const NoteEditor &,
......@@ -210,9 +231,6 @@ namespace gnote {
NoteTag::Ptr m_link_tag;
NoteTag::Ptr m_broken_link_tag;
sigc::connection m_on_note_deleted_cid;
sigc::connection m_on_note_added_cid;
sigc::connection m_on_note_renamed_cid;
static bool s_text_event_connected;
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment