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