/* ** Copyright (C) 2010-2020 Dirk-Jan C. Binnema ** ** 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, 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, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include #include #include /* for memset */ #include #include #include #include "mug-msg-list-view.h" #include "mug-query-bar.h" #include "mug-msg-view.h" #include "mug-shortcuts.h" struct _MugData { GtkWidget* win; GtkWidget* statusbar; GtkWidget* mlist; GtkWidget* toolbar; GtkWidget* msgview; GtkWidget* querybar; GtkWidget* shortcuts; gchar* muhome; }; typedef struct _MugData MugData; static void about_mug(MugData* mugdata) { GtkWidget* about; about = gtk_message_dialog_new(GTK_WINDOW(mugdata->win), GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "Mug version %s\n" "A graphical frontend to the 'mu' e-mail search engine\n\n" "(c) 2010-2013 Dirk-Jan C. Binnema\n" "Released under the terms of the GPLv3+", VERSION); gtk_dialog_run(GTK_DIALOG(about)); gtk_widget_destroy(about); } enum _ToolAction { ACTION_PREV_MSG = 1, ACTION_NEXT_MSG, ACTION_REINDEX, ACTION_DO_QUIT, ACTION_ABOUT, ACTION_SEPARATOR /* pseudo action */ }; typedef enum _ToolAction ToolAction; static void on_tool_button_clicked(GtkToolButton* btn, MugData* mugdata) { ToolAction action; action = (ToolAction)GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(btn), "action")); switch (action) { case ACTION_DO_QUIT: gtk_main_quit(); break; case ACTION_NEXT_MSG: mug_msg_list_view_move_next(MUG_MSG_LIST_VIEW(mugdata->mlist)); break; case ACTION_PREV_MSG: mug_msg_list_view_move_prev(MUG_MSG_LIST_VIEW(mugdata->mlist)); break; case ACTION_ABOUT: about_mug(mugdata); break; default: g_print("%u\n", action); } } static GtkToolItem* tool_button(const char* name) { GtkWidget* icon; icon = gtk_image_new_from_icon_name(name, GTK_ICON_SIZE_SMALL_TOOLBAR); return gtk_menu_tool_button_new(icon, NULL); } static GtkToolItem* get_connected_tool_button(const char* stock_id, ToolAction action, MugData* mugdata) { GtkToolItem* btn; btn = tool_button(stock_id); g_object_set_data(G_OBJECT(btn), "action", GUINT_TO_POINTER(action)); g_signal_connect(G_OBJECT(btn), "clicked", G_CALLBACK(on_tool_button_clicked), mugdata); return btn; } static GtkWidget* mug_toolbar(MugData* mugdata) { GtkWidget* toolbar; int i; struct { const char* stock_id; ToolAction action; } tools[] = {{"go-up", ACTION_PREV_MSG}, {"go-down", ACTION_NEXT_MSG}, {NULL, ACTION_SEPARATOR}, {"view-refresh", ACTION_REINDEX}, {NULL, ACTION_SEPARATOR}, {"help-about", ACTION_ABOUT}, {NULL, ACTION_SEPARATOR}, {"application-exit", ACTION_DO_QUIT}}; toolbar = gtk_toolbar_new(); for (i = 0; i != G_N_ELEMENTS(tools); ++i) { if (tools[i].action == ACTION_SEPARATOR) { /* separator? */ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), gtk_separator_tool_item_new(), i); continue; } else /* nope: a real item */ gtk_toolbar_insert( GTK_TOOLBAR(toolbar), get_connected_tool_button(tools[i].stock_id, tools[i].action, mugdata), i); } return toolbar; } static void on_shortcut_clicked(GtkWidget* w, const gchar* query, MugData* mdata) { mug_query_bar_set_query(MUG_QUERY_BAR(mdata->querybar), query, TRUE); } static GtkWidget* mug_shortcuts_bar(MugData* data) { data->shortcuts = mug_shortcuts_new(mu_runtime_path(MU_RUNTIME_PATH_BOOKMARKS)); g_signal_connect(G_OBJECT(data->shortcuts), "clicked", G_CALLBACK(on_shortcut_clicked), data); return data->shortcuts; } static GtkWidget* mug_statusbar(void) { GtkWidget* statusbar; statusbar = gtk_statusbar_new(); return statusbar; } static void on_query_changed(MugQueryBar* bar, const char* query, MugData* mugdata) { int count; /* clear the old message */ mug_msg_view_set_msg(MUG_MSG_VIEW(mugdata->msgview), NULL); count = mug_msg_list_view_query(MUG_MSG_LIST_VIEW(mugdata->mlist), query); if (count >= 0) { gchar* msg = g_strdup_printf("%d message%s found matching '%s'", count, count > 1 ? "s" : "", mug_msg_list_view_get_query(MUG_MSG_LIST_VIEW(mugdata->mlist))); gtk_statusbar_push(GTK_STATUSBAR(mugdata->statusbar), 0, msg); g_free(msg); mug_msg_list_view_move_first(MUG_MSG_LIST_VIEW(mugdata->mlist)); gtk_widget_grab_focus(GTK_WIDGET(mugdata->mlist)); } if (count == 0) /* nothing found */ mug_query_bar_grab_focus(MUG_QUERY_BAR(bar)); } static void on_msg_selected(MugMsgListView* mlist, const char* mpath, MugData* mugdata) { mug_msg_view_set_msg(MUG_MSG_VIEW(mugdata->msgview), mpath); } static void on_list_view_error(MugMsgListView* mlist, MugError err, MugData* mugdata) { GtkWidget* errdialog; const char* msg; switch (err) { case MUG_ERROR_XAPIAN_NOT_UPTODATE: msg = "The Xapian Database has the wrong version\n" "Please run 'mu init'"; break; case MUG_ERROR_XAPIAN_DIR: msg = "Cannot find the Xapian database dir\n" "Please restart mug with --muhome=... pointing\n" "to your mu home directory"; break; case MUG_ERROR_QUERY: msg = "Error in query"; break; default: msg = "Some error occurred"; break; } errdialog = gtk_message_dialog_new(GTK_WINDOW(mugdata->win), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg); gtk_dialog_run(GTK_DIALOG(errdialog)); gtk_widget_destroy(errdialog); if (err == MUG_ERROR_QUERY) mug_query_bar_grab_focus(MUG_QUERY_BAR(mugdata->querybar)); } static GtkWidget* mug_querybar(void) { GtkWidget* querybar; querybar = mug_query_bar_new(); return querybar; } static GtkWidget* mug_query_area(MugData* mugdata) { GtkWidget *queryarea, *paned, *scrolled; queryarea = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2); paned = gtk_paned_new(GTK_ORIENTATION_VERTICAL); mugdata->mlist = mug_msg_list_view_new(mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB)); scrolled = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add(GTK_CONTAINER(scrolled), mugdata->mlist); gtk_paned_add1(GTK_PANED(paned), scrolled); mugdata->msgview = mug_msg_view_new(); mug_msg_view_set_note(MUG_MSG_VIEW(mugdata->msgview), "

