1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  * Pan - A Newsreader for Gtk+
4  * Copyright (C) 2002-2006  Charles Kerr <charles@rebelbase.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include <config.h>
21 #include <glib/gi18n.h>
22 #include "gtk-compat.h"
23 #include <pan/general/debug.h>
24 #include <pan/general/macros.h>
25 #include <pan/icons/pan-pixbufs.h>
26 #include <pan/tasks/task-article.h>
27 #include <pan/tasks/queue.h>
28 #include <pan/usenet-utils/text-massager.h>
29 #include <pan/data-impl/rules-filter.h>
30 #include "hig.h"
31 #include "pad.h"
32 #include "pan-file-entry.h"
33 #include "save-attach-ui.h"
34 #include "gtk-compat.h"
35 
36 using namespace pan;
37 
38 namespace
39 {
40 
41   void
show_group_substitution_help_dialog(gpointer window)42   show_group_substitution_help_dialog (gpointer window)
43   {
44     const char * str = _("%g — group as one directory (alt.binaries.pictures.trains)\n"
45                          "%G — group as nested directory (/alt/binaries/pictures/trains)\n"
46                          "%s — Subject line excerpt\n"
47                          "%S — Subject line\n"
48                          "%n — Poster display name\n"
49                          "%e — Poster email address\n"
50                          "%d — Article timestamp\n"
51                          "“/home/user/News/Pan/%g” becomes\n"
52                          "“/home/user/News/Pan/alt.binaries.pictures.trains”, and\n"
53                          "“/home/user/News/Pan/%G” becomes\n"
54                          "“/home/user/News/Pan/alt/binaries/pictures/trains”,");
55     GtkWidget * w = gtk_message_dialog_new (GTK_WINDOW(window),
56                                             GTK_DIALOG_DESTROY_WITH_PARENT,
57                                             GTK_MESSAGE_INFO,
58                                             GTK_BUTTONS_CLOSE, "%s", str);
59     g_signal_connect_swapped (GTK_OBJECT(w), "response",
60                               G_CALLBACK(gtk_widget_destroy), GTK_OBJECT (w));
61     gtk_widget_show_all (w);
62   }
63 
delete_save_dialog(gpointer castme)64   void delete_save_dialog (gpointer castme)
65   {
66     delete static_cast<SaveAttachmentsDialog*>(castme);
67   }
68 
69   enum { PATH_GROUP, PATH_ENTRY };
70 
71   int path_mode (PATH_GROUP);
72 }
73 
74 void
response_cb(GtkDialog * dialog,int response,gpointer user_data)75 SaveAttachmentsDialog :: response_cb (GtkDialog * dialog,
76                            int response,
77                            gpointer user_data)
78 {
79 
80   if (response == GTK_RESPONSE_OK)
81   {
82     SaveAttachmentsDialog * self (static_cast<SaveAttachmentsDialog*>(user_data));
83 
84     bool subject_in_path = false;
85 
86     // set the path mode based on what widgets exist & are set
87     GtkWidget * gr (self->_save_group_path_radio);
88     GtkWidget * er (self->_save_custom_path_radio);
89     if (gr && er)
90       path_mode = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gr)) ? PATH_GROUP : PATH_ENTRY;
91     else
92       path_mode = PATH_ENTRY;
93 
94     // get the save path
95     std::string path, opath;
96     if (path_mode == PATH_GROUP)
97       path = (const char*) g_object_get_data (G_OBJECT(gr), "default-group-save-path");
98     else if (path_mode == PATH_ENTRY)  {
99       path = pan :: file_entry_get (self->_save_path_entry);
100       self->_prefs.set_string ("default-save-attachments-path", path);
101     }
102     path = opath = expand_download_dir (path.c_str(), self->_group.to_view());
103     if ((path.find("%s") != path.npos) || (path.find("%S") != path.npos))
104       subject_in_path = true;
105 
106     std::string sep( self->_prefs.get_string("save-subj-separator", "-") );
107 
108     const bool always (self->_prefs.get_flag("mark-downloaded-articles-read", false));
109 
110     // make the tasks...
111     Queue::tasks_t tasks;
112     foreach_const (std::vector<Article>, self->_articles, it)
113     {
114       if (subject_in_path)
115         path = expand_download_dir_subject(opath.c_str(), it->subject, sep);
116       tasks.push_back (new TaskArticle (self->_server_rank,
117                                         self->_group_server,
118                                         *it,
119                                         self->_cache,
120                                         self->_read,
121                                         always ? TaskArticle::ALWAYS_MARK : TaskArticle::NEVER_MARK,
122                                         0,
123                                         TaskArticle::DECODE,
124                                         path,
125                                         self->_filename,
126                                         self->_options));
127     }
128 
129     // get the queue mode...
130     Queue::AddMode queue_mode;
131     std::string s = self->_prefs.get_string ("save-article-priority", "age");
132     if      (s == "top")    queue_mode = Queue::TOP;
133     else if (s == "bottom") queue_mode = Queue::BOTTOM;
134     else                    queue_mode = Queue::AGE;
135 
136     // queue up the tasks...
137     if (!tasks.empty())
138       self->_queue.add_tasks (tasks, queue_mode);
139   }
140 
141   gtk_widget_destroy (GTK_WIDGET(dialog));
142 }
143 
144 namespace
145 {
entry_changed_cb(GtkEditable *,gpointer radio_or_null)146   void entry_changed_cb (GtkEditable*, gpointer radio_or_null)
147   {
148     if (radio_or_null)
149       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(radio_or_null), true);
150   }
151 
combo_box_selection_changed(GtkComboBox * combo,gpointer prefs,const char * key)152   void combo_box_selection_changed (GtkComboBox * combo, gpointer prefs, const char * key)
153   {
154     GtkTreeIter iter;
155     gtk_combo_box_get_active_iter (combo, &iter);
156     GtkTreeModel * model (gtk_combo_box_get_model (combo));
157     char * s (0);
158     gtk_tree_model_get (model, &iter, 0, &s, -1);
159     static_cast<Prefs*>(prefs)->set_string (key, s);
160     g_free (s);
161   }
162 
priority_combo_box_selection_changed(GtkComboBox * combo,gpointer prefs)163   void priority_combo_box_selection_changed (GtkComboBox * combo, gpointer prefs)
164   {
165     combo_box_selection_changed (combo, prefs, "save-article-priority");
166   }
167 
create_combo_box(Prefs & prefs,const char * key,const char * fallback,...)168   GtkWidget* create_combo_box (Prefs& prefs, const char * key, const char * fallback, ...)
169   {
170     const std::string active_str (prefs.get_string (key, fallback));
171     int active (-1);
172     GtkListStore * store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
173     va_list args;
174     va_start (args, fallback);
175     for (int i=0;; ++i) {
176       const char * key_str = va_arg (args, const char*);
177       if (!key_str) break;
178       const char * gui_str = va_arg (args, const char*);
179       GtkTreeIter iter;
180       gtk_list_store_append (store, &iter);
181       gtk_list_store_set (store, &iter, 0, key_str, 1, gui_str, -1);
182       if (active_str == key_str) active = i;
183     }
184     va_end(args);
185     GtkWidget * w = gtk_combo_box_new_with_model (GTK_TREE_MODEL(store));
186     GtkCellRenderer * renderer (gtk_cell_renderer_text_new ());
187     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (w), renderer, true);
188     gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (w), renderer, "text", 1, NULL);
189     gtk_combo_box_set_active (GTK_COMBO_BOX(w), active);
190     return w;
191   }
192 
create_priority_combo_box(Prefs & prefs)193   GtkWidget* create_priority_combo_box (Prefs& prefs)
194   {
195     return create_combo_box (prefs, "save-article-priority", "age",
196                              "age", _("Add to the queue sorted by date posted"),
197                              "top", _("Add to the front of the queue"),
198                              "bottom", _("Add to the back of the queue"),
199                              NULL);
200   }
201 }
202 
SaveAttachmentsDialog(Prefs & prefs,const GroupPrefs & group_prefs,const ServerRank & server_rank,const GroupServer & group_server,ArticleCache & cache,ArticleRead & read,Queue & queue,GtkWindow * parent_window,const Quark & group,const std::vector<Article> & articles,const TaskArticle::SaveOptions & options,const char * filename)203 SaveAttachmentsDialog :: SaveAttachmentsDialog
204                          (Prefs                       & prefs,
205                           const GroupPrefs            & group_prefs,
206                           const ServerRank            & server_rank,
207                           const GroupServer           & group_server,
208                           ArticleCache                & cache,
209                           ArticleRead                 & read,
210                           Queue                       & queue,
211                           GtkWindow                   * parent_window,
212                           const Quark                 & group,
213                           const std::vector<Article>  & articles,
214                           const TaskArticle::SaveOptions & options,
215                           const char                  * filename):
216   _prefs(prefs),
217   _server_rank (server_rank),
218   _group_server (group_server),
219   _cache (cache),
220   _read (read),
221   _queue (queue),
222   _group (group),
223   _root (0),
224   _save_custom_path_radio (0),
225   _save_group_path_radio (0),
226   _articles (articles),
227   _options (options),
228   _filename (filename)
229 {
230   GtkWidget * dialog = gtk_dialog_new_with_buttons (_("Pan: Save Attachments"),
231                                                     parent_window,
232                                                     GTK_DIALOG_DESTROY_WITH_PARENT,
233                                                     NULL, NULL);
234   gtk_dialog_add_button (GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
235   GtkWidget * focus = gtk_dialog_add_button (GTK_DIALOG(dialog), GTK_STOCK_SAVE, GTK_RESPONSE_OK);
236   gtk_window_set_role (GTK_WINDOW(dialog), "pan-save-attachments-dialog");
237   gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_OK);
238   g_signal_connect (dialog, "response", G_CALLBACK(response_cb), this);
239   g_signal_connect_swapped (dialog, "destroy", G_CALLBACK(delete_save_dialog), this);
240 
241   const std::string group_path (group_prefs.get_string (group, "default-group-save-path", ""));
242   const bool have_group_default (!group_path.empty());
243 
244   int row (0);
245   GtkWidget *t, *w, *h;
246   t = HIG :: workarea_create ();
247 
248   HIG :: workarea_add_section_spacer (t, row, have_group_default ? 4 : 3);
249 
250   if (path_mode==PATH_GROUP && !have_group_default)
251       path_mode = PATH_ENTRY;
252 
253   h = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
254   if (have_group_default) {
255     w = _save_custom_path_radio = gtk_radio_button_new_with_mnemonic (NULL, _("_Location:"));
256     gtk_box_pack_start (GTK_BOX(h), w, false, false, 0);
257     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(w), path_mode==PATH_ENTRY);
258   }
259   w = _save_path_entry = file_entry_new (_("Save Articles"));
260   gtk_box_pack_start (GTK_BOX(h), w, true, true, 0);
261   std::string path (_prefs.get_string ("default-save-attachments-path", ""));
262   if (path.empty())
263     path = g_get_home_dir ();
264   file_entry_set (w, path.c_str());
265   g_signal_connect (file_entry_gtk_entry(w), "changed", G_CALLBACK(entry_changed_cb), _save_custom_path_radio);
266   gtk_widget_set_size_request (GTK_WIDGET(w), 400, 0);
267   w = gtk_button_new_from_stock (GTK_STOCK_HELP);
268   gtk_box_pack_start (GTK_BOX(h), w, false, false, 0);
269   g_signal_connect_swapped (w, "clicked", G_CALLBACK (show_group_substitution_help_dialog), dialog);
270   if (have_group_default)
271     HIG :: workarea_add_wide_control (t, &row, h);
272   else
273     HIG :: workarea_add_row (t, &row, _("_Location:"), h, file_entry_gtk_entry(_save_path_entry));
274 
275   if (have_group_default) {
276     char * pch = g_strdup_printf (_("_Group’s path: %s"), group_path.c_str());
277     w = gtk_radio_button_new_with_mnemonic_from_widget (GTK_RADIO_BUTTON(_save_custom_path_radio), pch);
278     _save_group_path_radio = w;
279     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(w), path_mode==PATH_GROUP);
280     g_object_set_data_full (G_OBJECT(w), "default-group-save-path", g_strdup(group_path.c_str()), g_free);
281     g_free (pch);
282     HIG :: workarea_add_wide_control (t, &row, w);
283   }
284 
285   w = create_priority_combo_box (prefs);
286   g_signal_connect (w,  "changed", G_CALLBACK(priority_combo_box_selection_changed), &prefs);
287   w = HIG :: workarea_add_row (t, &row, _("_Priority:"), w);
288 
289   gtk_widget_show_all (t);
290   gtk_box_pack_start (GTK_BOX( gtk_dialog_get_content_area( GTK_DIALOG(dialog))), t, true, true, 0);
291   gtk_widget_grab_focus (focus);
292   _root = dialog;
293 }
294