1 /**********************************************************************
2  *
3  *   FreeDoko a Doppelkopf-Game
4  *
5  *   Copyright (C) 2001 – 2018 by Diether Knof and Borg Enders
6  *
7  *   This program is free software; you can redistribute it and/or
8  *   modify it under the terms of the GNU General Public License as
9  *   published by the Free Software Foundation; either version 2 of
10  *   the License, or (at your option) any later version.
11  *
12  *   This program is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *   You can find this license in the file 'gpl.txt'.
17  *
18  *   You should have received a copy of the GNU General Public License
19  *   along with this program; if not, write to the Free Software
20  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
21  *   MA  02111-1307  USA
22  *
23  *  Contact:
24  *    Diether Knof dknof@posteo.de
25  *
26  *********************************************************************/
27 
28 #include "constants.h"
29 
30 #ifdef USE_UI_GTKMM
31 
32 #include "preferences.h"
33 #include "preferences/cards_order.h"
34 #include "preferences/cardset.h"
35 #include "preferences/cards_back.h"
36 #include "preferences/iconset.h"
37 #include "preferences/background.h"
38 
39 #include "ui.h"
40 #include "cards.h"
41 #include "main_window.h"
42 #include "menu.h"
43 
44 #include "../../misc/preferences.h"
45 #ifdef WINDOWS
46 #include "../../utils/string.h"
47 #endif
48 
49 #include "widgets/labelspinbutton.h"
50 #include "widgets/filemenu_extension.h"
51 #include "widgets/filemenu_file.h"
52 #include <gtkmm/notebook.h>
53 #include <gtkmm/label.h>
54 #include <gtkmm/entry.h>
55 #include <gtkmm/checkbutton.h>
56 #include <gtkmm/checkmenuitem.h>
57 #include <gtkmm/separatormenuitem.h>
58 #include <gtkmm/fontchooserdialog.h>
59 #include <gtkmm/colorchooserdialog.h>
60 namespace UI_GTKMM_NS {
61 
62 /** constructor
63  **
64  ** @param    parent   the parent object
65  **/
Preferences(Base * const parent)66 Preferences::Preferences(Base* const parent) :
67   Base(parent),
68   StickyDialog("FreeDoko – " + _("Window::preferences"), false)
69 {
70   this->ui->add_window(*this);
71 
72 #ifdef WORKAROUND
73   { // name: change to utf8
74     auto const name = UI_GTKMM::to_utf8(::preferences(::Preferences::Type::name));
75     ::preferences.set(::Preferences::Type::name, name);
76   } // name: change to utf8
77 #endif
78 
79   this->group_notebook = Gtk::manage(new Gtk::Notebook());
80   this->group_notebook->set_tab_pos(Gtk::POS_LEFT);
81 
82   this->create_backup();
83 
84   this->signal_realize().connect(sigc::mem_fun(*this, &Preferences::init));
85   this->signal_show().connect(sigc::mem_fun(*this, &Preferences::update_all));
86 
87 #ifndef GLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED
88   this->signal_key_press_event().connect(sigc::mem_fun(*this, &Preferences::on_key_press_event));
89 #endif
90 } // Preferences::Preferences(Base* parent)
91 
92 /** destruktor
93  **/
~Preferences()94 Preferences::~Preferences()
95 {
96   for (auto widget : this->type_bool)
97     delete static_cast<::Preferences::Type::Bool*>(widget.second->steal_data("type"));
98 
99   delete this->language_menu;
100   for (auto selector : this->font_chooser) {
101     delete static_cast<int*>(selector.second->steal_data("type"));
102     delete selector.second;
103   } // for (selector : this->font_chooser)
104   for (auto selector : this->color_chooser) {
105     delete static_cast<int*>(selector.second->steal_data("type"));
106     delete selector.second;
107   } // for (selector : this->color_chooser)
108 } // Preferences::~Preferences()
109 
110 /** creates all subelements
111  **/
112 void
init()113 Preferences::init()
114 {
115   this->set_icon(this->ui->icon);
116 
117   this->reset_button = Gtk::manage(new Gtk::Button(_("Button::reset")));
118   this->reset_button->set_image_from_icon_name("edit-undo");
119   this->reset_button->set_always_show_image();
120   this->add_action_widget(*this->reset_button, Gtk::RESPONSE_REJECT);
121   this->reset_button->show();
122   this->reset_button->signal_clicked().connect(sigc::mem_fun(*this,
123                                                              &Preferences::reset)
124                                               );
125 
126   auto close_button = add_close_button(*this);
127   close_button->signal_clicked().connect(sigc::mem_fun(*this,
128                                                        &Preferences::save)
129                                         );
130 
131 
132 
133   this->language_menu = new Gtk::Menu();
134 
135   { // create the buttons
136     for (auto t : ::Preferences::Type::bool_list) {
137       this->type_bool[t] = Gtk::manage(new Gtk::CheckButton(_(t)));
138       //this->type_bool.back()->set_halign(Gtk::ALIGN_CENTER);
139       this->type_bool[t]->set_data("type", new ::Preferences::Type::Bool(t));
140       this->type_bool[t]->signal_toggled().connect(sigc::bind<int>(sigc::mem_fun(*this, &Preferences::changed),
141                                                                        t)
142                                                       );
143     } // for (t : ::Preferences::Type::bool_list)
144     for (auto t : ::Preferences::Type::unsigned_list) {
145       this->type_unsigned[t] = Gtk::manage(new Gtk::LabelSpinButton(_(t)));
146       //this->type_unsigned.back()->set_halign(Gtk::ALIGN_CENTER);
147       this->type_unsigned[t]->set_data("type", new ::Preferences::Type::Unsigned(t));
148       this->type_unsigned[t]->signal_value_changed().connect(sigc::bind<int>(sigc::mem_fun(*this, &Preferences::changed),
149                                                                                  t)
150                                                                 );
151       switch (t) {
152       case ::Preferences::Type::card_play_delay:
153       case ::Preferences::Type::full_trick_close_delay:
154       case ::Preferences::Type::gametype_window_close_delay:
155       case ::Preferences::Type::marriage_window_close_delay:
156       case ::Preferences::Type::genscher_window_close_delay:
157       case ::Preferences::Type::announcement_window_close_delay:
158       case ::Preferences::Type::swines_window_close_delay:
159         // make the precision 1/1000 seconds
160         this->type_unsigned[t]->get_spin_button()->set_digits(1);
161         this->type_unsigned[t]->get_spin_button()->set_increments(0.1, 1);
162         break;
163 #ifdef USE_THREADS
164       case ::Preferences::Type::threads_max:
165 #endif
166       case ::Preferences::Type::table_rotation:
167       case ::Preferences::Type::cards_height:
168       case ::Preferences::Type::ui_main_window_pos_x:
169       case ::Preferences::Type::ui_main_window_pos_y:
170       case ::Preferences::Type::ui_main_window_width:
171       case ::Preferences::Type::ui_main_window_height:
172         break;
173       } // switch(t)
174     } // for (t : ::Preferences::Type::unsigned_list)
175     for (auto t : ::Preferences::Type::string_list) {
176       this->type_string_label[t] = Gtk::manage(new Gtk::Label(_(t) + ":"));
177 
178       this->type_string_label[t]->set_data("type", new ::Preferences::Type::String(t));
179 
180       switch (t) {
181       case ::Preferences::Type::name:
182         this->type_string[t] = Gtk::manage(new Gtk::Entry());
183         dynamic_cast<Gtk::Entry*>(this->type_string[t])->signal_focus_out_event().connect(sigc::bind<int>(sigc::mem_fun(*this, &Preferences::focus_out_event), t));
184         break;
185 
186       case ::Preferences::Type::language:
187         this->type_string[t] = Gtk::manage(new Gtk::Button(_(t)));
188         dynamic_cast<Gtk::Button*>(this->type_string[t])->signal_clicked().connect(sigc::mem_fun(*this->language_menu, &Widget::show_all));
189         //	  dynamic_cast<Gtk::Button*>(this->type_string[t])->signal_clicked().connect(sigc::mem_fun(*this, &Preferences::language_menu_create));
190         dynamic_cast<Gtk::Button*>(this->type_string[t])->signal_clicked().connect(sigc::bind<bool>(sigc::mem_fun(*this, &Preferences::language_menu_create), true));
191         // a direct signal for popping up the menu does not work
192         //dynamic_cast<Gtk::Button*>(this->type_string[t])->signal_clicked().connect(sigc::bind<guint, guint32>(sigc::mem_fun(*this->language_menu, &Gtk::Menu::popup), 0, 0));
193         break;
194 
195       case ::Preferences::Type::cardset:
196       case ::Preferences::Type::cards_back:
197       case ::Preferences::Type::iconset:
198       case ::Preferences::Type::background:
199         break;
200 
201       case ::Preferences::Type::name_font:
202       case ::Preferences::Type::trickpile_points_font: {
203         auto button = Gtk::manage(new Gtk::Button(_(t)));
204         this->type_string[t] = button;
205         this->font_chooser[t] = new Gtk::FontChooserDialog(_("font selector"));
206         this->font_chooser[t]->set_data("type", new int(t));
207         button->set_data("font selector", this->font_chooser[t]);
208         button->signal_clicked().connect(sigc::mem_fun0(*this->font_chooser[t], &Gtk::Window::present));
209         break;
210       }
211 
212       case ::Preferences::Type::name_font_color:
213       case ::Preferences::Type::name_active_font_color:
214       case ::Preferences::Type::name_reservation_font_color:
215       case ::Preferences::Type::trickpile_points_font_color:
216       case ::Preferences::Type::poverty_shift_arrow_color: {
217         auto button = Gtk::manage(new Gtk::Button(_(t)));
218         this->type_string[t] = button;
219         this->color_chooser[t] = new Gtk::ColorChooserDialog(_("color selector"));
220         this->color_chooser[t]->set_data("type", new int(t));
221         button->set_data("color selector", this->color_chooser[t]);
222         button->signal_clicked().connect(sigc::mem_fun0(*this->color_chooser[t], &Gtk::Window::present));
223         break;
224       }
225 
226 #ifdef USE_SOUND_COMMAND
227       case ::Preferences::Type::play_sound_command:
228 #endif
229       case ::Preferences::Type::browser_command:
230         this->type_string[t] = Gtk::manage(new Gtk::Entry());
231         dynamic_cast<Gtk::Entry*>(this->type_string[t])->signal_focus_out_event().connect(sigc::bind<int>(sigc::mem_fun(*this, &Preferences::focus_out_event), t));
232         break;
233 
234       } // switch(t)
235       if (this->type_string[t]) {
236         this->type_string[t]->set_data("type", new ::Preferences::Type::String(t));
237       }
238     } // for (t : ::Preferences::Type::string_list)
239   } // create the buttons
240 
241   { // set the pages of the notebook
242     { // General
243       auto vbox = this->add_group_box("general");
244       (void)_("Preferences::Group::general");
245 
246       { // name
247         auto hbox = Gtk::manage(new Gtk::Box());
248         hbox->set_spacing(1 EX);
249         hbox->set_halign(Gtk::ALIGN_CENTER);
250         hbox->add(*this->type_string_label[::Preferences::Type::name]);
251         hbox->add(*this->type_string[::Preferences::Type::name]);
252         vbox->add(*hbox);
253       } // name
254       { // language
255         auto hbox = Gtk::manage(new Gtk::Box());
256         hbox->set_spacing(1 EX);
257         hbox->set_halign(Gtk::ALIGN_CENTER);
258         hbox->add(*this->type_string_label[::Preferences::Type::language]);
259         hbox->add(*this->type_string[::Preferences::Type::language]);
260         vbox->add(*hbox);
261       } // language
262       vbox->add(*this->type_bool[::Preferences::Type::sound]);
263 #ifdef USE_THREADS
264       vbox->add(*this->type_unsigned[::Preferences::Type::threads_max]);
265 #endif
266       vbox->add(*this->type_bool[::Preferences::Type::show_bug_report_button_in_game_finished_window]);
267       vbox->add(*this->type_bool[::Preferences::Type::save_bug_reports_on_desktop]);
268       vbox->add(*this->type_bool[::Preferences::Type::save_party_changes]);
269       vbox->add(*this->type_bool[::Preferences::Type::additional_party_settings]);
270 #ifdef USE_SOUND_COMMAND
271       { // play sound command
272         auto hbox = Gtk::manage(new Gtk::Box());
273         hbox->set_spacing(1 EX);
274         hbox->set_halign(Gtk::ALIGN_CENTER);
275         hbox->add(*this->type_string_label[::Preferences::Type::play_sound_command]);
276         hbox->add(*this->type_string[::Preferences::Type::play_sound_command]);
277         vbox->add(*hbox);
278       } // play sound command
279 #endif
280       { // browser
281         auto hbox = Gtk::manage(new Gtk::Box());
282         hbox->set_spacing(1 EX);
283         hbox->set_halign(Gtk::ALIGN_CENTER);
284         hbox->add(*this->type_string_label[::Preferences::Type::browser_command]);
285         hbox->add(*this->type_string[::Preferences::Type::browser_command]);
286         vbox->add(*hbox);
287       } // browser
288     } // General
289 
290     { // Behaviour
291       auto vbox = this->add_group_box("behaviour");
292       (void)_("Preferences::Group::behaviour");
293 
294       vbox->add(*this->type_bool[::Preferences::Type::announce_in_table]);
295       vbox->add(*this->type_unsigned[::Preferences::Type::card_play_delay]);
296     } // Behaviour
297     { // Assistance
298       auto vbox = this->add_group_box("assistance");
299       (void)_("Preferences::Group::assistance");
300 
301       vbox->add(*this->type_bool[::Preferences::Type::show_soloplayer_in_game]);
302       vbox->add(*this->type_bool[::Preferences::Type::announce_swines_automatically]);
303       vbox->add(*this->type_bool[::Preferences::Type::show_if_valid]);
304       vbox->add(*this->type_bool[::Preferences::Type::emphasize_valid_cards]);
305     } // Assistance
306     { // Help
307       auto vbox = this->add_group_box("help");
308       (void)_("Preferences::Group::help");
309 
310       vbox->add(*this->type_bool[::Preferences::Type::show_all_hands]);
311       vbox->add(*this->type_bool[::Preferences::Type::show_ai_information_hands]);
312       vbox->add(*this->type_bool[::Preferences::Type::show_known_teams_in_game]);
313       vbox->add(*this->type_bool[::Preferences::Type::show_ai_information_teams]);
314       vbox->add(*this->type_bool[::Preferences::Type::show_trickpiles_points]);
315       vbox->add(*this->type_bool[::Preferences::Type::automatic_card_suggestion]);
316     } // Help
317     { // information windows
318       auto subnotebook
319         = this->add_group_notebook("information windows");
320       (void)_("Preferences::Group::information windows");
321 
322       { // gametype window
323         auto vbox = this->add_subgroup_box(*subnotebook,
324                                            "gametype");
325         (void)_("Preferences::Subgroup::gametype");
326 
327         vbox->add(*this->type_bool[::Preferences::Type::show_gametype_window]);
328         vbox->add(*this->type_bool[::Preferences::Type::close_gametype_window_automatically]);
329         vbox->add(*this->type_unsigned[::Preferences::Type::gametype_window_close_delay]);
330       } // gametype window
331       { // full trick window
332         auto vbox = this->add_subgroup_box(*subnotebook,
333                                            "full trick");
334         (void)_("Preferences::Subgroup::full trick");
335 
336         vbox->add(*this->type_bool[::Preferences::Type::show_full_trick_window]);
337         vbox->add(*this->type_bool[::Preferences::Type::show_full_trick_window_if_special_points]);
338         vbox->add(*this->type_bool[::Preferences::Type::close_full_trick_automatically]);
339         vbox->add(*this->type_unsigned[::Preferences::Type::full_trick_close_delay]);
340       } // full trick window
341       { // marriage finding window
342         auto vbox = this->add_subgroup_box(*subnotebook,
343                                            "marriage");
344         (void)_("Preferences::Subgroup::marriage");
345 
346         vbox->add(*this->type_bool[::Preferences::Type::show_marriage_window]);
347         vbox->add(*this->type_bool[::Preferences::Type::close_marriage_window_automatically]);
348         vbox->add(*this->type_unsigned[::Preferences::Type::marriage_window_close_delay]);
349       } // marriage finding window
350       { // genscher window
351         auto vbox = this->add_subgroup_box(*subnotebook,
352                                            "genscher");
353         (void)_("Preferences::Subgroup::genscher");
354 
355         vbox->add(*this->type_bool[::Preferences::Type::show_genscher_window]);
356         vbox->add(*this->type_bool[::Preferences::Type::close_genscher_window_automatically]);
357         vbox->add(*this->type_unsigned[::Preferences::Type::genscher_window_close_delay]);
358       } // genscher window
359       { // announcement window
360         auto vbox = this->add_subgroup_box(*subnotebook,
361                                            "announcement");
362         (void)_("Preferences::Subgroup::announcement");
363 
364         vbox->add(*this->type_bool[::Preferences::Type::show_announcement_window]);
365         vbox->add(*this->type_bool[::Preferences::Type::close_announcement_window_automatically]);
366         vbox->add(*this->type_unsigned[::Preferences::Type::announcement_window_close_delay]);
367       } // announcement window
368       { // swines window
369         auto vbox = this->add_subgroup_box(*subnotebook,
370                                            "swines");
371         (void)_("Preferences::Subgroup::swines");
372 
373         vbox->add(*this->type_bool[::Preferences::Type::show_swines_window]);
374         vbox->add(*this->type_bool[::Preferences::Type::close_swines_window_automatically]);
375         vbox->add(*this->type_unsigned[::Preferences::Type::swines_window_close_delay]);
376       } // swines window
377     } // Information windows
378     {
379       auto vbox = this->add_group_box("cards order");
380       (void)_("Preferences::Group::cards order");
381       this->cards_order = make_unique<CardsOrder>(*this);
382       vbox->add(*this->cards_order);
383     }
384     { // Appearance
385       auto subnotebook = this->add_group_notebook("appearance");
386       (void)_("Preferences::Group::appearance");
387 
388       { // cardset
389         auto vbox = this->add_subgroup_box(*subnotebook,
390                                            "cardset");
391         (void)_("Preferences::Subgroup::cardset");
392         this->cardset = make_unique<Cardset>(*this);
393         vbox->set_valign(Gtk::ALIGN_FILL);
394         vbox->set_halign(Gtk::ALIGN_FILL);
395         vbox->pack_start(*this->cardset);
396       } // cardset
397       { // cards back
398         auto vbox = this->add_subgroup_box(*subnotebook,
399                                            "cards back");
400         (void)_("Preferences::Subgroup::cards back");
401         this->cards_back = make_unique<CardsBack>(*this);
402         vbox->set_valign(Gtk::ALIGN_FILL);
403         vbox->set_halign(Gtk::ALIGN_FILL);
404         vbox->pack_start(*this->cards_back);
405       } // cards back
406       { // iconset
407         auto vbox = this->add_subgroup_box(*subnotebook,
408                                            "iconset");
409         (void)_("Preferences::Subgroup::iconset");
410         this->iconset = make_unique<Iconset>(*this);
411         vbox->set_valign(Gtk::ALIGN_FILL);
412         vbox->set_halign(Gtk::ALIGN_FILL);
413         vbox->pack_start(*this->iconset);
414       } // iconset
415       { // background
416         auto vbox = this->add_subgroup_box(*subnotebook,
417                                            "background");
418         (void)_("Preferences::Subgroup::background");
419         this->background = make_unique<Background>(*this);
420         vbox->set_valign(Gtk::ALIGN_FILL);
421         vbox->set_halign(Gtk::ALIGN_FILL);
422         vbox->pack_start(*this->background);
423       } // background
424       { // table
425         auto vbox = this->add_subgroup_box(*subnotebook,
426                                            "table");
427         (void)_("Preferences::Subgroup::table");
428 
429         vbox->add(*this->type_bool[::Preferences::Type::own_hand_on_table_bottom]);
430         vbox->add(*this->type_unsigned[::Preferences::Type::table_rotation]);
431         vbox->add(*this->type_bool[::Preferences::Type::rotate_trick_cards]);
432         vbox->add(*this->type_bool[::Preferences::Type::original_cards_size]);
433         vbox->add(*this->type_unsigned[::Preferences::Type::cards_height]);
434       } // table
435       { // Fonts and colors
436         auto vbox = this->add_subgroup_box(*subnotebook,
437                                            "fonts and colors");
438         (void)_("Preferences::Subgroup::fonts and colors");
439 
440         { // the name
441           auto vbox2 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
442           vbox->set_halign(Gtk::ALIGN_CENTER);
443 
444           vbox2->add(*this->type_string[::Preferences::Type::name_font]);
445           vbox2->add(*this->type_string[::Preferences::Type::name_font_color]);
446           vbox2->add(*this->type_string[::Preferences::Type::name_active_font_color]);
447           vbox2->add(*this->type_string[::Preferences::Type::name_reservation_font_color]);
448           vbox->add(*vbox2);
449         } // the name
450 
451         { // the trickpile points
452           auto vbox2 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
453           vbox->set_halign(Gtk::ALIGN_CENTER);
454 
455 
456           vbox2->add(*this->type_string[::Preferences::Type::trickpile_points_font]);
457           vbox2->add(*this->type_string[::Preferences::Type::trickpile_points_font_color]);
458 
459           vbox->add(*vbox2);
460         } // the trickpile points
461 
462         { // poverty arrow
463           vbox->add(*this->type_string[::Preferences::Type::poverty_shift_arrow_color]);
464         } // poverty arrow
465       } // Fonts and colors
466       { // Splash screen
467         auto vbox = this->add_subgroup_box(*subnotebook,
468                                            "splash screen");
469         (void)_("Preferences::Subgroup::splash screen");
470 
471         vbox->add(*this->type_bool[::Preferences::Type::show_splash_screen]);
472         vbox->add(*this->type_bool[::Preferences::Type::splash_screen_transparent]);
473       } // Splash screen
474     } // Appearance
475   } // set the pages of the notebook
476 
477 #ifndef RELEASE
478 #ifdef DKNOF
479   { // test, whether all preferences-buttons are packed in a container
480     for (auto t : ::Preferences::Type::bool_list) {
481       if (!this->type_bool[t]->get_parent()
482           && (t != ::Preferences::Type::automatic_savings)
483           && (t != ::Preferences::Type::ui_main_window_fullscreen)
484          ) {
485         cerr << "GTKMM_DOKO::Preferences::Type::Preferences():\n"
486           << "  preference '" << to_string(t) << "' not packed\n";
487       }
488     }
489     for (auto t : ::Preferences::Type::unsigned_list) {
490       if (!this->type_unsigned[t]->get_parent()
491           && (t != ::Preferences::Type::ui_main_window_pos_x)
492           && (t != ::Preferences::Type::ui_main_window_pos_y)
493           && (t != ::Preferences::Type::ui_main_window_width)
494           && (t != ::Preferences::Type::ui_main_window_height)
495          ) {
496         cerr << "GTKMM_DOKO::Preferences::Type::Preferences():\n"
497           << "  preference '" << to_string(t)
498           << "' not packed\n";
499       }
500     }
501   } // test, whether all preference-buttons are packed in a container
502 #endif
503 #endif
504 
505   this->get_content_area()->pack_start(*this->group_notebook);
506 
507 
508   { // signals
509     for (auto i : this->font_chooser) {
510       auto selector = i.second;
511       selector->signal_response().connect(sigc::bind<Gtk::FontChooserDialog*>(sigc::mem_fun(*this, &Preferences::font_chooser_response), selector));
512     } // for (selector : this->font_chooser)
513 
514     for (auto i : this->color_chooser) {
515       auto selector = i.second;
516       auto const type
517         = *static_cast<::Preferences::Type::String*>(selector->get_data("type"));
518 
519       selector->signal_response().connect(sigc::bind<Gtk::ColorChooserDialog*>(sigc::mem_fun(*this, &Preferences::color_chooser_response), selector));
520 #ifdef TODO
521       selector->signal_color_changed().connect(sigc::bind<int>(sigc::mem_fun(*this, &Preferences::changed), type));
522 #endif
523       (void)type;
524     } // for (selector : this->color_chooser)
525   } // signals
526 
527   this->update_all();
528 
529   this->show_all_children();
530 
531 #ifdef RELEASE
532 #ifndef WINDOWS
533 #ifndef LINUX
534   this->type_bool[::Preferences::Type::save_bug_reports_on_desktop]->hide();
535 #endif
536 #endif
537 #endif // #ifdef RELEASE
538 } // void Preferences::init()
539 
540 /** adds to the group notebook a page with a vbox for group 'name'.
541  **
542  ** @param    name   name of the group
543  **
544  ** @return   created vbox for the preferences of the group
545  **/
546 Gtk::Box*
add_group_box(string const & name)547 Preferences::add_group_box(string const& name)
548 {
549   auto vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 1 EX));
550   vbox->set_halign(Gtk::ALIGN_CENTER);
551   vbox->set_valign(Gtk::ALIGN_CENTER);
552 
553   this->add_group(name, *vbox);
554 
555   return vbox;
556 } // Gtk::Box* Preferences::add_group_box(string name)
557 
558 /** adds to the group notebook a subnotebook for group 'name'.
559  **
560  ** @param    name   name of the group
561  **
562  ** @return   created notebook for the subgroups
563  **/
564 Gtk::Notebook*
add_group_notebook(string const & name)565 Preferences::add_group_notebook(string const& name)
566 {
567   auto notebook = Gtk::manage(new Gtk::Notebook());
568   this->add_group(name, *notebook);
569   notebook->set_tab_pos(Gtk::POS_LEFT);
570 
571   return notebook;
572 } // Gtk::Notebook* Preferences::add_group_notebook(string name)
573 
574 /** adds to the group notebook a page of 'widget' for group 'name'.
575  **
576  ** @param    name     name of the group
577  ** @param    widget   widget contianing the group
578  **/
579 void
add_group(string const & name,Gtk::Widget & widget)580 Preferences::add_group(string const& name, Gtk::Widget& widget)
581 {
582   widget.show();
583 
584   auto label = Gtk::manage(new Gtk::Label(_("Preferences::Group::" + name)));
585   label->show();
586 
587   this->group_notebook->append_page(widget, *label);
588   this->group_notebook->child_property_tab_expand(widget).set_value(true);
589 } // void Preferences::add_group(string name, Gtk::Widget& widget)
590 
591 /** adds to the subgroup notebook a page with a vbox for group 'name'.
592  **
593  ** @param    subnotebook   notebook to add the group
594  ** @param    name      name of the group
595  **
596  ** @return   created vbox for the preferences of the group
597  **/
598 Gtk::Box*
add_subgroup_box(Gtk::Notebook & subnotebook,string const & name)599 Preferences::add_subgroup_box(Gtk::Notebook& subnotebook,
600                               string const& name)
601 {
602   auto label = Gtk::manage(new Gtk::Label(_("Preferences::Subgroup::" + name)));
603   label->show();
604 
605   auto vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 1 EX));
606   vbox->set_halign(Gtk::ALIGN_CENTER);
607   vbox->set_valign(Gtk::ALIGN_CENTER);
608 
609   subnotebook.append_page(*vbox, *label);
610   this->group_notebook->child_property_tab_expand(*vbox).set_value(true);
611   return vbox;
612 } // Gtk::Box* Preferences::add_subgroup_box(Gtk::Notebook& subnotebook, string name)
613 
614 /** creates a backup
615  **/
616 void
create_backup()617 Preferences::create_backup()
618 {
619   this->backup_ = make_unique<::Preferences>(::preferences);
620 } // void Preferences::create_backup()
621 
622 /** @return   the backup
623  **/
624 ::Preferences const&
backup() const625 Preferences::backup() const
626 {
627   DEBUG_ASSERTION(this->backup_,
628                   "Preferences::Type::backup():\n"
629                   "  'this->backup_' == nullptr");
630 
631   return *this->backup_;
632 } // Preferences const& Preferences::backup() const
633 
634 /** save the preferences
635  **/
636 void
save()637 Preferences::save()
638 {
639   if (::preferences(::Preferences::Type::automatic_savings))
640     ::preferences.save();
641 } // void Preferences::save()
642 
643 /** resets the preferences
644  **/
645 void
reset()646 Preferences::reset()
647 {
648   if (this->backup_)
649     ::preferences = *this->backup_;
650 } // void Preferences::reset()
651 
652 /** update the sensitivity of all widgets
653  **/
654 void
sensitivity_update()655 Preferences::sensitivity_update()
656 {
657   for (auto i : this->type_bool) {
658     auto widget = i.second;
659     widget->set_sensitive(::preferences.dependencies(*static_cast<::Preferences::Type::Bool*>(widget->get_data("type"))));
660   }
661   for (auto i : this->type_unsigned) {
662     auto widget = i.second;
663     auto const type
664       = *static_cast<::Preferences::Type::Unsigned*>(widget->get_data("type"));
665 
666     widget->set_sensitive(::preferences.dependencies(type));
667     if (type == ::Preferences::Type::cards_height) {
668       // make such a limit, so that a right-click (maximum) does not lead
669       // to a limit problem
670       widget->set_range(::preferences.min(type),
671                         10 * this->ui->cards->height_original());
672     } else {
673       widget->set_range(::preferences.min(type), ::preferences.max(type));
674     }
675   } // for (widget : this->type_unsigned)
676 } // void Preferences::sensitivity_update()
677 
678 /** update all
679  **/
680 void
update_all()681 Preferences::update_all()
682 {
683   for (auto t : ::Preferences::Type::bool_list)
684     this->update(t, false);
685   for (auto t : ::Preferences::Type::unsigned_list)
686     this->update(t, false);
687   for (auto t : ::Preferences::Type::string_list)
688     this->update(t, false);
689   this->update(::Preferences::Type::cards_order, false);
690 
691   this->sensitivity_update();
692 
693   this->reset_button->set_sensitive(this->backup() != ::preferences);
694 } // void Preferences::update_all()
695 
696 /** update the preferences 'type'
697  **
698  ** @param    type   the type of the preference
699  **/
700 void
preference_update(int const type)701 Preferences::preference_update(int const type)
702 {
703   this->update(type);
704   this->sensitivity_update();
705 } // void Preferences::preference_update(int type)
706 
707 /** update the preference 'type'
708  **
709  ** @param    type      the type of the preference
710  ** @param    update_sensitivity   whether the sensitivity shall be updated
711  **    (default: true)
712  **
713  ** @todo   style of the font and color buttons
714  **/
715 void
update(int const typex,bool const update_sensitivity)716 Preferences::update(int const typex, bool const update_sensitivity)
717 {
718   if (!this->is_visible())
719     return ;
720 
721   if (update_sensitivity)
722     this->sensitivity_update();
723 
724   if (contains(::Preferences::Type::bool_list, typex)) {
725     auto const type = static_cast<::Preferences::Type::Bool>(typex);
726     this->type_bool[type]->set_active(::preferences.value(type));
727     switch (type) {
728     case ::Preferences::Type::emphasize_valid_cards:
729       this->ui->main_window->menu->emphasize_valid_cards->set_active(::preferences.value(type));
730       break;
731     case ::Preferences::Type::show_all_hands:
732       this->ui->main_window->menu->show_all_hands->set_active(::preferences.value(type));
733       break;
734     default:
735       break;
736     } // switch(type)
737   } else if (contains(::Preferences::Type::unsigned_list, typex)) {
738     auto const type = static_cast<::Preferences::Type::Unsigned>(typex);
739     switch (type) {
740     case ::Preferences::Type::card_play_delay:
741     case ::Preferences::Type::full_trick_close_delay:
742     case ::Preferences::Type::gametype_window_close_delay:
743     case ::Preferences::Type::marriage_window_close_delay:
744     case ::Preferences::Type::genscher_window_close_delay:
745     case ::Preferences::Type::announcement_window_close_delay:
746     case ::Preferences::Type::swines_window_close_delay:
747       // make one second to correspond with '1.0'
748       this->type_unsigned[type]->set_value((1.0 / 1000) * ::preferences.value(type));
749       break;
750 #ifdef USE_THREADS
751     case ::Preferences::Type::threads_max:
752 #endif
753     case ::Preferences::Type::table_rotation:
754     case ::Preferences::Type::cards_height:
755       this->type_unsigned[type]->set_value(::preferences.value(type));
756       break;
757     case ::Preferences::Type::ui_main_window_pos_x:
758     case ::Preferences::Type::ui_main_window_pos_y:
759     case ::Preferences::Type::ui_main_window_width:
760     case ::Preferences::Type::ui_main_window_height:
761       break;
762     } // switch(type)
763   } else if (contains(::Preferences::Type::string_list, typex)) {
764     auto const type = static_cast<::Preferences::Type::String>(typex);
765     switch (type) {
766     case ::Preferences::Type::name:
767       dynamic_cast<Gtk::Entry*>(this->type_string[type])->set_text(::preferences(type));
768       break;
769     case ::Preferences::Type::language: {
770       this->language_menu_create(false);
771       auto button = dynamic_cast<Gtk::Button*>(this->type_string[type]);
772       if (::preferences.value(type) == "") {
773         button->set_label(_("default"));
774       } else if (::preferences.value(type) == "de") {
775         button->set_label("deutsch");
776       } else if (::preferences.value(type) == "de-alt") {
777         button->set_label("deutsch – alt");
778       } else if (::preferences.value(type) == "en") {
779         button->set_label("english");
780       } else  {
781         button->set_label(::preferences.value(type));
782       }
783       break;
784     }
785     case ::Preferences::Type::cardset:
786     case ::Preferences::Type::cards_back:
787     case ::Preferences::Type::iconset:
788     case ::Preferences::Type::background:
789       break;
790     case ::Preferences::Type::name_font:
791     case ::Preferences::Type::trickpile_points_font:
792       static_cast<Gtk::FontChooserDialog*>(this->type_string[type]->get_data("font selector"))->set_font(::preferences(type));
793 #ifdef POSTPONED
794       // ToDo
795       // don't work :-(
796       { // change the style
797         auto style
798           = this->type_string[type]->get_style();
799 
800         style->set_font(Pango::FontDescription(::preferences(type)));
801 
802         this->type_string[type]->set_style(style);
803       } // change the style
804 #endif // #ifdef POSTPONED
805       break;
806 
807     case ::Preferences::Type::name_font_color:
808     case ::Preferences::Type::name_active_font_color:
809     case ::Preferences::Type::name_reservation_font_color:
810     case ::Preferences::Type::trickpile_points_font_color:
811     case ::Preferences::Type::poverty_shift_arrow_color:
812       static_cast<Gtk::ColorChooserDialog*>(this->type_string[type]->get_data("color selector"))->set_rgba(Gdk::RGBA(::preferences(type)));
813 #ifdef POSTPONED
814       // change the color of the text in the buttons
815       auto style
816         = this->type_string[type]->get_style();
817       // = static_cast<Gtk::Button*>(this->type_string[type])->get_child()->get_style();
818 
819       Gdk::RGBA color(::preferences(type));
820       this->ui->colormap->alloc_color(color);
821       style->set_fg(Gtk::STATE_normal, color);
822       style->set_text(Gtk::STATE_normal, color);
823       style->set_text_aa(Gtk::STATE_normal, color);
824       style->set_base(Gtk::STATE_normal, color);
825 
826       this->type_string[type]->set_style(style);
827       static_cast<Gtk::Button*>(this->type_string[type])->get_child()->set_style(style);
828 #endif // #ifdef POSTPONED
829       break;
830 
831 #ifdef USE_SOUND_COMMAND
832     case ::Preferences::Type::play_sound_command:
833 #endif
834     case ::Preferences::Type::browser_command:
835       dynamic_cast<Gtk::Entry*>(this->type_string[type])->set_text(::preferences(type));
836       break;
837     } // switch(type)
838 
839   } else if (typex == ::Preferences::Type::cards_order) {
840     this->cards_order->update();
841   } else {
842     DEBUG_ASSERTION(false,
843                     "Preferences::update(type, update_sensitivity):\n"
844                     "  type '" << typex << "' unknown.");
845   }
846 
847   if (update_sensitivity)
848     this->reset_button->set_sensitive(this->backup() != ::preferences);
849 } // void Preferences::update(int type, bool update_sensitivity = true)
850 
851 /** a preference has been changed by the user
852  **
853  ** @param    type   the type of the preference
854  **/
855 void
changed(int const typex)856 Preferences::changed(int const typex)
857 {
858   if (contains(::Preferences::Type::bool_list, typex)) {
859     auto const type = static_cast<::Preferences::Type::Bool>(typex);
860     ::preferences.set(type,
861                       this->type_bool[type]->get_active());
862   } else if (contains(::Preferences::Type::unsigned_list, typex)) {
863     auto const type = static_cast<::Preferences::Type::Unsigned>(typex);
864     if (this->type_unsigned[type]->get_realized()) {
865       switch (type) {
866       case ::Preferences::Type::card_play_delay:
867       case ::Preferences::Type::full_trick_close_delay:
868       case ::Preferences::Type::gametype_window_close_delay:
869       case ::Preferences::Type::marriage_window_close_delay:
870       case ::Preferences::Type::genscher_window_close_delay:
871       case ::Preferences::Type::announcement_window_close_delay:
872       case ::Preferences::Type::swines_window_close_delay:
873         // make one second to correspond with '1.0'
874         ::preferences.set(type,
875                           static_cast<int>(1000 * this->type_unsigned[type]->get_value()));
876         break;
877 #ifdef USE_THREADS
878       case ::Preferences::Type::threads_max:
879 #endif
880       case ::Preferences::Type::table_rotation:
881       case ::Preferences::Type::cards_height:
882         ::preferences.set(type,
883                           this->type_unsigned[type]->get_value_as_int());
884         break;
885       case ::Preferences::Type::ui_main_window_pos_x:
886       case ::Preferences::Type::ui_main_window_pos_y:
887       case ::Preferences::Type::ui_main_window_width:
888       case ::Preferences::Type::ui_main_window_height:
889         DEBUG_ASSERTION(false,
890                         "Preferences type '" + to_string(type) + " cannot be changed manually\n");
891         break;
892       } // switch(type)
893     } // if (type is realized)
894   } else if (contains(::Preferences::Type::string_list, typex)) {
895     auto const type = static_cast<::Preferences::Type::String>(typex);
896     switch (type) {
897     case ::Preferences::Type::name:
898       ::preferences.set(type,
899                         dynamic_cast<Gtk::Entry*>(this->type_string[type])->get_text());
900       break;
901     case ::Preferences::Type::language:
902       break;
903     case ::Preferences::Type::cardset:
904       // -> Cardset
905       break;
906     case ::Preferences::Type::cards_back:
907       // -> CardsBack
908       break;
909     case ::Preferences::Type::iconset:
910       // -> Iconset
911       break;
912     case ::Preferences::Type::background:
913       // -> Background
914       break;
915     case ::Preferences::Type::name_font:
916     case ::Preferences::Type::trickpile_points_font:
917       ::preferences.set(type,
918                         static_cast<Gtk::FontChooserDialog*>(this->type_string[type]->get_data("font selector"))->get_font());
919       break;
920     case ::Preferences::Type::name_font_color:
921     case ::Preferences::Type::name_active_font_color:
922     case ::Preferences::Type::name_reservation_font_color:
923     case ::Preferences::Type::trickpile_points_font_color:
924     case ::Preferences::Type::poverty_shift_arrow_color:
925       ::preferences.set(type,
926                         colorname(static_cast<Gtk::ColorChooserDialog*>(this->type_string[type]->get_data("color selector"))->get_rgba()));
927       break;
928 
929 #ifdef USE_SOUND_COMMAND
930     case ::Preferences::Type::play_sound_command:
931 #endif
932     case ::Preferences::Type::browser_command:
933       ::preferences.set(type,
934                         dynamic_cast<Gtk::Entry*>(this->type_string[type])->get_text());
935       break;
936     } // switch(type)
937   } // if (type == ())
938   // cards order is managed in 'preferences/cards_order.cpp'
939 } // void Preferences::changed(int type)
940 
941 /** a preference has been changed by the user
942  **
943  ** @param    focus   ignored
944  ** @param    type    the type of the preference
945  **/
946 bool
focus_out_event(GdkEventFocus * event,int const type)947 Preferences::focus_out_event(GdkEventFocus* event, int const type)
948 {
949   this->changed(type);
950 
951   return true;
952 } // bool Preferences::focus_out_event(GdkEventFocus* event, int type)
953 
954 /** open the cards order
955  **/
956 void
open_cards_order()957 Preferences::open_cards_order()
958 {
959   this->realize();
960   this->group_notebook->set_current_page(5);
961   this->present();
962 } // void Preferences::open_cards_order()
963 
964 /** create and show the language menu
965  **
966  ** @param    popup   whether to popup the menu (default: true)
967  **/
968 void
language_menu_create(bool const popup)969 Preferences::language_menu_create(bool const popup)
970 {
971 #if 0
972   if (this->language_menu == NULL)
973     return ;
974 #endif
975 
976   { // remove all children
977     auto children = this->language_menu->get_children();
978 
979     if (!children.empty()) {
980       for (auto child : children)
981         this->language_menu->remove(*child);
982     } // if (!children.empty())
983   } // remove all children
984 
985   // the language menu consists of the default button, a separator and
986   // the languages (taken from '::language'
987   Gtk::MenuItem* item = nullptr;
988 
989   // memory leak: the translation created in 'Ui::->translations->add' is not removed
990   item = Gtk::manage(new Gtk::MenuItem(_("default")));
991   item->signal_activate().connect(sigc::bind<string>(sigc::mem_fun(*this, &Preferences::language_selected), ""));
992   this->language_menu->add(*item);
993   item = Gtk::manage(new Gtk::SeparatorMenuItem());
994   this->language_menu->add(*item);
995 
996   vector<std::pair<string, string>> const names
997     = {{"deutsch", "de"},
998       {"deutsch - alt", "de-alt"},
999       {"english", "en"}};
1000   for (auto name : names) {
1001     item = Gtk::manage(new Gtk::MenuItem(name.first));
1002     item->signal_activate().connect(sigc::bind<string>(sigc::mem_fun(*this, &Preferences::language_selected), name.second));
1003     this->language_menu->add(*item);
1004   } // for (name : names)
1005 
1006   if (popup)
1007     this->language_menu->popup(0, 0);
1008 
1009   this->language_menu->show_all();
1010 } // void Preferences::language_menu_create(bool popup = true)
1011 
1012 /** change of the language
1013  **
1014  ** @param    language   new language
1015  **/
1016 void
language_selected(string const language)1017 Preferences::language_selected(string const language)
1018 {
1019   ::preferences.set(::Preferences::Type::language, language);
1020 } // void Preferences::language_selected(string language)
1021 
1022 /** change of the cardset
1023  **
1024  ** @param    cardset   new cardset
1025  **/
1026 void
cardset_selected(string const cardset)1027 Preferences::cardset_selected(string const cardset)
1028 {
1029   ::preferences.set(::Preferences::Type::cardset, cardset);
1030 } // void Preferences::cardset_selected(string cardset)
1031 
1032 /** the font chooser has emmited a response
1033  **
1034  ** @param    response_id     id of the response
1035  ** @param    font_chooser   the font selector
1036  **/
1037 void
font_chooser_response(int const response_id,Gtk::FontChooserDialog * const font_chooser)1038 Preferences::font_chooser_response(int const response_id,
1039                                    Gtk::FontChooserDialog* const font_chooser)
1040 {
1041   if (response_id == Gtk::RESPONSE_OK) {
1042     auto const type
1043       = *static_cast<::Preferences::Type::String*>(font_chooser->get_data("type"));
1044     font_chooser->hide();
1045     this->changed(type);
1046   } else {
1047     font_chooser->hide();
1048   }
1049 } // void Preferences::font_chooser_response(int response_id, Gtk::FontChooserDialog* font_chooser)
1050 
1051 /** the color of the preference 'type' has emmited a response
1052  **
1053  ** @param    response_id     id of the response
1054  ** @param    color_chooser   the color selector
1055  **/
1056 void
color_chooser_response(int const response_id,Gtk::ColorChooserDialog * const color_chooser)1057 Preferences::color_chooser_response(int const response_id,
1058                                     Gtk::ColorChooserDialog* const color_chooser)
1059 {
1060   if (response_id == Gtk::RESPONSE_OK) {
1061     auto const type
1062       = *static_cast<::Preferences::Type::String*>(color_chooser->get_data("type"));
1063     //color_chooser->set_rgba(Gdk::RGBA(::preferences(type)));
1064     //::preferences.set(type, color_chooser->get_rgba().to_string());
1065     color_chooser->hide();
1066     this->changed(type);
1067   } else {
1068     color_chooser->hide();
1069   }
1070 } // void Preferences::color_chooser_response(int response_id, Gtk::ColorChooserDialog* color_chooser)
1071 
1072 /** a key has been pressed
1073  ** C-o: output of the preferences on 'stdout'
1074  **
1075  ** @param    key   the key
1076  **
1077  ** @return   whether the key was managed
1078  **/
1079 bool
on_key_press_event(GdkEventKey * key)1080 Preferences::on_key_press_event(GdkEventKey* key)
1081 {
1082   bool managed = false;
1083 
1084   if ((key->state & ~GDK_SHIFT_MASK) == GDK_CONTROL_MASK) {
1085     switch (key->keyval) {
1086     case GDK_KEY_o: // ouput of the preferences
1087       cout << ::preferences;
1088       managed = true;
1089       break;
1090     } // switch (key->keyval)
1091   } // if (key->state == GDK_CONTROL_MASK)
1092 
1093   return (managed
1094           || this->StickyDialog::on_key_press_event(key)
1095           || this->ui->key_press(key));
1096 } // bool Preferences::on_key_press_event(GdkEventKey* key)
1097 
1098 } // namespace UI_GTKMM_NS
1099 
1100 #endif // #ifdef USE_UI_GTKMM
1101