Welcome to mug!


" "mug is an experimental UI for mu, which will " "slowly evolve into something useful.

Enjoy the ride."); g_signal_connect(G_OBJECT(mugdata->mlist), "msg-selected", G_CALLBACK(on_msg_selected), mugdata); g_signal_connect(G_OBJECT(mugdata->mlist), "error-occured", G_CALLBACK(on_list_view_error), mugdata); gtk_paned_add2(GTK_PANED(paned), mugdata->msgview); mugdata->querybar = mug_querybar(); g_signal_connect(G_OBJECT(mugdata->querybar), "query-changed", G_CALLBACK(on_query_changed), mugdata); gtk_box_pack_start(GTK_BOX(queryarea), mugdata->querybar, FALSE, FALSE, 2); gtk_box_pack_start(GTK_BOX(queryarea), paned, TRUE, TRUE, 2); gtk_widget_show_all(queryarea); return queryarea; } static GtkWidget* mug_main_area(MugData* mugdata) { GtkWidget *mainarea, *w; mainarea = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); w = mug_shortcuts_bar(mugdata); gtk_box_pack_start(GTK_BOX(mainarea), w, FALSE, FALSE, 0); gtk_widget_show(w); w = mug_query_area(mugdata); gtk_box_pack_start(GTK_BOX(mainarea), w, TRUE, TRUE, 0); gtk_widget_show(w); return mainarea; } static GtkWidget* mug_shell(MugData* mugdata) { GtkWidget* vbox; mugdata->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(mugdata->win), "Mug Mail Search"); vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2); mugdata->toolbar = mug_toolbar(mugdata); gtk_box_pack_start(GTK_BOX(vbox), mugdata->toolbar, FALSE, FALSE, 2); gtk_box_pack_start(GTK_BOX(vbox), mug_main_area(mugdata), TRUE, TRUE, 2); mugdata->statusbar = mug_statusbar(); gtk_box_pack_start(GTK_BOX(vbox), mugdata->statusbar, FALSE, FALSE, 2); gtk_container_add(GTK_CONTAINER(mugdata->win), vbox); gtk_widget_show_all(vbox); gtk_window_set_default_size(GTK_WINDOW(mugdata->win), 700, 500); gtk_window_set_resizable(GTK_WINDOW(mugdata->win), TRUE); { gchar* icon; icon = g_strdup_printf("%s%cmug.svg", MUGDIR, G_DIR_SEPARATOR); gtk_window_set_icon_from_file(GTK_WINDOW(mugdata->win), icon, NULL); g_free(icon); } return mugdata->win; } static gint on_focus_query_bar(GtkWidget* ignored, GdkEventKey* event, MugData* mugdata) { if (event->type == GDK_KEY_RELEASE && event->keyval == GDK_KEY_Escape) { mug_query_bar_grab_focus(MUG_QUERY_BAR(mugdata->querybar)); return 1; } return 0; } int main(int argc, char* argv[]) { MugData mugdata; GtkWidget* mugshell; GOptionContext* octx; GOptionEntry entries[] = { {"muhome", 0, 0, G_OPTION_ARG_FILENAME, &mugdata.muhome, "specify an alternative mu directory", NULL}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} /* sentinel */ }; gtk_init(&argc, &argv); octx = g_option_context_new("- mug options"); g_option_context_add_main_entries(octx, entries, "Mug"); memset(&mugdata, 0, sizeof(MugData)); if (!g_option_context_parse(octx, &argc, &argv, NULL)) { g_option_context_free(octx); g_printerr("mug: error in options\n"); return 1; } g_option_context_free(octx); mu_runtime_init(mugdata.muhome, "mug", FALSE); mugshell = mug_shell(&mugdata); g_signal_connect(G_OBJECT(mugshell), "destroy", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect(G_OBJECT(mugshell), "key_release_event", G_CALLBACK(on_focus_query_bar), (gpointer)&mugdata); gtk_widget_show(mugshell); mug_query_bar_grab_focus(MUG_QUERY_BAR(mugdata.querybar)); gtk_main(); g_free(mugdata.muhome); mu_runtime_uninit(); return 0; }