1 # include <iostream>
2 
3 # include <gtkmm.h>
4 # include <gtkmm/widget.h>
5 # include <gtkmm/notebook.h>
6 # include <pangomm/fontdescription.h>
7 # include <pango/pango.h>
8 
9 # ifndef DISABLE_VTE
10 # include <vte/vte.h>
11 # endif
12 
13 # include <boost/filesystem.hpp>
14 
15 # include "astroid.hh"
16 # include "poll.hh"
17 # include "main_window.hh"
18 # include "modes/mode.hh"
19 # include "modes/thread_index/thread_index.hh"
20 # include "modes/saved_searches.hh"
21 # include "modes/help_mode.hh"
22 # include "modes/edit_message.hh"
23 # include "modes/log_view.hh"
24 # include "command_bar.hh"
25 # include "actions/action.hh"
26 # include "actions/action_manager.hh"
27 # include "utils/resource.hh"
28 
29 using namespace std;
30 namespace bfs = boost::filesystem;
31 
32 # ifndef DISABLE_VTE
33 extern "C" {
mw_on_terminal_child_exit(VteTerminal * t,gint a,gpointer mw)34   void mw_on_terminal_child_exit (VteTerminal * t, gint a, gpointer mw) {
35     ((Astroid::MainWindow *) mw)->on_terminal_child_exit (t, a);
36   }
37 
mw_on_terminal_commit(VteTerminal * t,gchar ** tx,guint sz,gpointer mw)38   void mw_on_terminal_commit (VteTerminal * t, gchar ** tx, guint sz, gpointer mw) {
39     ((Astroid::MainWindow *) mw)->on_terminal_commit (t, tx, sz);
40   }
41 
42 # if VTE_CHECK_VERSION(0,48,0)
mw_on_terminal_spawn_callback(VteTerminal * t,GPid pid,GError * err,gpointer mw)43   void mw_on_terminal_spawn_callback (VteTerminal * t, GPid pid, GError * err, gpointer mw)
44   {
45     ((Astroid::MainWindow *) mw)->on_terminal_spawn_callback (t, pid, err);
46   }
47 # endif
48 }
49 # endif
50 
51 namespace Astroid {
52   atomic<uint> MainWindow::nextid (0);
53   int          Notebook::icon_size = 42;
54 
Notebook()55   Notebook::Notebook () {
56     set_scrollable (true);
57 
58     set_action_widget (&icons, Gtk::PACK_END);
59     icons.show_all ();
60 
61     astroid->poll->signal_poll_state ().connect (
62         sigc::mem_fun (this, &Notebook::poll_state_changed));
63     signal_size_allocate ().connect (
64         sigc::mem_fun (this, &Notebook::on_my_size_allocate));
65 
66     poll_state_changed (astroid->poll->get_poll_state());
67   }
68 
on_my_size_allocate(Gtk::Allocation &)69   void Notebook::on_my_size_allocate (Gtk::Allocation &) {
70     icon_size = icons.get_height ();
71   }
72 
poll_state_changed(bool state)73   void Notebook::poll_state_changed (bool state) {
74     if (state && !spinner_on) {
75       /* set up spinner for poll */
76       spinner_on = true;
77       icons.pack_end (poll_spinner, true, true, 5);
78       icons.show_all ();
79       poll_spinner.start ();
80     } else if (!state && spinner_on) {
81       poll_spinner.stop ();
82       icons.remove (poll_spinner);
83       spinner_on = false;
84     }
85   }
86 
add_widget(Gtk::Widget * w)87   void Notebook::add_widget (Gtk::Widget * w) {
88     icons.pack_start (*w, true, true, 5);
89     w->show ();
90     icons.show_all ();
91   }
92 
remove_widget(Gtk::Widget * w)93   void Notebook::remove_widget (Gtk::Widget * w) {
94     icons.remove (*w);
95   }
96 
MainWindow()97   MainWindow::MainWindow () {
98     id = ++nextid;
99 
100     actions = astroid->actions;
101 
102     LOG (debug) << "mw: init, id: " << id;
103 
104     set_title ("");
105     set_default_size (1200, 800);
106 
107     path icon = Resource (false, "ui/icons/icon_color.png").get_path ();
108     try {
109       refptr<Gdk::Pixbuf> pixbuf = Gdk::Pixbuf::create_from_file (icon.c_str (), 42, 42, true);
110       set_icon (pixbuf);
111     } catch (Gdk::PixbufError &e) {
112       LOG (error) << "mw: could not set icon: " << e.what ();
113     }
114 
115     vbox.set_orientation (Gtk::ORIENTATION_VERTICAL);
116 
117     command.set_main_window (this);
118 
119     command.property_search_mode_enabled().signal_changed().connect(
120         sigc::mem_fun (*this, &MainWindow::on_command_mode_changed)
121         );
122 
123     vbox.pack_start (command, false, true, 0);
124     vbox.pack_start (notebook, Gtk::PACK_EXPAND_WIDGET, 0);
125 
126     /* set up yes-no asker */
127     rev_yes_no = Gtk::manage (new Gtk::Revealer ());
128     rev_yes_no->set_transition_type (Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP);
129 
130     Gtk::Box * rh = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
131 
132     label_yes_no = Gtk::manage (new Gtk::Label ());
133     rh->pack_start (*label_yes_no, true, true, 5);
134     label_yes_no->set_halign (Gtk::ALIGN_START);
135 
136     /* buttons */
137     Gtk::Button * yes = Gtk::manage (new Gtk::Button("_Yes"));
138     Gtk::Button * no  = Gtk::manage (new Gtk::Button("_No"));
139 
140     yes->set_use_underline (true);
141     no->set_use_underline (true);
142 
143     rh->pack_start (*yes, false, true, 5);
144     rh->pack_start (*no, false, true, 5);
145 
146     rev_yes_no->set_margin_top (0);
147     rh->set_margin_bottom (5);
148 
149     rev_yes_no->add (*rh);
150     rev_yes_no->set_reveal_child (false);
151     vbox.pack_end (*rev_yes_no, false, true, 0);
152 
153     yes->signal_clicked().connect (sigc::mem_fun (this, &MainWindow::on_yes));
154     no->signal_clicked().connect (sigc::mem_fun (this, &MainWindow::on_no));
155 
156     /* multi key handler */
157     rev_multi = Gtk::manage (new Gtk::Revealer ());
158     rev_multi->set_transition_type (Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP);
159 
160     Gtk::Box * rh_ = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
161 
162     label_multi = Gtk::manage (new Gtk::Label ());
163     rh_->pack_start (*label_multi, true, true, 5);
164     label_multi->set_halign (Gtk::ALIGN_START);
165 
166     rev_multi->set_margin_top (0);
167     rh->set_margin_bottom (5);
168 
169     rev_multi->add (*rh_);
170     rev_multi->set_reveal_child (false);
171     vbox.pack_end (*rev_multi, false, true, 0);
172 
173     /* terminal */
174 # ifndef DISABLE_VTE
175     rev_terminal = Gtk::manage (new Gtk::Revealer ());
176     rev_terminal->set_transition_type (Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP);
177     rev_terminal->set_reveal_child (false);
178     vbox.pack_end (*rev_terminal, false, true, 0);
179 
180     terminal_cwd = bfs::current_path ();
181 # endif
182 
183     add (vbox);
184 
185     show_all_children ();
186 
187     /* connect keys */
188     add_events (Gdk::KEY_PRESS_MASK);
189     signal_key_press_event ().connect (
190         sigc::mem_fun(*this, &MainWindow::on_key_press));
191 
192     /* got focus, grab keys */
193     signal_focus_in_event ().connect (
194         sigc::mem_fun (this, &MainWindow::on_my_focus_in_event));
195     signal_focus_out_event ().connect (
196         sigc::mem_fun (this, &MainWindow::on_my_focus_out_event));
197 
198     /* change page */
199     notebook.signal_switch_page ().connect (
200         sigc::mem_fun (this, &MainWindow::on_my_switch_page));
201 
202     /* catch update title events */
203     update_title_dispatcher.connect (
204         sigc::mem_fun (this, &MainWindow::on_update_title));
205 
206     /* catch window close events */
207     signal_delete_event ().connect (
208         sigc::mem_fun (this, &MainWindow::on_delete_event));
209 
210     /* register keys {{{ */
211     keys.title = "MainWindow";
212 
213     keys.register_key ("q",
214         "main_window.quit_ask",
215         "Quit astroid",
216         [&] (Key) {
217           if (astroid->get_windows().size () > 1) {
218             /* other windows, just close this one */
219             quit ();
220           } else {
221             LOG (debug) << "really quit?: " << id;
222             ask_yes_no ("Really quit?", [&](bool yes){ if (yes) quit (); });
223           }
224           return true;
225         });
226 
227     keys.register_key ("Q",
228         "main_window.quit",
229         "Quit astroid (without asking)",
230         [&] (Key) {
231           quit ();
232           return true;
233         });
234 
235     keys.register_key ("l", { "b" },
236         "main_window.next_page",
237         "Next page",
238         [&] (Key)
239         {
240           if (notebook.get_current_page () == (notebook.get_n_pages () - 1))
241             set_active (0);
242           else
243             set_active (notebook.get_current_page() + 1);
244 
245           return true;
246         });
247 
248     keys.register_key ("h", { "B" },
249         "main_window.previous_page",
250         "Previous page",
251         [&] (Key) {
252           if (notebook.get_current_page() == 0)
253             set_active (notebook.get_n_pages()-1);
254           else
255             set_active (notebook.get_current_page() - 1);
256 
257           return true;
258         });
259 
260     keys.register_key ("M-1",
261         "main_window.jump_to_page_1",
262         "Jump to page 1",
263         bind (&MainWindow::jump_to_page, this, _1, 1));
264 
265     keys.register_key ("M-2",
266         "main_window.jump_to_page_2",
267         "Jump to page 2",
268         bind (&MainWindow::jump_to_page, this, _1, 2));
269 
270     keys.register_key ("M-3",
271         "main_window.jump_to_page_3",
272         "Jump to page 3",
273         bind (&MainWindow::jump_to_page, this, _1, 3));
274 
275     keys.register_key ("M-4",
276         "main_window.jump_to_page_4",
277         "Jump to page 4",
278         bind (&MainWindow::jump_to_page, this, _1, 4));
279 
280     keys.register_key ("M-5",
281         "main_window.jump_to_page_5",
282         "Jump to page 5",
283         bind (&MainWindow::jump_to_page, this, _1, 5));
284 
285     keys.register_key ("M-6",
286         "main_window.jump_to_page_6",
287         "Jump to page 6",
288         bind (&MainWindow::jump_to_page, this, _1, 6));
289 
290     keys.register_key ("M-7",
291         "main_window.jump_to_page_7",
292         "Jump to page 7",
293         bind (&MainWindow::jump_to_page, this, _1, 7));
294 
295     keys.register_key ("M-8",
296         "main_window.jump_to_page_8",
297         "Jump to page 8",
298         bind (&MainWindow::jump_to_page, this, _1, 8));
299 
300     keys.register_key ("M-9",
301         "main_window.jump_to_page_9",
302         "Jump to page 9",
303         bind (&MainWindow::jump_to_page, this, _1, 9));
304 
305     keys.register_key ("M-0",
306         "main_window.jump_to_page_0",
307         "Jump to page 0",
308         bind (&MainWindow::jump_to_page, this, _1, 0));
309 
310     keys.register_key ("C-w", "main_window.close_page",
311         "Close mode (or window if other windows are open)",
312         [&] (Key) {
313           close_page ();
314           return true;
315         });
316 
317     keys.register_key ("C-W", "main_window.close_page_force",
318         "Force close mode (or window if other windows are open)",
319         [&] (Key) {
320           close_page (true);
321           return true;
322         });
323 
324     keys.register_key ("o",
325         "main_window.search",
326         "Search",
327         [&] (Key) {
328           enable_command (CommandBar::CommandMode::Search, "", NULL);
329           return true;
330         });
331 
332     keys.register_key ("M-s", "main_window.show_saved_searches",
333         "Show saved searches",
334         [&] (Key) {
335           add_mode (new SavedSearches (this));
336           return true;
337         });
338 
339     keys.register_key ("L", "main_window.search_tag",
340         "Search for tag:",
341         [&] (Key) {
342           enable_command (CommandBar::CommandMode::Search, "tag:", NULL);
343           return true;
344         });
345 
346 
347     keys.register_key (Key (GDK_KEY_question), "main_window.show_help",
348         "Show help",
349         [&] (Key) {
350           HelpMode * h = new HelpMode (this);
351           h->show_help ((Mode*) notebook.get_nth_page (notebook.get_current_page()));
352           add_mode (h);
353           return true;
354         });
355 
356     keys.register_key ("z", "main_window.show_log",
357         "Show log window",
358         [&] (Key) {
359           add_mode (new LogView (this));
360           return true;
361         });
362 
363     keys.register_key ("u", "main_window.undo",
364         "Undo last action",
365         [&] (Key) {
366           actions->undo ();
367           return true;
368         });
369 
370     keys.register_key ("m", "main_window.new_mail",
371         "Compose new mail",
372         [&] (Key) {
373           add_mode (new EditMessage (this));
374           return true;
375         });
376 
377     keys.register_key ("P", "main_window.poll",
378         "Start manual poll",
379         [&] (Key) {
380           astroid->poll->poll ();
381           return true;
382         });
383 
384     keys.register_key ("M-p", "main_window.toggle_auto_poll",
385         "Toggle auto poll",
386         [&] (Key) {
387           astroid->poll->toggle_auto_poll ();
388           return true;
389         });
390 
391     keys.register_key ("C-c", "main_window.cancel_poll",
392         "Cancel ongoing poll",
393         [&] (Key) {
394           astroid->poll->cancel_poll ();
395           return true;
396         });
397 
398     keys.register_key ("C-o", "main_window.open_new_window",
399         "Open new main window",
400         [&] (Key) {
401           astroid->open_new_window ();
402           return true;
403         });
404 
405     keys.register_key ("\"", "main_window.clipboard",
406         "Set target clipboard",
407         [&] (Key k) {
408           multi_key (clipboard, k);
409           return true;
410         });
411 
412     clipboard.register_key ("+", "main_window.clipboard.clipboard",
413         "Set target clipboard to CLIPBOARD (default)",
414         [&] (Key) {
415           astroid->clipboard_target = GDK_SELECTION_CLIPBOARD;
416           return true;
417         });
418 
419     clipboard.register_key ("*", "main_window.clipboard.primary",
420         "Set target clipboard to PRIMARY",
421         [&] (Key) {
422           astroid->clipboard_target = GDK_SELECTION_PRIMARY;
423           return true;
424         });
425 
426 # ifndef DISABLE_VTE
427     keys.register_key ("|", "main_window.open_terminal",
428         "Open terminal",
429         [&] (Key) {
430           enable_terminal ();
431           return true;
432         });
433 # endif
434 
435     // }}}
436   }
437 
jump_to_page(Key,int pg)438   bool MainWindow::jump_to_page (Key, int pg) {
439     if (pg == 0) {
440       pg = notebook.get_n_pages () - 1;
441     } else {
442       pg--;
443     }
444 
445     LOG (debug) << "mw: swapping to page: " << pg;
446 
447     if (notebook.get_current_page () != pg) {
448       set_active (pg);
449     }
450 
451     return true;
452   }
453 
is_current(Mode * m)454   bool MainWindow::is_current (Mode * m) {
455     int i = notebook.get_current_page ();
456     return (m == ((Mode *) notebook.get_nth_page (i)));
457   }
458 
set_title(ustring t)459   void MainWindow::set_title (ustring t) {
460 
461     ustring tt = ustring::compose( "Astroid (%1)", Astroid::version);
462 
463     if (t.size() > 0) {
464       tt = t + " - " + tt;
465     }
466 
467     Gtk::Window::set_title (tt);
468   }
469 
on_update_title()470   void MainWindow::on_update_title () {
471     int n = notebook.get_current_page();
472 
473     if (n >= 0 && n <= notebook.get_n_pages()-1) {
474       set_title (((Mode*) notebook.get_nth_page(n))->get_label());
475     } else {
476       set_title ("");
477     }
478   }
479 
enable_command(CommandBar::CommandMode m,ustring title,ustring cmd,function<void (ustring)> f)480   void MainWindow::enable_command (CommandBar::CommandMode m, ustring title, ustring cmd, function<void(ustring)> f) {
481     ungrab_active ();
482     command.enable_command (m, title, cmd, f);
483     active_mode = Command;
484     command.add_modal_grab ();
485   }
486 
enable_command(CommandBar::CommandMode m,ustring cmd,function<void (ustring)> f)487   void MainWindow::enable_command (CommandBar::CommandMode m, ustring cmd, function<void(ustring)> f) {
488     ungrab_active ();
489     command.enable_command (m, cmd, f);
490     active_mode = Command;
491     command.add_modal_grab ();
492   }
493 
disable_command()494   void MainWindow::disable_command () {
495     // hides itself
496     command.remove_modal_grab();
497     set_active (current);
498     active_mode = Window;
499   }
500 
on_command_mode_changed()501   void MainWindow::on_command_mode_changed () {
502     if (!command.get_search_mode()) {
503       disable_command ();
504     }
505   }
506 
507   /* Terminal {{{ */
508 # ifndef DISABLE_VTE
enable_terminal()509   void MainWindow::enable_terminal () {
510     rev_terminal->set_reveal_child (true);
511     ungrab_active ();
512     active_mode = Terminal;
513 
514     vte_term = vte_terminal_new ();
515     gtk_container_add (GTK_CONTAINER(rev_terminal->gobj ()), vte_term);
516     rev_terminal->show_all ();
517 
518     /* load font settings */
519     ustring font_desc_string = astroid->config("terminal").get<string> ("font_description");
520 
521     if (font_desc_string == "" || font_desc_string == "default") {
522       auto settings = Gio::Settings::create ("org.gnome.desktop.interface");
523       font_desc_string = settings->get_string ("monospace-font-name");
524     }
525 
526     auto font_description = Pango::FontDescription (font_desc_string);
527 
528     /* https://developer.gnome.org/pangomm/stable/classPango_1_1FontDescription.html#details */
529     if (font_description.get_size () == 0) {
530       LOG (warn) << "terminal.font_description: no size specified, expect weird behaviour.";
531     }
532 
533     vte_terminal_set_font (VTE_TERMINAL(vte_term), font_description.gobj ());
534     vte_terminal_set_size (VTE_TERMINAL (vte_term), 1, astroid->config("terminal").get<int> ("height"));
535 
536     /* start shell */
537     char * shell = vte_get_user_shell ();
538 
539     char * args[2] = { shell, NULL };
540     char * envs[1] = { NULL };
541 
542     LOG (info) << "mw: starting terminal..: " << shell;
543 
544     if (!bfs::exists (terminal_cwd)) {
545       terminal_cwd = bfs::current_path ();
546     }
547 
548 # if VTE_CHECK_VERSION(0,48,0)
549     vte_terminal_spawn_async (VTE_TERMINAL(vte_term),
550         VTE_PTY_DEFAULT,
551         terminal_cwd.c_str(),
552         args,
553         envs,
554         G_SPAWN_DEFAULT,
555         NULL,
556         NULL,
557         NULL,
558         -1,
559         NULL,
560         mw_on_terminal_spawn_callback,
561         this);
562 # else
563     GError * err = NULL;
564     vte_terminal_spawn_sync (VTE_TERMINAL(vte_term),
565         VTE_PTY_DEFAULT,
566         terminal_cwd.c_str(),
567         args,
568         envs,
569         G_SPAWN_DEFAULT,
570         NULL,
571         NULL,
572         &terminal_pid,
573         NULL,
574         (err = NULL, &err));
575 
576     on_terminal_spawn_callback (VTE_TERMINAL(vte_term), terminal_pid, err);
577 
578 # endif
579 
580     gtk_widget_grab_focus (vte_term);
581     gtk_grab_add (vte_term);
582   }
583 
on_terminal_spawn_callback(VteTerminal * vte_term,GPid pid,GError * err)584   void MainWindow::on_terminal_spawn_callback (VteTerminal * vte_term, GPid pid, GError * err)
585   {
586     if (err) {
587       LOG (error) << "mw: terminal: " << err->message;
588       disable_terminal ();
589     } else {
590       terminal_pid = pid;
591 
592       LOG (debug) << "mw: terminal started: " << terminal_pid;
593       g_signal_connect (vte_term, "child-exited",
594           G_CALLBACK (mw_on_terminal_child_exit),
595           (gpointer) this);
596 
597       g_signal_connect (vte_term, "commit",
598           G_CALLBACK (mw_on_terminal_commit),
599           (gpointer) this);
600 
601     }
602   }
603 
disable_terminal()604   void MainWindow::disable_terminal () {
605     LOG (info) << "mw: disabling terminal..";
606     rev_terminal->set_reveal_child (false);
607     set_active (current);
608     active_mode = Window;
609     gtk_grab_remove (vte_term);
610 
611     gtk_widget_destroy (vte_term);
612   }
613 
on_terminal_child_exit(VteTerminal *,gint)614   void MainWindow::on_terminal_child_exit (VteTerminal *, gint) {
615     LOG (info) << "mw: terminal exited (cwd: " << terminal_cwd.c_str () << ")";
616     disable_terminal ();
617   }
618 
on_terminal_commit(VteTerminal *,gchar **,guint)619   void MainWindow::on_terminal_commit (VteTerminal *, gchar **, guint) {
620     bfs::path pth = bfs::path(ustring::compose ("/proc/%1/cwd", terminal_pid).c_str ());
621 
622     if (bfs::exists (pth)) {
623       terminal_cwd = bfs::canonical (pth);
624     }
625   }
626 # endif
627 
628   // }}}
629 
quit()630   void MainWindow::quit () {
631     LOG (info) << "mw: quit: " << id;
632     in_quit = true;
633 
634     /* focus out */
635     ungrab_active ();
636 
637     for (int c = notebook.get_n_pages () -1; c >= 0; c--) {
638       /* ((Mode*) notebook.get_nth_page (c))->pre_close (); */
639       del_mode (c);
640     }
641 
642     close (); // Gtk::Window::close ()
643   }
644 
on_delete_event(GdkEventAny *)645   bool MainWindow::on_delete_event (GdkEventAny *) {
646     if (!in_quit)
647       quit ();
648     return false;
649   }
650 
on_yes()651   void MainWindow::on_yes () {
652     answer_yes_no (true);
653   }
654 
on_no()655   void MainWindow::on_no () {
656     answer_yes_no (false);
657   }
658 
659 
mode_key_handler(GdkEventKey * event)660   bool MainWindow::mode_key_handler (GdkEventKey * event) {
661     if (yes_no_waiting) {
662       switch (event->keyval) {
663         case GDK_KEY_Y:
664         case GDK_KEY_y:
665           answer_yes_no (true);
666           return true;
667 
668         case GDK_KEY_Escape:
669         case GDK_KEY_N:
670         case GDK_KEY_n:
671           answer_yes_no (false);
672           return true;
673       }
674 
675       /* swallow all other keys */
676       return true;
677 
678     } else if (multi_waiting) {
679       bool res = false;
680 
681       switch (event->keyval) {
682         case GDK_KEY_Escape:
683           {
684             res = true;
685           }
686           break;
687 
688         default:
689           {
690             res = multi_keybindings.handle (event);
691           }
692           break;
693       }
694 
695       /* close rev */
696       multi_waiting = !res;
697 
698       if (res) {
699         rev_multi->set_reveal_child (false);
700         multi_keybindings.clear ();
701       }
702 
703       return true; // swallow all keys
704 
705 # ifndef DISABLE_VTE
706     } else if (active_mode == Terminal) {
707       return true;
708     }
709 # else
710     }
711 # endif
712 
713     return false;
714   }
715 
on_key_press(GdkEventKey * event)716   bool MainWindow::on_key_press (GdkEventKey * event) {
717     if (mode_key_handler (event)) return true;
718 
719     if (active_mode == Command) {
720       command.command_handle_event (event);
721       return true;
722     }
723 
724     return keys.handle (event);
725   }
726 
add_mode(Mode * m)727   void MainWindow::add_mode (Mode * m) {
728     m = Gtk::manage (m);
729 
730     Gtk::Widget * w = m;
731 
732     int n = notebook.insert_page ((*w), m->tab_label, current+1);
733 
734     notebook.show_all ();
735 
736     set_active (n);
737   }
738 
ask_yes_no(ustring question,std::function<void (bool)> closure)739   void MainWindow::ask_yes_no (
740       ustring question,
741       std::function <void (bool)> closure)
742   {
743     using std::endl;
744     LOG (info) << "mw: " << question;
745 
746     if (yes_no_waiting || multi_waiting) {
747       LOG (warn) << "mw: already waiting for answer to previous question, discarding this one.";
748       return;
749     }
750 
751     yes_no_waiting = true;
752     yes_no_closure = closure;
753 
754     rev_yes_no->set_reveal_child (true);
755     label_yes_no->set_text (question + " [y/n]");
756   }
757 
answer_yes_no(bool yes)758   void MainWindow::answer_yes_no (bool yes) {
759     using std::endl;
760     rev_yes_no->set_reveal_child (false);
761 
762     if (yes) {
763       LOG (info) << "mw: yes-no: got yes!";
764     } else {
765       LOG (info) << "mw: yes-no: got no :/";
766     }
767 
768     if (yes_no_waiting) {
769       if (yes_no_closure != NULL) {
770         yes_no_closure (yes);
771       }
772     }
773 
774     yes_no_closure = NULL;
775     yes_no_waiting = false;
776   }
777 
multi_key(Keybindings & kb,Key)778   bool MainWindow::multi_key (Keybindings & kb, Key /* k */)
779   {
780     using std::endl;
781     LOG (info) << "mw: starting multi key.";
782 
783     if (yes_no_waiting || multi_waiting) {
784       LOG (warn) << "mw: already waiting for answer to previous question, discarding this one.";
785       return true;
786     }
787 
788     multi_waiting = true;
789     multi_keybindings = kb;
790 
791     rev_multi->set_reveal_child (true);
792     label_multi->set_markup (kb.short_help ());
793 
794     return true;
795   }
796 
del_mode(int c)797   void MainWindow::del_mode (int c) {
798     // LOG (debug) << "mw: del mode: " << c;
799     if (notebook.get_n_pages() > 1) {
800       if (c >= 0) {
801         if (c == 0) {
802           set_active (c + 1);
803         } else {
804           set_active (c - 1);
805         }
806 
807         ((Mode*) notebook.get_nth_page (c))->pre_close ();
808         notebook.remove_page (c); // this should free the widget (?)
809       } else {
810         LOG (warn) << "mw: attempt to remove negative page";
811       }
812     } else {
813       if (!in_quit && astroid->get_windows().size () > 1) {
814         LOG (debug) << "mw: other windows available, closing this one.";
815         quit ();
816       }
817     }
818   }
819 
close_page(Mode * m,bool force)820   void MainWindow::close_page (Mode * m, bool force) {
821     m->close (force);
822   }
823 
close_page(bool force)824   void MainWindow::close_page (bool force) {
825     int c = notebook.get_current_page ();
826 
827     ((Mode*) notebook.get_nth_page (c))->close (force);
828   }
829 
ungrab_active()830   void MainWindow::ungrab_active () {
831     if (current >= 0) {
832       if (notebook.get_n_pages() > current) {
833         //LOG (debug) << "mw: release modal, from: " << current;
834         ((Mode*) notebook.get_nth_page (current))->release_modal();
835         active = false;
836       }
837     }
838   }
839 
on_my_switch_page(Gtk::Widget *,guint no)840   void MainWindow::on_my_switch_page (Gtk::Widget * /* w */, guint no) {
841     grab_active (no);
842   }
843 
grab_active(int n)844   void MainWindow::grab_active (int n) {
845     //LOG (debug) << "mw: set active: " << n << ", current: " << current;
846 
847     ungrab_active ();
848 
849     //LOG (debug) << "mw: grab modal to: " << n;
850 
851     if (_has_focus) {
852       /* we have focus */
853       ((Mode*) notebook.get_nth_page (n))->grab_modal();
854     } else {
855       LOG (debug) << "mw: does not have focus, will not grab modal.";
856     }
857 
858     current = n;
859     active = true;
860 
861     on_update_title ();
862   }
863 
set_active(int n)864   void MainWindow::set_active (int n) {
865     LOG (debug) << "mw: set active: " << n << ", current: " << current;
866 
867     if (n >= 0 && n <= notebook.get_n_pages()-1) {
868 
869       if (notebook.get_current_page() != n) {
870         notebook.set_current_page (n);
871       }
872 
873       grab_active (n);
874 
875     } else {
876       // LOG (debug) << "mw: set active: page is out of range: " << n;
877       on_update_title ();
878     }
879   }
880 
on_my_focus_in_event(GdkEventFocus *)881   bool MainWindow::on_my_focus_in_event (GdkEventFocus * /* event */) {
882     _has_focus = true;
883     if (!in_quit && active) set_active (current);
884     LOG (debug) << "mw: focus-in: " << id << " active: " << active << ", in_quit: " << in_quit;
885     return false;
886   }
887 
on_my_focus_out_event(GdkEventFocus *)888   bool MainWindow::on_my_focus_out_event (GdkEventFocus * /* event */) {
889     //LOG (debug) << "mw: focus-out: " << id;
890     _has_focus = false;
891     if ((current < notebook.get_n_pages ()) && (current >= 0))
892       ((Mode*) notebook.get_nth_page (current))->release_modal();
893     return false;
894   }
895 }
896 
897