1 /*
2
3 This file is from Nitrogen, an X11 background setter.
4 Copyright (C) 2006 Dave Foster & Javeed Shaikh
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20 */
21
22 #include "NWindow.h"
23 #include "Config.h"
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include "gcs-i18n.h"
27 #include "Util.h"
28 #include <algorithm>
29 #include "NPrefsWindow.h"
30
31 #ifdef USE_XINERAMA
32 #include <X11/extensions/Xinerama.h>
33 #endif
34
35 // leethax constructor
36
NWindow(SetBG * bg_setter)37 NWindow::NWindow(SetBG* bg_setter) : apply (Gtk::Stock::APPLY), btn_prefs(Gtk::Stock::PREFERENCES) {
38 this->bg_setter = bg_setter;
39
40 set_border_width (5);
41 set_default_size (450, 500);
42
43 main_vbox.set_spacing (5);
44 add (main_vbox);
45
46 // setup imagecombos
47 this->setup_select_boxes();
48
49 // bottom hbox
50 bot_hbox.set_spacing (5);
51 bot_hbox.pack_start (select_mode, FALSE, FALSE, 0);
52 bot_hbox.pack_start (select_display, FALSE, FALSE, 0);
53 bot_hbox.pack_start(button_bgcolor, FALSE, FALSE, 0);
54
55 bot_hbox.pack_end(apply, FALSE, FALSE, 0);
56 bot_hbox.pack_end(btn_prefs, FALSE, FALSE, 0);
57
58 // add to main box
59 main_vbox.pack_start (view, TRUE, TRUE, 0);
60 main_vbox.pack_start (bot_hbox, FALSE, FALSE, 0);
61
62 // signals
63 view.signal_selected.connect(sigc::mem_fun(*this, &NWindow::sighandle_dblclick_item));
64 apply.signal_clicked ().connect (sigc::mem_fun(*this, &NWindow::sighandle_click_apply));
65 btn_prefs.signal_clicked().connect(sigc::mem_fun(*this, &NWindow::sighandle_btn_prefs));
66
67 // set icon
68 try {
69 Glib::RefPtr<Gtk::IconTheme> icontheme = Gtk::IconTheme::get_default();
70 std::vector<Glib::RefPtr<Gdk::Pixbuf> > vec;
71 vec.push_back(icontheme->load_icon("nitrogen", 16, Gtk::ICON_LOOKUP_NO_SVG));
72 vec.push_back(icontheme->load_icon("nitrogen", 22, Gtk::ICON_LOOKUP_NO_SVG));
73 vec.push_back(icontheme->load_icon("nitrogen", 32, Gtk::ICON_LOOKUP_NO_SVG));
74 vec.push_back(icontheme->load_icon("nitrogen", 48, Gtk::ICON_LOOKUP_NO_SVG));
75 vec.push_back(icontheme->load_icon("nitrogen", 128, Gtk::ICON_LOOKUP_NO_SVG));
76 Glib::ListHandle<Glib::RefPtr<Gdk::Pixbuf> > lister(vec);
77
78 this->set_icon_list(lister);
79 } catch (Gtk::IconThemeError e) {
80 // don't even worry about it!
81 }
82
83 // accel group for keyboard shortcuts
84 // unfortunately we have to basically make a menu which we never add to the UI
85 m_action_group = Gtk::ActionGroup::create();
86 m_action_group->add(Gtk::Action::create("FileMenu", ""));
87 m_action_group->add(Gtk::Action::create("Quit", Gtk::Stock::QUIT),
88 Gtk::AccelKey("<control>Q"),
89 sigc::mem_fun(*this, &NWindow::sighandle_accel_quit));
90
91 m_action_group->add(Gtk::Action::create("Close", Gtk::Stock::CLOSE),
92 Gtk::AccelKey("<control>W"),
93 sigc::mem_fun(*this, &NWindow::sighandle_accel_quit));
94
95 m_action_group->add(Gtk::Action::create("Random", Gtk::Stock::MEDIA_NEXT),
96 Gtk::AccelKey("<control>R"),
97 sigc::mem_fun(*this, &NWindow::sighandle_random));
98
99 m_ui_manager = Gtk::UIManager::create();
100 m_ui_manager->insert_action_group(m_action_group);
101
102 add_accel_group(m_ui_manager->get_accel_group());
103
104 Glib::ustring ui = "<ui>"
105 "<menubar name='MenuBar'>"
106 "<menu action='FileMenu'>"
107 "<menuitem action='Random' />"
108 "<menuitem action='Close' />"
109 "<menuitem action='Quit' />"
110 "</menu>"
111 "</menubar>"
112 "</ui>";
113 m_ui_manager->add_ui_from_string(ui);
114
115 m_dirty = false;
116 }
117
118 // shows all of our widgets
119
show(void)120 void NWindow::show (void) {
121 view.show();
122 select_mode.show();
123 // show only if > 1 entry in box
124 if (this->map_displays.size() > 1) select_display.show();
125 apply.show();
126 bot_hbox.show();
127 main_vbox.show();
128 button_bgcolor.show();
129 btn_prefs.show();
130
131 this->set_title("Nitrogen");
132 }
133
134 /**
135 * Handles the user pressing Ctrl+Q or Ctrl+W, standard quit buttons.
136 */
sighandle_accel_quit()137 void NWindow::sighandle_accel_quit() {
138 if (!handle_exit_request())
139 hide();
140 }
141
142 /**
143 * Handles the user pressing Ctrl+R to get a random image.
144 */
sighandle_random()145 void NWindow::sighandle_random() {
146 Glib::Rand rand;
147
148 guint size = view.store->children().size();
149 guint idx = rand.get_int_range(0, size);
150
151 Glib::ustring tidx = Glib::ustring::compose("%1", idx);
152 Gtk::TreeModel::Path path(tidx);
153 Gtk::TreeModel::const_iterator iter = view.store->get_iter(path);
154
155 view.set_selected(path, &iter);
156 }
157
158 /**
159 * Handles the user double clicking on a row in the view.
160 */
sighandle_dblclick_item(const Gtk::TreeModel::Path & path)161 void NWindow::sighandle_dblclick_item (const Gtk::TreeModel::Path& path) {
162
163 // find out which image was double clicked
164 Gtk::TreeModel::iterator iter = (view.store)->get_iter(path);
165 Gtk::TreeModel::Row row = *iter;
166
167 // preview - set dirty flag, if setter says we should
168 m_dirty = this->set_bg(row[view.record.Filename]);
169 }
170
171 /**
172 * Handles the user pressing the apply button.
173 */
sighandle_click_apply(void)174 void NWindow::sighandle_click_apply (void) {
175 this->apply_bg();
176 }
177
178 /**
179 * Performs the apply action
180 * Grabs the selected items and
181 * calls set_bg on it. It also saves the bg and closes the application if
182 * the app is not multiheaded, or the full xin desktop is selected.
183 */
apply_bg()184 void NWindow::apply_bg () {
185
186 // find out which image is currently selected
187 Gtk::TreeModel::iterator iter = view.get_selected();
188 if (!iter)
189 return;
190
191 Gtk::TreeModel::Row row = *iter;
192 Glib::ustring file = row[view.record.Filename];
193 bool saveToConfig = this->set_bg(file);
194
195 // apply - remove dirty flag
196 m_dirty = false;
197
198 SetBG::SetMode mode = SetBG::string_to_mode( this->select_mode.get_active_data() );
199 Glib::ustring thedisp = this->select_display.get_active_data();
200 Gdk::Color bgcolor = this->button_bgcolor.get_color();
201
202 // save
203 if (saveToConfig)
204 Config::get_instance()->set_bg(thedisp, file, mode, bgcolor);
205
206 // tell the bg setter to forget about the first pixmap
207 bg_setter->clear_first_pixmaps();
208
209 // tell the row that he's now on thedisp
210 row[view.record.CurBGOnDisp] = thedisp;
211
212 // reload config "cache"
213 view.load_map_setbgs();
214
215 // find items removed by this config set. typically 0 or 1 items, which is the
216 // old background on thedisp, but could be 2 items if xinerama individual bgs
217 // are replaced by the fullscreen xinerama.
218 for (Gtk::TreeIter i = view.store->children().begin(); i != view.store->children().end(); i++)
219 {
220 Glib::ustring curbgondisp = (*i)[view.record.CurBGOnDisp];
221 if (curbgondisp == "")
222 continue;
223
224 std::map<Glib::ustring, Glib::ustring>::iterator mapiter = view.map_setbgs.find(curbgondisp);
225
226 // checking for fullscreen xinerama with several screens end
227 if( mapiter == view.map_setbgs.end() )
228 break;
229
230 // if filenames don't match, this row must be blanked out!
231 Glib::ustring filename = (*i)[view.record.Filename];
232 if (filename != (*mapiter).second)
233 {
234 (*i)[view.record.CurBGOnDisp] = "";
235 (*i)[view.record.Description] = Glib::ustring(filename, filename.rfind("/")+1);
236 }
237 else
238 (*i)[view.record.Description] = Util::make_current_set_string(this, filename, (*mapiter).first);
239 }
240 }
241
242 /**
243 * Common handler for window delete or key accels.
244 *
245 * Prompts the user to save if necessary.
246 */
handle_exit_request()247 bool NWindow::handle_exit_request()
248 {
249 if (m_dirty)
250 {
251 int result;
252 Gtk::MessageDialog dialog(*this,
253 _("You previewed an image without applying it, apply?"), false,
254 Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE, true);
255
256 dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
257 dialog.add_button(Gtk::Stock::NO, Gtk::RESPONSE_NO);
258 dialog.add_button(Gtk::Stock::YES, Gtk::RESPONSE_YES);
259
260 dialog.set_default_response(Gtk::RESPONSE_YES);
261 result = dialog.run();
262
263 switch (result)
264 {
265 case Gtk::RESPONSE_YES:
266 this->apply_bg();
267 break;
268 case Gtk::RESPONSE_NO:
269 bg_setter->reset_first_pixmaps();
270 break;
271 case Gtk::RESPONSE_CANCEL:
272 case Gtk::RESPONSE_DELETE_EVENT:
273 return true;
274 };
275 }
276
277 return false;
278 }
279
on_delete_event(GdkEventAny * event)280 bool NWindow::on_delete_event(GdkEventAny *event)
281 {
282 return handle_exit_request();
283 }
284
285 /**
286 * Queries the necessary widgets to get the data needed to set a bg. *
287 * @param file The file to set the bg to
288 *
289 * @returns If the dirty flag should be set or not
290 */
set_bg(const Glib::ustring file)291 bool NWindow::set_bg(const Glib::ustring file) {
292
293 // get the data from the active items
294 SetBG::SetMode mode = SetBG::string_to_mode(this->select_mode.get_active_data());
295 Glib::ustring thedisp = this->select_display.get_active_data();
296 Gdk::Color bgcolor = this->button_bgcolor.get_color();
297
298 // set it
299 bg_setter->set_bg(thedisp, file, mode, bgcolor);
300
301 return bg_setter->save_to_config();
302 }
303
304 // leethax destructor
~NWindow()305 NWindow::~NWindow () {}
306
307 /**
308 * Creates our ImageCombo boxes
309 */
setup_select_boxes()310 void NWindow::setup_select_boxes() {
311
312 Glib::RefPtr<Gtk::IconTheme> icontheme = Gtk::IconTheme::get_default();
313 Glib::RefPtr<Gdk::Pixbuf> icon, genericicon, video_display_icon;
314
315 try {
316 genericicon = icontheme->load_icon("image-x-generic", 16, Gtk::ICON_LOOKUP_NO_SVG);
317 } catch (Gtk::IconThemeError e) {
318 genericicon = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, 16, 16);
319 }
320
321 try {
322 video_display_icon = icontheme->load_icon("video-display", 16, Gtk::ICON_LOOKUP_NO_SVG);
323 } catch (Gtk::IconThemeError e) {
324 video_display_icon = genericicon;
325 }
326
327 // modes
328 icon = genericicon;
329 this->select_mode.add_image_row( icon, _("Automatic"), SetBG::mode_to_string(SetBG::SET_AUTO), true );
330 try {
331 icon = icontheme->load_icon("wallpaper-scaled", 16, Gtk::ICON_LOOKUP_NO_SVG);
332 if (!icon) icon = genericicon;
333 } catch (Gtk::IconThemeError e) {
334 icon = genericicon;
335 }
336 this->select_mode.add_image_row( icon, _("Scaled"), SetBG::mode_to_string(SetBG::SET_SCALE), false );
337
338 try {
339 icon = icontheme->load_icon("wallpaper-centered", 16, Gtk::ICON_LOOKUP_NO_SVG);
340 if (!icon) icon = genericicon;
341 } catch (Gtk::IconThemeError e) {
342 icon = genericicon;
343 }
344 this->select_mode.add_image_row( icon, _("Centered"), SetBG::mode_to_string(SetBG::SET_CENTER), false );
345
346 try {
347 icon = icontheme->load_icon("wallpaper-tiled", 16, Gtk::ICON_LOOKUP_NO_SVG);
348 if (!icon) icon = genericicon;
349 } catch (Gtk::IconThemeError e) {
350 icon = genericicon;
351 }
352 this->select_mode.add_image_row( icon, _("Tiled"), SetBG::mode_to_string(SetBG::SET_TILE), false );
353
354 try {
355 icon = icontheme->load_icon("wallpaper-zoomed", 16, Gtk::ICON_LOOKUP_NO_SVG);
356 if (!icon) icon = genericicon;
357 } catch (Gtk::IconThemeError e) {
358 icon = genericicon;
359 }
360
361 this->select_mode.add_image_row( icon, _("Zoomed"), SetBG::mode_to_string(SetBG::SET_ZOOM), false );
362 this->select_mode.add_image_row( icon, _("Zoomed Fill"), SetBG::mode_to_string(SetBG::SET_ZOOM_FILL), false );
363
364
365 // @TODO GET ALL THIS INTEL FROM THE SETTER
366 //
367 // displays
368 map_displays = this->bg_setter->get_active_displays();
369
370 for (std::map<Glib::ustring, Glib::ustring>::const_iterator i = map_displays.begin(); i != map_displays.end(); i++) {
371 this->select_display.add_image_row( video_display_icon, (*i).second, (*i).first, false);
372 }
373
374 return;
375 }
376
377 /**
378 * Sets the file selection, mode combo box, and color button to appropriate default values, based on
379 * what is in the configuration file.
380 */
set_default_selections()381 void NWindow::set_default_selections()
382 {
383 // grab the current config (if there is one)
384 Config *cfg = Config::get_instance();
385 std::vector<Glib::ustring> cfg_displays;
386 if(cfg->get_bg_groups(cfg_displays))
387 {
388 SetBG::SetMode m;
389 Gdk::Color c;
390 Glib::ustring default_selection;
391 Glib::ustring file;
392
393 if (find(cfg_displays.begin(), cfg_displays.end(), this->bg_setter->get_fullscreen_key()) != cfg_displays.end())
394 default_selection = this->bg_setter->get_fullscreen_key();
395 else {
396 // iterate the displays we know until we find the first one set
397 std::map<Glib::ustring, Glib::ustring> known_disps = this->bg_setter->get_active_displays();
398 for (std::map<Glib::ustring, Glib::ustring>::const_iterator i = known_disps.begin(); i != known_disps.end(); i++)
399 {
400 if (find(cfg_displays.begin(), cfg_displays.end(), (*i).first) != cfg_displays.end())
401 {
402 default_selection = (*i).first;
403 break;
404 }
405 }
406 }
407
408 // make sure whatever we came up with is in the config file, if not, just return
409 if (find(cfg_displays.begin(), cfg_displays.end(), default_selection) == cfg_displays.end())
410 return;
411
412 if (!cfg->get_bg(default_selection, file, m, c)) {
413 // failed. return?
414 return;
415 }
416
417 // set em!
418 button_bgcolor.set_color(c);
419 select_mode.select_value( SetBG::mode_to_string(m) );
420
421 // iterate through filename list
422 for (Gtk::TreeIter iter = view.store->children().begin(); iter != view.store->children().end(); iter++)
423 {
424 if ( (*iter)[view.record.CurBGOnDisp] == default_selection) {
425 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(view, &Thumbview::select), new Gtk::TreeIter(iter)), 100);
426 break;
427 }
428 }
429 }
430 }
431
432 /**
433 * Sets the display combobox to the given display value.
434 *
435 * There will always be a selected item as long as there are items after calling this.
436 *
437 * @param display The display index to use. If not found, use first available.
438 */
set_default_display(int display)439 void NWindow::set_default_display(int display)
440 {
441 if (!select_display.select_value(this->bg_setter->make_display_key(display)))
442 select_display.set_active(0);
443 }
444
445 /////////////////////////////////////////////////////////////////////////////
446
447 /**
448 * Handles the preferences button being pressed.
449 * Shows the preferences dialog and updates the view as needed.
450 */
sighandle_btn_prefs()451 void NWindow::sighandle_btn_prefs()
452 {
453 Config *cfg = Config::get_instance();
454 Config *clone = cfg->clone();
455
456 NPrefsWindow *nw = new NPrefsWindow(*this, clone);
457 int resp = nw->run();
458
459 if (resp == Gtk::RESPONSE_OK)
460 {
461 // figure out what directories to reload and what directories to not reload!
462 // do this before we reload the main config!
463 //
464 // if the recurse flag changed, we need to unload/reload everything
465 bool recurse_changed = cfg->get_recurse() != clone->get_recurse();
466
467 VecStrs vec_load;
468 VecStrs vec_unload;
469
470 VecStrs vec_cfg_dirs = cfg->get_dirs();
471 VecStrs vec_clone_dirs = clone->get_dirs();
472 for (VecStrs::iterator i = vec_cfg_dirs.begin(); i != vec_cfg_dirs.end(); i++)
473 if (recurse_changed || find(vec_clone_dirs.begin(), vec_clone_dirs.end(), *i) == vec_clone_dirs.end())
474 vec_unload.push_back(*i);
475
476 for (VecStrs::iterator i = vec_clone_dirs.begin(); i != vec_clone_dirs.end(); i++)
477 if (recurse_changed || find(vec_cfg_dirs.begin(), vec_cfg_dirs.end(), *i) == vec_cfg_dirs.end())
478 vec_load.push_back(*i);
479
480 cfg->load_cfg(); // tells the global instance to reload itself from disk, which the prefs dialog
481 // told our clone to save to
482 view.set_current_display_mode(cfg->get_display_mode());
483 view.set_icon_captions(cfg->get_icon_captions());
484
485 for (VecStrs::iterator i = vec_unload.begin(); i != vec_unload.end(); i++)
486 view.unload_dir(*i);
487
488 for (VecStrs::iterator i = vec_load.begin(); i != vec_load.end(); i++)
489 view.load_dir(*i);
490
491 view.set_sort_mode(cfg->get_sort_mode());
492 }
493
494 }
495
496