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