1 /*
2  * gnote
3  *
4  * Copyright (C) 2010-2021 Aurimas Cernius
5  * Copyright (C) 2010 Debarshi Ray
6  * Copyright (C) 2009 Hubert Figuiere
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <glibmm/i18n.h>
27 #include <gtkmm/alignment.h>
28 #include <gtkmm/headerbar.h>
29 #include <gtkmm/image.h>
30 #include <gtkmm/separator.h>
31 #include <gtkmm/separatormenuitem.h>
32 #include <gtkmm/stock.h>
33 
34 #include "debug.hpp"
35 #include "iactionmanager.hpp"
36 #include "iconmanager.hpp"
37 #include "ignote.hpp"
38 #include "note.hpp"
39 #include "notemanager.hpp"
40 #include "notewindow.hpp"
41 #include "recentchanges.hpp"
42 #include "sharp/string.hpp"
43 
44 namespace gnote {
45 
46   namespace {
47     const char *MAIN_MENU_PRIMARY_ICON = "open-menu-symbolic";
48     const char *MAIN_MENU_SECONDARY_ICON = "view-more-symbolic";
49   }
50 
NoteRecentChanges(IGnote & g,NoteManagerBase & m)51   NoteRecentChanges::NoteRecentChanges(IGnote & g, NoteManagerBase & m)
52     : MainWindow(_("Gnote"))
53     , m_gnote(g)
54     , m_note_manager(m)
55     , m_preferences(g.preferences())
56     , m_search_box(nullptr)
57     , m_find_next_prev_box(nullptr)
58     , m_search_entry(nullptr)
59     , m_embedded_widget(nullptr)
60     , m_mapped(false)
61     , m_entry_changed_timeout(NULL)
62     , m_window_menu_embedded(NULL)
63     , m_window_menu_default(NULL)
64     , m_accel_group(Gtk::AccelGroup::create())
65     , m_keybinder(m_accel_group)
66   {
67     add_accel_group(m_accel_group);
68     set_default_size(450,400);
69     set_resizable(true);
70     if(g.preferences().main_window_maximized()) {
71       maximize();
72     }
73 
74     set_icon_name(IconManager::GNOTE);
75 
76     m_search_notes_widget = new SearchNotesWidget(g, m);
77     m_search_notes_widget->signal_open_note
78       .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_open_note));
79     m_search_notes_widget->signal_open_note_new_window
80       .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_open_note_new_window));
81     m_search_notes_widget->notes_widget().signal_key_press_event()
82       .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_notes_widget_key_press));
83 
84     make_header_bar();
85     auto content = manage(new Gtk::Grid);
86     content->set_orientation(Gtk::ORIENTATION_VERTICAL);
87     int content_y_attach = 0;
88     if(use_client_side_decorations(m_preferences)) {
89       set_titlebar(*static_cast<Gtk::HeaderBar*>(m_header_bar));
90     }
91     else {
92       content->attach(*m_header_bar, 0, content_y_attach++, 1, 1);
93     }
94     content->attach(m_embed_box, 0, content_y_attach++, 1, 1);
95     m_embed_box.set_hexpand(true);
96     m_embed_box.set_vexpand(true);
97     m_embed_box.show();
98     content->show();
99 
100     add(*content);
101     signal_delete_event().connect(sigc::mem_fun(*this, &NoteRecentChanges::on_delete));
102     signal_key_press_event()
103       .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_key_pressed));
104     g.signal_quit
105       .connect(sigc::mem_fun(*this, &NoteRecentChanges::close_window));// to save size/pos
106     m_keybinder.add_accelerator(sigc::mem_fun(*this, &NoteRecentChanges::close_window),
107                                 GDK_KEY_W, Gdk::CONTROL_MASK, (Gtk::AccelFlags)0);
108     m_keybinder.add_accelerator(sigc::mem_fun(*this, &NoteRecentChanges::close_window),
109                                 GDK_KEY_Q, Gdk::CONTROL_MASK, (Gtk::AccelFlags)0);
110 
111     std::map<Glib::ustring, const Glib::VariantType*> actions = g.action_manager().get_main_window_actions();
112     for(std::map<Glib::ustring, const Glib::VariantType*>::iterator iter = actions.begin();
113         iter != actions.end(); ++iter) {
114       MainWindowAction::Ptr action;
115       if(iter->second == NULL) {
116         add_action(action = MainWindowAction::create(iter->first));
117       }
118       else if(iter->second == &Glib::Variant<bool>::variant_type()) {
119         add_action(action = MainWindowAction::create(iter->first, false));
120       }
121       else if(iter->second == &Glib::Variant<gint32>::variant_type()) {
122         add_action(action = MainWindowAction::create(iter->first, 0));
123       }
124       else if(iter->second == &Glib::Variant<Glib::ustring>::variant_type()) {
125         add_action(action = MainWindowAction::create(iter->first, Glib::ustring("")));
126       }
127       if(action) {
128         action->is_modifying(g.action_manager().is_modifying_main_window_action(iter->first));
129       }
130     }
131     find_action("close-window")->signal_activate()
132       .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_close_window));
133 
134     embed_widget(*m_search_notes_widget);
135   }
136 
137 
~NoteRecentChanges()138   NoteRecentChanges::~NoteRecentChanges()
139   {
140     if(m_entry_changed_timeout) {
141       delete m_entry_changed_timeout;
142     }
143     if(!m_search_box && m_search_text) {
144       delete m_search_text;
145     }
146     delete m_search_notes_widget;
147   }
148 
make_header_bar()149   void NoteRecentChanges::make_header_bar()
150   {
151     Gtk::Grid *left_box = manage(new Gtk::Grid);
152     left_box->get_style_context()->add_class(GTK_STYLE_CLASS_RAISED);
153     left_box->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
154     left_box->set_valign(Gtk::ALIGN_CENTER);
155     m_all_notes_button = manage(new Gtk::Button);
156     Gtk::Image *image = manage(new Gtk::Image);
157     image->property_icon_name() = "go-previous-symbolic";
158     image->property_icon_size() = GTK_ICON_SIZE_MENU;
159     m_all_notes_button->set_image(*image);
160     m_all_notes_button->set_tooltip_text(_("All Notes"));
161     m_all_notes_button->signal_clicked().connect(sigc::mem_fun(*this, &NoteRecentChanges::on_all_notes_button_clicked));
162     m_all_notes_button->add_accelerator("activate", m_accel_group, GDK_KEY_comma, Gdk::CONTROL_MASK, (Gtk::AccelFlags) 0);
163     m_all_notes_button->show_all();
164     left_box->attach(*m_all_notes_button, 0, 0, 1, 1);
165 
166     m_new_note_button = manage(new Gtk::Button);
167     image = manage(new Gtk::Image);
168     image->property_icon_name() = "list-add-symbolic";
169     image->property_icon_size() = GTK_ICON_SIZE_MENU;
170     m_new_note_button->set_image(*image);
171     m_new_note_button->set_tooltip_text(_("Create New Note"));
172     m_new_note_button->add_accelerator("activate", m_accel_group, GDK_KEY_N, Gdk::CONTROL_MASK, (Gtk::AccelFlags) 0);
173     m_new_note_button->signal_clicked().connect(sigc::mem_fun(*m_search_notes_widget, &SearchNotesWidget::new_note));
174     m_new_note_button->show_all();
175     left_box->attach(*m_new_note_button, 1, 0, 1, 1);
176     left_box->show();
177 
178     m_embedded_toolbar.set_margin_start(6);
179     m_embedded_toolbar.set_halign(Gtk::ALIGN_START);
180     m_embedded_toolbar.set_valign(Gtk::ALIGN_CENTER);
181     m_embedded_toolbar.show();
182 
183     Gtk::Grid *right_box = manage(new Gtk::Grid);
184     right_box->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
185     right_box->set_column_spacing(5);
186     right_box->set_valign(Gtk::ALIGN_CENTER);
187     image = manage(new Gtk::Image);
188     image->property_icon_name() = "edit-find-symbolic";
189     image->property_icon_size() = GTK_ICON_SIZE_MENU;
190     m_search_button.set_image(*image);
191     m_search_button.signal_toggled().connect(sigc::mem_fun(*this, &NoteRecentChanges::on_search_button_toggled));
192     m_search_button.add_accelerator("activate", m_accel_group, GDK_KEY_F, Gdk::CONTROL_MASK, (Gtk::AccelFlags) 0);
193     m_search_button.set_tooltip_text(_("Search"));
194     m_search_button.show_all();
195     right_box->attach(m_search_button, 0, 0, 1, 1);
196 
197     m_window_actions_button = manage(new Gtk::Button);
198     image = manage(new Gtk::Image);
199     image->property_icon_name() = MAIN_MENU_PRIMARY_ICON;
200     image->property_icon_size() = GTK_ICON_SIZE_MENU;
201     m_window_actions_button->set_image(*image);
202     m_window_actions_button->signal_clicked().connect(
203       sigc::mem_fun(*this, &NoteRecentChanges::on_show_window_menu));
204     m_window_actions_button->add_accelerator(
205       "activate", m_accel_group, GDK_KEY_F10, (Gdk::ModifierType) 0, (Gtk::AccelFlags) 0);
206     m_window_actions_button->show_all();
207     right_box->attach(*m_window_actions_button, 1, 0, 1, 1);
208     right_box->show();
209 
210     if(use_client_side_decorations(m_preferences)) {
211       Gtk::HeaderBar *header_bar = manage(new Gtk::HeaderBar);
212       header_bar->set_show_close_button(true);
213       header_bar->pack_start(*left_box);
214       header_bar->pack_end(*right_box);
215       header_bar->pack_end(m_embedded_toolbar);
216       m_header_bar = header_bar;
217     }
218     else {
219       Gtk::Grid *header_bar = manage(new Gtk::Grid);
220       header_bar->set_margin_start(5);
221       header_bar->set_margin_end(5);
222       header_bar->set_margin_top(5);
223       header_bar->set_margin_bottom(5);
224       header_bar->attach(*left_box, 0, 0, 1, 1);
225       left_box->set_hexpand(true);
226       header_bar->attach(m_embedded_toolbar, 2, 0, 1, 1);
227       header_bar->attach(*right_box, 3, 0, 1, 1);
228       m_header_bar = header_bar;
229     }
230 
231     m_header_bar->show();
232   }
233 
make_search_box()234   void NoteRecentChanges::make_search_box()
235   {
236     if(m_search_box) {
237       return;
238     }
239 
240     Glib::ustring search_text;
241     if(m_search_text) {
242       search_text = *m_search_text;
243       delete m_search_text;
244     }
245     m_search_entry = manage(new Gtk::SearchEntry);
246     m_search_entry->set_text(search_text);
247     m_search_entry->set_activates_default(false);
248     m_search_entry->set_size_request(300);
249     m_search_entry->signal_key_press_event()
250       .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_entry_key_pressed), false);
251     m_search_entry->signal_changed()
252       .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_entry_changed));
253     m_search_entry->signal_activate()
254       .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_entry_activated));
255     m_search_entry->show();
256 
257     m_search_box = manage(new Gtk::Grid);
258     m_search_box->set_hexpand(false);
259     m_search_box->attach(*m_search_entry, 0, 0, 1, 1);
260     m_search_box->set_halign(Gtk::ALIGN_CENTER);
261 
262     auto content = dynamic_cast<Gtk::Grid*>(m_embed_box.get_parent());
263     if(content) {
264       content->attach_next_to(*m_search_box, m_embed_box, Gtk::POS_TOP);
265     }
266     else {
267       ERR_OUT(_("Parent of embed box is not a Gtk::Grid, please report a bug!"));
268     }
269   }
270 
make_find_next_prev()271   void NoteRecentChanges::make_find_next_prev()
272   {
273     if(m_find_next_prev_box) {
274       return;
275     }
276 
277     m_find_next_prev_box = manage(new Gtk::Grid);
278     m_find_next_prev_box->set_margin_start(5);
279 
280     Gtk::Button *find_next_button = manage(new Gtk::Button);
281     Gtk::Image *image = manage(new Gtk::Image);
282     image->property_icon_name() = "go-down-symbolic";
283     image->property_icon_size() = GTK_ICON_SIZE_MENU;
284     find_next_button->set_image(*image);
285     find_next_button->set_always_show_image(true);
286     find_next_button->signal_clicked()
287       .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_find_next_button_clicked));
288     find_next_button->add_accelerator("activate", m_accel_group, GDK_KEY_G, Gdk::CONTROL_MASK, (Gtk::AccelFlags) 0);
289     find_next_button->show();
290     m_find_next_prev_box->attach(*find_next_button, 0, 0, 1, 1);
291 
292     Gtk::Button *find_prev_button = manage(new Gtk::Button);
293     image = manage(new Gtk::Image);
294     image->property_icon_name() = "go-up-symbolic";
295     image->property_icon_size() = GTK_ICON_SIZE_MENU;
296     find_prev_button->set_image(*image);
297     find_prev_button->set_always_show_image(true);
298     find_prev_button->signal_clicked()
299       .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_find_prev_button_clicked));
300     find_prev_button->add_accelerator("activate", m_accel_group, GDK_KEY_G, Gdk::CONTROL_MASK|Gdk::SHIFT_MASK, (Gtk::AccelFlags) 0);
301     find_prev_button->show();
302     m_find_next_prev_box->attach(*find_prev_button, 1, 0, 1, 1);
303 
304     auto grid = dynamic_cast<Gtk::Grid*>(m_search_entry->get_parent());
305     if(grid) {
306       grid->attach(*m_find_next_prev_box, 1, 0, 1, 1);
307     }
308     else {
309       ERR_OUT(_("Parent of search entry is not Gtk::Grid, please report a bug!"));
310     }
311   }
312 
on_search_button_toggled()313   void NoteRecentChanges::on_search_button_toggled()
314   {
315     if(m_search_button.get_active()) {
316       show_search_bar();
317     }
318     else {
319       m_search_box->hide();
320       SearchableItem *searchable_widget = dynamic_cast<SearchableItem*>(currently_foreground());
321       if(searchable_widget) {
322         searchable_widget->perform_search("");
323       }
324     }
325   }
326 
on_find_next_button_clicked()327   void NoteRecentChanges::on_find_next_button_clicked()
328   {
329     SearchableItem *searchable_widget = dynamic_cast<SearchableItem*>(currently_foreground());
330     if(searchable_widget) {
331       searchable_widget->goto_next_result();
332     }
333   }
334 
on_find_prev_button_clicked()335   void NoteRecentChanges::on_find_prev_button_clicked()
336   {
337     SearchableItem *searchable_widget = dynamic_cast<SearchableItem*>(currently_foreground());
338     if(searchable_widget) {
339       searchable_widget->goto_previous_result();
340     }
341   }
342 
show_search_bar(bool focus)343   void NoteRecentChanges::show_search_bar(bool focus)
344   {
345     make_search_box();
346     if(m_search_box->get_visible()) {
347       focus = false;
348     }
349     m_search_box->show();
350     if(focus) {
351       m_search_entry->grab_focus();
352     }
353     Glib::ustring text = m_search_entry->get_text();
354     update_search_bar(*currently_foreground(), text != "");
355   }
356 
update_search_bar(EmbeddableWidget & widget,bool perform_search)357   void NoteRecentChanges::update_search_bar(EmbeddableWidget & widget, bool perform_search)
358   {
359     SearchableItem *searchable_item = dynamic_cast<SearchableItem*>(&widget);
360     if(searchable_item) {
361       m_search_button.show();
362       if(searchable_item->supports_goto_result()) {
363         if(m_search_box && m_search_box->get_visible()) {
364           make_find_next_prev();
365           m_find_next_prev_box->show();
366         }
367       }
368       else {
369         if(m_find_next_prev_box) {
370           m_find_next_prev_box->hide();
371         }
372       }
373       if(perform_search) {
374         searchable_item->perform_search(m_search_button.get_active() ? m_search_entry->get_text() : "");
375       }
376     }
377     else {
378       m_search_button.set_active(false);
379       m_search_button.hide();
380     }
381   }
382 
present_search()383   void NoteRecentChanges::present_search()
384   {
385     EmbeddableWidget *current = currently_foreground();
386     if(m_search_notes_widget == dynamic_cast<SearchNotesWidget*>(current)) {
387       return;
388     }
389     if(current) {
390       background_embedded(*current);
391     }
392     foreground_embedded(*m_search_notes_widget);
393   }
394 
present_note(const Note::Ptr & note)395   void NoteRecentChanges::present_note(const Note::Ptr & note)
396   {
397     embed_widget(*note->create_window());
398   }
399 
400 
new_note()401   void NoteRecentChanges::new_note()
402   {
403     std::vector<Gtk::Widget*> current = m_embed_box.get_children();
404     SearchNotesWidget *search_wgt = dynamic_cast<SearchNotesWidget*>(current.size() > 0 ? current[0] : NULL);
405     if(search_wgt) {
406       search_wgt->new_note();
407     }
408     else {
409       present_note(std::static_pointer_cast<Note>(m_note_manager.create()));
410     }
411   }
412 
413 
on_open_note(const Note::Ptr & note)414   void NoteRecentChanges::on_open_note(const Note::Ptr & note)
415   {
416     if(m_preferences.open_notes_in_new_window()) {
417       on_open_note_new_window(note);
418     }
419     else {
420       if(!present_active(note)) {
421         present_note(note);
422       }
423     }
424   }
425 
on_open_note_new_window(const Note::Ptr & note)426   void NoteRecentChanges::on_open_note_new_window(const Note::Ptr & note)
427   {
428     present_in_new_window(m_gnote, note, m_preferences.enable_close_note_on_escape());
429   }
430 
on_delete_note()431   void NoteRecentChanges::on_delete_note()
432   {
433     m_search_notes_widget->delete_selected_notes();
434   }
435 
436 
437 
close_window()438   void NoteRecentChanges::close_window()
439   {
440     Glib::RefPtr<Gdk::Window> win = get_window();
441     // background window (for tray to work) might not have GDK window
442     if(win) {
443       m_preferences.main_window_maximized(win->get_state() & Gdk::WINDOW_STATE_MAXIMIZED);
444     }
445     std::vector<Gtk::Widget*> current = m_embed_box.get_children();
446     for(std::vector<Gtk::Widget*>::iterator iter = current.begin();
447         iter != current.end(); ++iter) {
448       EmbeddableWidget *widget = dynamic_cast<EmbeddableWidget*>(*iter);
449       if(widget) {
450         background_embedded(*widget);
451       }
452     }
453     if(m_embedded_widget) {
454       m_embedded_widget->unembed();
455       m_embedded_widget = nullptr;
456     }
457 
458     hide();
459   }
460 
461 
is_search()462   bool NoteRecentChanges::is_search()
463   {
464     return m_search_notes_widget == currently_foreground();
465   }
466 
467 
on_close_window(const Glib::VariantBase &)468   void NoteRecentChanges::on_close_window(const Glib::VariantBase&)
469   {
470     close_window();
471   }
472 
473 
on_delete(GdkEventAny *)474   bool NoteRecentChanges::on_delete(GdkEventAny *)
475   {
476     close_window();
477     return true;
478   }
479 
on_key_pressed(GdkEventKey * ev)480   bool NoteRecentChanges::on_key_pressed(GdkEventKey *ev)
481   {
482     guint keyval;
483     gdk_event_get_keyval((GdkEvent*)ev, &keyval);
484     switch(keyval) {
485     case GDK_KEY_Escape:
486       if(m_search_button.get_active()) {
487         m_search_entry->set_text("");
488         m_search_button.set_active(false);
489       }
490       // Allow Escape to close the window
491       else if(close_on_escape()) {
492         close_window();
493       }
494       else if(m_preferences.enable_close_note_on_escape()) {
495         EmbeddableWidget *current_item = currently_foreground();
496         if(current_item) {
497           background_embedded(*current_item);
498         }
499         foreground_embedded(*m_search_notes_widget);
500       }
501       break;
502     case GDK_KEY_F1:
503       utils::show_help("gnote", "", *this);
504       break;
505     default:
506       break;
507     }
508     return true;
509   }
510 
511 
on_show()512   void NoteRecentChanges::on_show()
513   {
514     // Select "All Notes" in the notebooks list
515     m_search_notes_widget->select_all_notes_notebook();
516 
517     EmbeddableWidget *widget = m_embedded_widget ? m_embedded_widget : m_search_notes_widget;
518     foreground_embedded(*widget);
519 
520     MainWindow::on_show();
521 
522     if(widget) {
523       int x = 0, y = 0;
524       widget->hint_position(x, y);
525       if(x && y) {
526         move(x, y);
527       }
528     }
529   }
530 
set_search_text(const Glib::ustring & value)531   void NoteRecentChanges::set_search_text(const Glib::ustring & value)
532   {
533     if(m_search_box) {
534       m_search_entry->set_text(value);
535     }
536     else {
537       if(!m_search_text) {
538         m_search_text = new Glib::ustring(value);
539       }
540       else {
541         *m_search_text = value;
542       }
543     }
544   }
545 
embed_widget(EmbeddableWidget & widget)546   void NoteRecentChanges::embed_widget(EmbeddableWidget & widget)
547   {
548     EmbeddableWidget *current = currently_foreground();
549     if(current == &widget) {
550       return;
551     }
552     if(m_embedded_widget) {
553       unembed_widget(*m_embedded_widget);
554     }
555     m_embedded_widget = &widget;
556     widget.embed(this);
557    if(get_visible()) {
558       foreground_embedded(widget);
559     }
560   }
561 
unembed_widget(EmbeddableWidget & widget)562   void NoteRecentChanges::unembed_widget(EmbeddableWidget & widget)
563   {
564     bool show_search = false;
565     if(&widget == m_embedded_widget) {
566       if(is_foreground(widget)) {
567         background_embedded(widget);
568         show_search = true;
569       }
570       m_embedded_widget = nullptr;
571       widget.unembed();
572     }
573     if(show_search) {
574       foreground_embedded(*m_search_notes_widget);
575     }
576   }
577 
foreground_embedded(EmbeddableWidget & widget)578   void NoteRecentChanges::foreground_embedded(EmbeddableWidget & widget)
579   {
580     try {
581       EmbeddableWidget *current_foreground = currently_foreground();
582       if(current_foreground == &widget) {
583         return;
584       }
585       else if(current_foreground) {
586         background_embedded(*current_foreground);
587       }
588       Gtk::Widget &wid = dynamic_cast<Gtk::Widget&>(widget);
589       m_embed_box.add(wid);
590       wid.show();
591       widget.foreground();
592 
593       bool maximized = m_preferences.main_window_maximized();
594       if(get_realized()) {
595         //if window is showing, use actual state
596         maximized = get_window()->get_state() & Gdk::WINDOW_STATE_MAXIMIZED;
597       }
598       int width = 0, height = 0;
599       widget.hint_size(width, height);
600       if(width && height) {
601         set_default_size(width, height);
602         if(!maximized && get_visible()) {
603           resize(width, height);
604         }
605       }
606       widget.size_internals();
607 
608       update_toolbar(widget);
609       if(&widget == m_search_notes_widget) {
610         set_title(_("Gnote"));
611       }
612       else {
613         set_title(widget.get_name());
614         m_current_embedded_name_slot = widget.signal_name_changed
615           .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_embedded_name_changed));
616       }
617     }
618     catch(std::bad_cast&) {
619     }
620 
621     try {
622       HasActions &has_actions = dynamic_cast<HasActions&>(widget);
623       if(m_window_menu_embedded) {
624         m_window_menu_embedded = NULL;
625       }
626       m_signal_popover_widgets_changed_cid = has_actions.signal_popover_widgets_changed
627         .connect(sigc::mem_fun(*this, &NoteRecentChanges::on_popover_widgets_changed));
628     }
629     catch(std::bad_cast&) {
630     }
631   }
632 
background_embedded(EmbeddableWidget & widget)633   void NoteRecentChanges::background_embedded(EmbeddableWidget & widget)
634   {
635     try {
636       if(currently_foreground() != &widget) {
637         return;
638       }
639       Gtk::Widget &wid = dynamic_cast<Gtk::Widget&>(widget);
640       widget.background();
641       m_embed_box.remove(wid);
642       m_signal_popover_widgets_changed_cid.disconnect();
643       m_current_embedded_name_slot.disconnect();
644 
645       if(m_window_menu_embedded) {
646         m_window_menu_embedded = NULL;
647       }
648     }
649     catch(std::bad_cast&) {
650     }
651 
652     auto children = m_embedded_toolbar.get_children();
653     for(auto child : children) {
654       m_embedded_toolbar.remove(*child);
655     }
656   }
657 
contains(EmbeddableWidget & widget)658   bool NoteRecentChanges::contains(EmbeddableWidget & widget)
659   {
660     if(&widget == m_search_notes_widget) {
661       return true;
662     }
663 
664     return &widget == m_embedded_widget;
665   }
666 
is_foreground(EmbeddableWidget & widget)667   bool NoteRecentChanges::is_foreground(EmbeddableWidget & widget)
668   {
669     for(Gtk::Widget *wgt : m_embed_box.get_children()) {
670       if(dynamic_cast<EmbeddableWidget*>(wgt) == &widget) {
671         return true;
672       }
673     }
674 
675     return false;
676   }
677 
add_action(const MainWindowAction::Ptr & action)678   void NoteRecentChanges::add_action(const MainWindowAction::Ptr & action)
679   {
680     m_actions[action->get_name()] = action;
681     MainWindow::add_action(action);
682   }
683 
find_action(const Glib::ustring & name)684   MainWindowAction::Ptr NoteRecentChanges::find_action(const Glib::ustring & name)
685   {
686     std::map<Glib::ustring, MainWindowAction::Ptr>::iterator iter = m_actions.find(name);
687     if(iter != m_actions.end()) {
688       return iter->second;
689     }
690     return MainWindowAction::Ptr();
691   }
692 
enabled(bool is_enabled)693   void NoteRecentChanges::enabled(bool is_enabled)
694   {
695     for(auto & iter : m_actions) {
696       if(iter.second->is_modifying()) {
697         iter.second->set_enabled(is_enabled);
698       }
699     }
700   }
701 
currently_foreground()702   EmbeddableWidget *NoteRecentChanges::currently_foreground()
703   {
704     std::vector<Gtk::Widget*> children = m_embed_box.get_children();
705     return children.size() ? dynamic_cast<EmbeddableWidget*>(children[0]) : NULL;
706   }
707 
on_map_event(GdkEventAny * evt)708   bool NoteRecentChanges::on_map_event(GdkEventAny *evt)
709   {
710     bool res = MainWindow::on_map_event(evt);
711     if(!m_mapped) {
712       auto widget = currently_foreground();
713       if(widget) {
714         widget->set_initial_focus();
715       }
716     }
717     m_mapped = true;
718     return res;
719   }
720 
on_entry_key_pressed(GdkEventKey * ev)721   bool NoteRecentChanges::on_entry_key_pressed(GdkEventKey *ev)
722   {
723     guint keyval;
724     gdk_event_get_keyval((GdkEvent*)ev, &keyval);
725     switch(keyval) {
726     case GDK_KEY_Escape:
727       m_search_entry->set_text("");
728       m_search_button.set_active(false);
729     }
730 
731     return false;
732   }
733 
on_entry_changed()734   void NoteRecentChanges::on_entry_changed()
735   {
736     if(!m_search_box || !m_search_box->get_visible()) {
737       return;
738     }
739     if(m_entry_changed_timeout == NULL) {
740       m_entry_changed_timeout = new utils::InterruptableTimeout();
741       m_entry_changed_timeout->signal_timeout
742         .connect(sigc::mem_fun(*this, &NoteRecentChanges::entry_changed_timeout));
743     }
744 
745     Glib::ustring search_text = get_search_text();
746     if(search_text.empty()) {
747       SearchableItem *searchable_widget = dynamic_cast<SearchableItem*>(currently_foreground());
748       if(searchable_widget) {
749         searchable_widget->perform_search(search_text);
750       }
751     }
752     else {
753       m_entry_changed_timeout->reset(500);
754     }
755   }
756 
on_entry_activated()757   void NoteRecentChanges::on_entry_activated()
758   {
759     if(m_entry_changed_timeout) {
760       m_entry_changed_timeout->cancel();
761     }
762 
763     entry_changed_timeout();
764   }
765 
entry_changed_timeout()766   void NoteRecentChanges::entry_changed_timeout()
767   {
768     if(!m_search_box || !m_search_box->get_visible()) {
769       return;
770     }
771     Glib::ustring search_text = get_search_text();
772     if(search_text.empty()) {
773       return;
774     }
775 
776     SearchableItem *searchable_widget = dynamic_cast<SearchableItem*>(currently_foreground());
777     if(searchable_widget) {
778       searchable_widget->perform_search(search_text);
779     }
780   }
781 
get_search_text()782   Glib::ustring NoteRecentChanges::get_search_text()
783   {
784     Glib::ustring text;
785     if(m_search_box) {
786       text = m_search_entry->get_text();
787     }
788     else if(m_search_text) {
789       text = *m_search_text;
790     }
791     text = sharp::string_trim(text);
792     return text;
793   }
794 
update_toolbar(EmbeddableWidget & widget)795   void NoteRecentChanges::update_toolbar(EmbeddableWidget & widget)
796   {
797     bool search = dynamic_cast<SearchNotesWidget*>(&widget) == m_search_notes_widget;
798     m_all_notes_button->set_visible(!search);
799     m_new_note_button->set_visible(search);
800     dynamic_cast<Gtk::Image*>(m_window_actions_button->get_image())->property_icon_name() = search ? MAIN_MENU_PRIMARY_ICON : MAIN_MENU_SECONDARY_ICON;
801     update_search_bar(widget, true);
802 
803     try {
804       HasEmbeddableToolbar & toolbar_provider = dynamic_cast<HasEmbeddableToolbar&>(widget);
805       Gtk::Widget *tool_item = toolbar_provider.embeddable_toolbar();
806       if(tool_item) {
807         m_embedded_toolbar.add(*tool_item);
808       }
809     }
810     catch(std::bad_cast &) {
811     }
812   }
813 
on_all_notes_button_clicked()814   void NoteRecentChanges::on_all_notes_button_clicked()
815   {
816     close_on_escape(false);  // intentional switch to search, user probably want to work more with this window
817     present_search();
818   }
819 
on_show_window_menu()820   void NoteRecentChanges::on_show_window_menu()
821   {
822     HasActions *embed_with_actions = dynamic_cast<HasActions*>(currently_foreground());
823     if(embed_with_actions) {
824       if(m_window_menu_embedded == NULL) {
825         m_window_menu_embedded = make_window_menu(m_window_actions_button, std::move(embed_with_actions->get_popover_widgets()));
826       }
827       m_window_menu_embedded->show_all();
828     }
829     else {
830       if(m_window_menu_default == NULL) {
831         m_window_menu_default = make_window_menu(m_window_actions_button, std::vector<PopoverWidget>());
832       }
833       m_window_menu_default->show_all();
834     }
835   }
836 
make_window_menu(Gtk::Button * button,std::vector<PopoverWidget> && items)837   Gtk::PopoverMenu *NoteRecentChanges::make_window_menu(Gtk::Button *button, std::vector<PopoverWidget> && items)
838   {
839     Gtk::PopoverMenu *menu = manage(new Gtk::PopoverMenu);
840     Gtk::Box *menu_box = manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
841     utils::set_common_popover_widget_props(*menu_box);
842     if(items.size() > 0) {
843       auto iter = items.begin();
844       auto current_section = iter->section;
845       for(; iter != items.end() && iter->section != APP_CUSTOM_SECTION; ++iter) {
846           if(iter->section != current_section) {
847             current_section = iter->section;
848             menu_box->add(*manage(new Gtk::Separator));
849           }
850           menu_box->add(*manage(iter->widget));
851       }
852 
853       menu->add(*menu_box);
854       for(; iter != items.end(); ++iter) {
855           PopoverSubmenu *submenu = dynamic_cast<PopoverSubmenu*>(iter->widget);
856           if(submenu) {
857             menu->add(*manage(iter->widget));
858             menu->child_property_submenu(*iter->widget) = submenu->name();
859           }
860           else {
861             ERR_OUT(_("Expected widget to be a sub-menu!"));
862           }
863       }
864     }
865     else {
866       menu_box->add(*manage(new Gtk::Label(_("No configured actions"))));
867       menu->add(*menu_box);
868     }
869 
870     menu->set_relative_to(*button);
871     menu->set_modal(true);
872     menu->set_position(Gtk::POS_BOTTOM);
873     return menu;
874   }
875 
on_embedded_name_changed(const Glib::ustring & name)876   void NoteRecentChanges::on_embedded_name_changed(const Glib::ustring & name)
877   {
878     set_title(name);
879   }
880 
on_popover_widgets_changed()881   void NoteRecentChanges::on_popover_widgets_changed()
882   {
883     if(m_window_menu_embedded) {
884       m_window_menu_embedded = NULL;
885     }
886   }
887 
on_notes_widget_key_press(GdkEventKey * ev)888   bool NoteRecentChanges::on_notes_widget_key_press(GdkEventKey *ev)
889   {
890     guint keyval;
891     gdk_event_get_keyval((GdkEvent*)ev, &keyval);
892     switch(keyval) {
893     case GDK_KEY_Escape:
894     case GDK_KEY_Delete:
895     case GDK_KEY_Tab:
896       return false;
897     case GDK_KEY_BackSpace:
898       if(m_search_button.get_active()) {
899         Glib::ustring s = m_search_entry->get_text();
900         if(s.size()) {
901           m_search_entry->set_text(s.substr(0, s.size() - 1));
902         }
903       }
904       return false;
905     default:
906       {
907         guint32 character = gdk_keyval_to_unicode(keyval);
908         if(character) {  // ignore special keys
909           if(!m_search_button.get_active()) {
910             // first show search box, then activate button
911             // because we do not want the box to get selected
912             show_search_bar(false);
913             m_search_button.activate();
914           }
915           Glib::ustring s;
916           s += character;
917           g_signal_emit_by_name(m_search_entry->gobj(), "insert-at-cursor", s.c_str());
918           return true;
919         }
920         return false;
921       }
922     }
923   }
924 
925 }
926 
927