1 /*
2  * gnote
3  *
4  * Copyright (C) 2010-2013,2016-2017,2019-2021 Aurimas Cernius
5  * Copyright (C) 2009 Hubert Figuiere
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 
22 #include <string.h>
23 
24 #include <gtkmm/settings.h>
25 
26 #include "notebuffer.hpp"
27 #include "noteeditor.hpp"
28 #include "preferences.hpp"
29 #include "undo.hpp"
30 #include "utils.hpp"
31 #include "debug.hpp"
32 #include "sharp/string.hpp"
33 
34 namespace gnote {
35 
NoteEditor(const Glib::RefPtr<Gtk::TextBuffer> & buffer,Preferences & preferences)36   NoteEditor::NoteEditor(const Glib::RefPtr<Gtk::TextBuffer> & buffer, Preferences & preferences)
37     : Gtk::TextView(buffer)
38     , m_preferences(preferences)
39   {
40     set_wrap_mode(Gtk::WRAP_WORD);
41     set_left_margin(default_margin());
42     set_right_margin(default_margin());
43     property_can_default().set_value(true);
44 
45     m_preferences.signal_enable_custom_font_changed.connect(sigc::mem_fun(*this, &NoteEditor::update_custom_font_setting));
46     m_preferences.signal_custom_font_face_changed.connect(sigc::mem_fun(*this, &NoteEditor::update_custom_font_setting));
47 
48     // query all monitored settings to get change notifications
49     bool enable_custom_font = m_preferences.enable_custom_font();
50     auto font_string = m_preferences.custom_font_face();
51 
52     // Set Font from preference
53     if(enable_custom_font) {
54       modify_font_from_string(font_string);
55     }
56 
57     // Set extra editor drag targets supported (in addition
58     // to the default TextView's various text formats)...
59     Glib::RefPtr<Gtk::TargetList> list = drag_dest_get_target_list();
60 
61 
62     list->add ("text/uri-list", (Gtk::TargetFlags)0, 1);
63     list->add ("_NETSCAPE_URL", (Gtk::TargetFlags)0, 1);
64 
65     signal_key_press_event().connect(sigc::mem_fun(*this, &NoteEditor::key_pressed), false);
66     signal_button_press_event().connect(sigc::mem_fun(*this, &NoteEditor::button_pressed), false);
67 
68     g_signal_connect(gobj(), "paste-clipboard", G_CALLBACK(paste_started), this);
69     g_signal_connect_after(gobj(), "paste-clipboard", G_CALLBACK(paste_ended), this);
70   }
71 
72 
update_custom_font_setting()73   void NoteEditor::update_custom_font_setting()
74   {
75     if (m_preferences.enable_custom_font()) {
76       auto fontString = m_preferences.custom_font_face();
77       DBG_OUT( "Switching note font to '%s'...", fontString.c_str());
78       modify_font_from_string (fontString);
79     }
80     else {
81       DBG_OUT("Switching back to the default font");
82       Gtk::Settings::get_default()->reset_property("gtk-font-name");
83     }
84   }
85 
86 
modify_font_from_string(const Glib::ustring & fontString)87   void NoteEditor::modify_font_from_string (const Glib::ustring & fontString)
88   {
89     DBG_OUT("Switching note font to '%s'...", fontString.c_str());
90     Gtk::Settings::get_default()->property_gtk_font_name() = fontString;
91   }
92 
93 
94 
95     //
96     // DND Drop handling
97     //
on_drag_data_received(const Glib::RefPtr<Gdk::DragContext> & context,int x,int y,const Gtk::SelectionData & selection_data,guint info,guint time)98   void NoteEditor::on_drag_data_received(const Glib::RefPtr<Gdk::DragContext> & context,
99                                          int x, int y,
100                                          const Gtk::SelectionData & selection_data,
101                                          guint info,  guint time)
102   {
103     bool has_url = false;
104 
105     auto targets = context->list_targets();
106     for(auto target : targets) {
107       if (target == "text/uri-list" ||
108           target == "_NETSCAPE_URL") {
109         has_url = true;
110         break;
111       }
112     }
113 
114     if (has_url) {
115       utils::UriList uri_list(selection_data);
116       bool more_than_one = false;
117 
118       // Place the cursor in the position where the uri was
119       // dropped, adjusting x,y by the TextView's VisibleRect.
120       Gdk::Rectangle rect;
121       get_visible_rect(rect);
122       int adjustedX = x + rect.get_x();
123       int adjustedY = y + rect.get_y();
124       Gtk::TextIter cursor;
125       get_iter_at_location (cursor, adjustedX, adjustedY);
126       get_buffer()->place_cursor (cursor);
127 
128       Glib::RefPtr<Gtk::TextTag> link_tag = get_buffer()->get_tag_table()->lookup ("link:url");
129 
130       for(utils::UriList::const_iterator iter = uri_list.begin();
131           iter != uri_list.end(); ++iter) {
132         const sharp::Uri & uri(*iter);
133         DBG_OUT("Got Dropped URI: %s", uri.to_string().c_str());
134         Glib::ustring insert;
135         if (uri.is_file()) {
136           // URL-escape the path in case
137           // there are spaces (bug #303902)
138           insert = sharp::Uri::escape_uri_string(uri.local_path());
139         }
140         else {
141           insert = uri.to_string ();
142         }
143 
144         if (insert.empty() || sharp::string_trim(insert).empty())
145           continue;
146 
147         if (more_than_one) {
148           cursor = get_buffer()->get_iter_at_mark (get_buffer()->get_insert());
149 
150           // FIXME: The space here is a hack
151           // around a bug in the URL Regex which
152           // matches across newlines.
153           if (cursor.get_line_offset() == 0) {
154             get_buffer()->insert (cursor, " \n");
155           }
156           else {
157             get_buffer()->insert (cursor, ", ");
158           }
159         }
160 
161         get_buffer()->insert_with_tag(cursor, insert, link_tag);
162         more_than_one = true;
163       }
164 
165       context->drag_finish(more_than_one, false, time);
166     }
167     else {
168       Gtk::TextView::on_drag_data_received (context, x, y, selection_data, info, time);
169     }
170   }
171 
key_pressed(GdkEventKey * ev)172   bool NoteEditor::key_pressed(GdkEventKey *ev)
173   {
174       bool ret_value = false;
175       if(!get_editable()) {
176         return ret_value;
177       }
178 
179       guint keyval;
180       GdkModifierType state;
181       GdkEvent *event = (GdkEvent*)ev;
182       if(!gdk_event_get_keyval(event, &keyval) || !gdk_event_get_state(event, &state)) {
183         return false;
184       }
185       switch(keyval)
186       {
187       case GDK_KEY_KP_Enter:
188       case GDK_KEY_Return:
189         // Allow opening notes with Ctrl + Enter
190         if(state != GDK_CONTROL_MASK) {
191           if(state & Gdk::SHIFT_MASK) {
192             ret_value = NoteBuffer::Ptr::cast_static(get_buffer())->add_new_line (true);
193           }
194           else {
195             ret_value = NoteBuffer::Ptr::cast_static(get_buffer())->add_new_line (false);
196           }
197           scroll_to (get_buffer()->get_insert());
198         }
199         break;
200       case GDK_KEY_Tab:
201         ret_value = NoteBuffer::Ptr::cast_static(get_buffer())->add_tab ();
202         scroll_to (get_buffer()->get_insert());
203         break;
204       case GDK_KEY_ISO_Left_Tab:
205         ret_value = NoteBuffer::Ptr::cast_static(get_buffer())->remove_tab ();
206         scroll_to (get_buffer()->get_insert());
207         break;
208       case GDK_KEY_Delete:
209         if(Gdk::SHIFT_MASK != (state & Gdk::SHIFT_MASK)) {
210           ret_value = NoteBuffer::Ptr::cast_static(get_buffer())->delete_key_handler();
211           scroll_to (get_buffer()->get_insert());
212         }
213         break;
214       case GDK_KEY_BackSpace:
215         ret_value = NoteBuffer::Ptr::cast_static(get_buffer())->backspace_key_handler();
216         break;
217       case GDK_KEY_Left:
218       case GDK_KEY_Right:
219       case GDK_KEY_Up:
220       case GDK_KEY_Down:
221       case GDK_KEY_End:
222         ret_value = false;
223         break;
224       default:
225         NoteBuffer::Ptr::cast_static(get_buffer())->check_selection();
226         break;
227       }
228 
229       return ret_value;
230     }
231 
232 
button_pressed(GdkEventButton *)233   bool NoteEditor::button_pressed (GdkEventButton * )
234   {
235     NoteBuffer::Ptr::cast_static(get_buffer())->check_selection();
236     return false;
237   }
238 
paste_started(GtkTextView *,NoteEditor * _this)239   void NoteEditor::paste_started(GtkTextView*, NoteEditor *_this)
240   {
241     _this->on_paste_start();
242   }
243 
paste_ended(GtkTextView *,NoteEditor * _this)244   void NoteEditor::paste_ended(GtkTextView*, NoteEditor *_this)
245   {
246     _this->on_paste_end();
247   }
248 
on_paste_start()249   void NoteEditor::on_paste_start()
250   {
251     auto buffer = NoteBuffer::Ptr::cast_static(get_buffer());
252     buffer->undoer().add_undo_action(new EditActionGroup(true));
253   }
254 
on_paste_end()255   void NoteEditor::on_paste_end()
256   {
257     auto buffer = NoteBuffer::Ptr::cast_static(get_buffer());
258     buffer->undoer().add_undo_action(new EditActionGroup(false));
259   }
260 
261 
262 }
263