1 //  Copyright (C) 2009, 2010, 2011, 2012, 2014, 2015, 2020 Ben Asselstine
2 //
3 //  This program is free software; you can redistribute it and/or modify
4 //  it under the terms of the GNU General Public License as published by
5 //  the Free Software Foundation; either version 3 of the License, or
6 //  (at your option) any later version.
7 //
8 //  This program is distributed in the hope that it will be useful,
9 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 //  GNU Library General Public License for more details.
12 //
13 //  You should have received a copy of the GNU General Public License
14 //  along with this program; if not, write to the Free Software
15 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16 //  02110-1301, USA.
17 
18 #include <config.h>
19 
20 #include <iostream>
21 #include <iomanip>
22 #include <assert.h>
23 #include <string.h>
24 #include <errno.h>
25 
26 #include <sigc++/functors/mem_fun.h>
27 #include <sigc++/functors/ptr_fun.h>
28 
29 #include <gtkmm.h>
30 #include "cityset-window.h"
31 #include "builder-cache.h"
32 #include "cityset-info-dialog.h"
33 
34 #include "defs.h"
35 #include "File.h"
36 
37 #include "ucompose.hpp"
38 
39 #include "image-editor-dialog.h"
40 #include "ImageCache.h"
41 #include "citysetlist.h"
42 #include "editor-quit-dialog.h"
43 #include "GameMap.h"
44 #include "editor-save-changes-dialog.h"
45 #include "timed-message-dialog.h"
46 
47 #define method(x) sigc::mem_fun(*this, &CitySetWindow::x)
48 
CitySetWindow(Glib::ustring load_filename)49 CitySetWindow::CitySetWindow(Glib::ustring load_filename)
50 {
51   needs_saving = false;
52   d_cityset = NULL;
53     Glib::RefPtr<Gtk::Builder> xml =
54       BuilderCache::editor_get("cityset-window.ui");
55 
56     xml->get_widget("window", window);
57     window->set_icon_from_file(File::getVariousFile("castle_icon.png"));
58     window->signal_delete_event().connect (method(on_window_closed));
59 
60     xml->get_widget("cityset_alignment", cityset_alignment);
61     xml->get_widget("new_cityset_menuitem", new_cityset_menuitem);
62     new_cityset_menuitem->signal_activate().connect (method(on_new_cityset_activated));
63     xml->get_widget("load_cityset_menuitem", load_cityset_menuitem);
64     load_cityset_menuitem->signal_activate().connect (method(on_load_cityset_activated));
65     xml->get_widget("save_cityset_menuitem", save_cityset_menuitem);
66     save_cityset_menuitem->signal_activate().connect (method(on_save_cityset_activated));
67     xml->get_widget("save_as_menuitem", save_as_menuitem);
68     save_as_menuitem->signal_activate().connect (method(on_save_as_activated));
69     xml->get_widget("validate_cityset_menuitem", validate_cityset_menuitem);
70     validate_cityset_menuitem->signal_activate().connect
71       (method(on_validate_cityset_activated));
72     xml->get_widget("quit_menuitem", quit_menuitem);
73     quit_menuitem->signal_activate().connect (method(on_quit_activated));
74     xml->get_widget("edit_cityset_info_menuitem", edit_cityset_info_menuitem);
75     edit_cityset_info_menuitem->signal_activate().connect
76       (method(on_edit_cityset_info_activated));
77     xml->get_widget ("help_about_menuitem", help_about_menuitem);
78     help_about_menuitem->signal_activate().connect
79        (method(on_help_about_activated));
80     xml->get_widget ("tutorial_menuitem", tutorial_menuitem);
81     tutorial_menuitem->signal_activate().connect
82       (method(on_tutorial_video_activated));
83     xml->get_widget("city_tile_width_spinbutton", city_tile_width_spinbutton);
84     city_tile_width_spinbutton->set_range (1, 4);
85     city_tile_width_spinbutton->signal_changed().connect
86       (method(on_city_tile_width_changed));
87     city_tile_width_spinbutton->signal_insert_text().connect
88       (sigc::hide(sigc::hide(method(on_city_tile_width_text_changed))));
89     xml->get_widget("ruin_tile_width_spinbutton", ruin_tile_width_spinbutton);
90     ruin_tile_width_spinbutton->set_range (1, 4);
91     ruin_tile_width_spinbutton->signal_changed().connect
92       (method(on_ruin_tile_width_changed));
93     ruin_tile_width_spinbutton->signal_insert_text().connect
94       (sigc::hide(sigc::hide(method(on_ruin_tile_width_text_changed))));
95     xml->get_widget("temple_tile_width_spinbutton",
96 		    temple_tile_width_spinbutton);
97     temple_tile_width_spinbutton->set_range (1, 4);
98     temple_tile_width_spinbutton->signal_changed().connect
99       (method(on_temple_tile_width_changed));
100     temple_tile_width_spinbutton->signal_insert_text().connect
101       (sigc::hide(sigc::hide(method(on_temple_tile_width_text_changed))));
102 
103     xml->get_widget("change_citypics_button", change_citypics_button);
104     change_citypics_button->signal_clicked().connect
105       (method(on_change_citypics_clicked));
106     xml->get_widget("change_razedcitypics_button", change_razedcitypics_button);
107     change_razedcitypics_button->signal_clicked().connect
108       (method(on_change_razedcitypics_clicked));
109     xml->get_widget("change_portpic_button", change_portpic_button);
110     change_portpic_button->signal_clicked().connect(method(on_change_portpic_clicked));
111     xml->get_widget("change_signpostpic_button", change_signpostpic_button);
112     change_signpostpic_button->signal_clicked().connect
113       (method(on_change_signpostpic_clicked));
114     xml->get_widget("change_ruinpics_button", change_ruinpics_button);
115     change_ruinpics_button->signal_clicked().connect(method(on_change_ruinpics_clicked));
116     xml->get_widget("change_templepic_button", change_templepic_button);
117     change_templepic_button->signal_clicked().connect
118       (method(on_change_templepic_clicked));
119     xml->get_widget("change_towerpics_button", change_towerpics_button);
120     change_towerpics_button->signal_clicked().connect
121       (method(on_change_towerpics_clicked));
122     xml->get_widget ("notebook", notebook);
123 
124     if (load_filename != "")
125       current_save_filename = load_filename;
126     update_cityset_panel();
127 
128     if (load_filename.empty() == false)
129       {
130 	load_cityset (load_filename);
131 	update_cityset_panel();
132         update_window_title();
133       }
134 }
135 
136 void
update_cityset_panel()137 CitySetWindow::update_cityset_panel()
138 {
139   cityset_alignment->set_sensitive(d_cityset != NULL);
140   Glib::ustring no_image = _("No image set");
141   Glib::ustring s;
142   if (d_cityset && d_cityset->getCitiesFilename().empty() == false)
143     s = d_cityset->getCitiesFilename();
144   else
145     s = no_image;
146   change_citypics_button->set_label(s);
147   if (d_cityset && d_cityset->getRazedCitiesFilename().empty() == false)
148     s = d_cityset->getRazedCitiesFilename();
149   else
150     s = no_image;
151   change_razedcitypics_button->set_label(s);
152   if (d_cityset && d_cityset->getPortFilename().empty() == false)
153     s = d_cityset->getPortFilename();
154   else
155     s = no_image;
156   change_portpic_button->set_label(s);
157   if (d_cityset && d_cityset->getSignpostFilename().empty() == false)
158     s = d_cityset->getSignpostFilename();
159   else
160     s = no_image;
161   change_signpostpic_button->set_label(s);
162   if (d_cityset && d_cityset->getRuinsFilename().empty() == false)
163     s = d_cityset->getRuinsFilename();
164   else
165     s = no_image;
166   change_ruinpics_button->set_label(s);
167   if (d_cityset && d_cityset->getTemplesFilename().empty() == false)
168     s = d_cityset->getTemplesFilename();
169   else
170     s = no_image;
171   change_templepic_button->set_label(s);
172   if (d_cityset && d_cityset->getTowersFilename().empty() == false)
173     s = d_cityset->getTowersFilename();
174   else
175     s = no_image;
176   change_towerpics_button->set_label(s);
177   if (d_cityset)
178     city_tile_width_spinbutton->set_value(d_cityset->getCityTileWidth());
179   else
180     city_tile_width_spinbutton->set_value(2);
181   if (d_cityset)
182     ruin_tile_width_spinbutton->set_value(d_cityset->getRuinTileWidth());
183   else
184     ruin_tile_width_spinbutton->set_value(1);
185   if (d_cityset)
186     temple_tile_width_spinbutton->set_value(d_cityset->getTempleTileWidth());
187   else
188     temple_tile_width_spinbutton->set_value(1);
189 }
190 
make_new_cityset()191 bool CitySetWindow::make_new_cityset ()
192 {
193   Glib::ustring msg = _("Save these changes before making a new City Set?");
194   if (check_discard (msg) == false)
195     return false;
196   save_cityset_menuitem->set_sensitive (false);
197   current_save_filename = "";
198   if (d_cityset)
199     delete d_cityset;
200 
201   guint32 num = 0;
202   Glib::ustring name =
203     Citysetlist::getInstance()->findFreeName(_("Untitled"), 100, num,
204                                              Cityset::get_default_tile_size ());
205 
206   d_cityset = new Cityset (Citysetlist::getNextAvailableId (1), name);
207   d_cityset->setNewTemporaryFile ();
208 
209   update_cityset_panel();
210   needs_saving = true;
211   update_window_title();
212   return true;
213 }
214 
on_new_cityset_activated()215 void CitySetWindow::on_new_cityset_activated()
216 {
217   make_new_cityset ();
218 }
219 
on_load_cityset_activated()220 void CitySetWindow::on_load_cityset_activated()
221 {
222   load_cityset ();
223 }
224 
on_validate_cityset_activated()225 void CitySetWindow::on_validate_cityset_activated()
226 {
227   std::list<Glib::ustring> msgs;
228   if (d_cityset == NULL)
229     return;
230   if (msgs.empty () == true)
231     {
232       bool valid = String::utrim (d_cityset->getName ()) != "";
233       if (!valid)
234         {
235           Glib::ustring s = _("The name of the City Set is invalid.");
236           msgs.push_back(s);
237         }
238     }
239   if (d_cityset->validateCitiesFilename() == false)
240     msgs.push_back(_("The cities picture is not set."));
241   if (d_cityset->validateRazedCitiesFilename() == false)
242     msgs.push_back(_("The razed cities picture is not set."));
243   if (d_cityset->validatePortFilename() == false)
244     msgs.push_back(_("The port picture is not set."));
245   if (d_cityset->validateSignpostFilename() == false)
246     msgs.push_back(_("The signpost picture is not set."));
247   if (d_cityset->validateRuinsFilename() == false)
248     msgs.push_back(_("The ruins picture is not set."));
249   if (d_cityset->validateTemplesFilename() == false)
250     msgs.push_back(_("The temple picture is not set."));
251   if (d_cityset->validateTowersFilename() == false)
252     msgs.push_back(_("The towers picture is not set."));
253   if (d_cityset->validateCityTileWidth() == false)
254     msgs.push_back(_("The tile width for temples must be over zero."));
255   if (d_cityset->validateRuinTileWidth() == false)
256     msgs.push_back(_("The tile width for ruins must be over zero."));
257   if (d_cityset->validateTempleTileWidth() == false)
258     msgs.push_back(_("The tile width for temples must be over zero."));
259   if (msgs.empty() == true && isValidName () == false)
260     msgs.push_back(_("The name of the City Set is not unique."));
261 
262   Glib::ustring msg = "";
263   for (std::list<Glib::ustring>::iterator it = msgs.begin(); it != msgs.end();
264        it++)
265     {
266       msg += (*it) + "\n";
267       break;
268     }
269 
270   if (msg == "")
271     msg = _("The City Set is valid.");
272 
273   TimedMessageDialog dialog(*window, msg, 0);
274   dialog.run_and_hide();
275 
276   return;
277 }
278 
on_save_as_activated()279 void CitySetWindow::on_save_as_activated()
280 {
281   if (check_save_valid (false))
282     save_current_cityset_file_as ();
283 }
284 
save_current_cityset_file_as()285 bool CitySetWindow::save_current_cityset_file_as ()
286 {
287   bool ret = false;
288   while (1)
289     {
290       Gtk::FileChooserDialog chooser(*window, _("Choose a Name"),
291                                      Gtk::FILE_CHOOSER_ACTION_SAVE);
292       Glib::RefPtr<Gtk::FileFilter> lwc_filter = Gtk::FileFilter::create();
293       lwc_filter->set_name(_("LordsAWar City Sets (*.lwc)"));
294       lwc_filter->add_pattern("*" + CITYSET_EXT);
295       chooser.add_filter(lwc_filter);
296       chooser.set_current_folder(File::getSetDir(CITYSET_EXT, false));
297 
298       chooser.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
299       chooser.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
300       chooser.set_default_response(Gtk::RESPONSE_ACCEPT);
301       chooser.set_do_overwrite_confirmation();
302       chooser.set_current_name (File::sanify(d_cityset->getName ()) +
303                                 CITYSET_EXT);
304 
305       chooser.show_all();
306       int res = chooser.run();
307 
308       if (res == Gtk::RESPONSE_ACCEPT)
309         {
310           Glib::ustring filename = chooser.get_filename();
311           Glib::ustring old_filename = current_save_filename;
312           guint32 old_id = d_cityset->getId ();
313           d_cityset->setId(Citysetlist::getNextAvailableId(old_id));
314 
315           ret = save_current_cityset_file(filename);
316           if (ret == false)
317             {
318               current_save_filename = old_filename;
319               d_cityset->setId(old_id);
320             }
321           else
322             {
323               save_cityset_menuitem->set_sensitive (true);
324               needs_saving = false;
325               d_cityset->created (filename);
326               Glib::ustring dir =
327                 File::add_slash_if_necessary (File::get_dirname (filename));
328               if (dir == File::getSetDir(CITYSET_EXT, false) ||
329                   dir == File::getSetDir(CITYSET_EXT, true))
330                 {
331                   //if we saved it to a standard place, update the list
332                   Citysetlist::getInstance()->add (Cityset::copy (d_cityset),
333                                                    filename);
334                   cityset_saved.emit(d_cityset->getId());
335                 }
336               update_cityset_panel();
337               update_window_title();
338             }
339         }
340       chooser.hide ();
341       if (res == Gtk::RESPONSE_CANCEL)
342         break;
343       if (ret == true)
344         break;
345     }
346   return ret;
347 }
348 
save_current_cityset_file(Glib::ustring filename)349 bool CitySetWindow::save_current_cityset_file (Glib::ustring filename)
350 {
351 
352   current_save_filename = filename;
353   if (current_save_filename.empty())
354     current_save_filename = d_cityset->getConfigurationFile(true);
355 
356   bool ok = d_cityset->save(current_save_filename, Cityset::file_extension);
357   if (ok)
358     {
359       if (Citysetlist::getInstance()->reload(d_cityset->getId()))
360         update_cityset_panel();
361       needs_saving = false;
362       update_window_title();
363       cityset_saved.emit(d_cityset->getId());
364     }
365   else
366     {
367       Glib::ustring errmsg = Glib::strerror(errno);
368       Glib::ustring msg = _("Error!  City Set could not be saved.");
369       msg += "\n" + current_save_filename + "\n" + errmsg;
370       TimedMessageDialog dialog(*window, msg, 0);
371       dialog.run_and_hide();
372     }
373   return ok;
374 }
375 
on_save_cityset_activated()376 void CitySetWindow::on_save_cityset_activated()
377 {
378   if (check_save_valid (true))
379     save_current_cityset_file();
380 }
381 
on_edit_cityset_info_activated()382 void CitySetWindow::on_edit_cityset_info_activated()
383 {
384   CitySetInfoDialog d(*window, d_cityset);
385   bool changed = d.run();
386   if (changed)
387     {
388       needs_saving = true;
389       update_window_title();
390     }
391 }
392 
on_help_about_activated()393 void CitySetWindow::on_help_about_activated()
394 {
395   Gtk::AboutDialog* dialog;
396 
397   Glib::RefPtr<Gtk::Builder> xml
398     = Gtk::Builder::create_from_file(File::getGladeFile("about-dialog.ui"));
399 
400   xml->get_widget("dialog", dialog);
401   dialog->set_transient_for(*window);
402   dialog->set_icon_from_file(File::getVariousFile("castle_icon.png"));
403 
404   dialog->set_version(PACKAGE_VERSION);
405   dialog->set_logo(ImageCache::loadMiscImage("castle_icon.png")->to_pixbuf());
406   dialog->show_all();
407   dialog->run();
408   delete dialog;
409 
410   return;
411 }
412 
load_cityset()413 bool CitySetWindow::load_cityset ()
414 {
415   bool ret = false;
416   Glib::ustring msg = _("Save these changes before opening a new City Set?");
417   if (check_discard (msg) == false)
418     return ret;
419   Gtk::FileChooserDialog chooser(*window,
420 				 _("Choose a City Set to Open"));
421   Glib::RefPtr<Gtk::FileFilter> lwc_filter = Gtk::FileFilter::create();
422   lwc_filter->set_name(_("LordsAWar City Sets (*.lwc)"));
423   lwc_filter->add_pattern("*" + CITYSET_EXT);
424   chooser.add_filter(lwc_filter);
425   chooser.set_current_folder(File::getSetDir(Cityset::file_extension, false));
426 
427   chooser.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
428   chooser.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_ACCEPT);
429   chooser.set_default_response(Gtk::RESPONSE_ACCEPT);
430 
431   chooser.show_all();
432   int res = chooser.run();
433 
434   if (res == Gtk::RESPONSE_ACCEPT)
435     {
436       bool ok = load_cityset(chooser.get_filename());
437       chooser.hide();
438       if (ok)
439         {
440           needs_saving = false;
441           update_window_title();
442           ret = true;
443         }
444     }
445 
446   update_cityset_panel();
447   return ret;
448 }
449 
load_cityset(Glib::ustring filename)450 bool CitySetWindow::load_cityset(Glib::ustring filename)
451 {
452   Glib::ustring old_current_save_filename = current_save_filename;
453   current_save_filename = filename;
454 
455   bool unsupported_version = false;
456   Cityset *cityset = Cityset::create(filename, unsupported_version);
457   if (cityset == NULL)
458     {
459       Glib::ustring msg;
460       if (unsupported_version)
461         msg = _("Error!  The version of City Set is unsupported.");
462       else
463         msg = _("Error!  City Set could not be loaded.");
464       TimedMessageDialog dialog(*window, msg, 0);
465       current_save_filename = old_current_save_filename;
466       dialog.run_and_hide();
467       return false;
468     }
469   if (d_cityset)
470     delete d_cityset;
471   d_cityset = cityset;
472   d_cityset->setLoadTemporaryFile ();
473 
474   bool broken = false;
475   d_cityset->instantiateImages(false, broken);
476   if (broken)
477     {
478       delete d_cityset;
479       d_cityset = NULL;
480       TimedMessageDialog td(*window, _("Couldn't load City Set images."), 0);
481       td.run_and_hide();
482       return false;
483     }
484   save_cityset_menuitem->set_sensitive (true);
485   update_window_title();
486   return true;
487 }
488 
quit()489 bool CitySetWindow::quit()
490 {
491   if (needs_saving)
492     {
493       EditorQuitDialog d (*window);
494       int response = d.run_and_hide();
495 
496       if (response == Gtk::RESPONSE_CANCEL) // we don't want to new
497         return false;
498 
499       else if (response == Gtk::RESPONSE_ACCEPT) // save it
500         {
501           bool saved = false;
502           bool existing = d_cityset->getDirectory().empty () == false;
503           if (existing)
504             {
505               if (check_save_valid (true))
506                 {
507                   if (save_current_cityset_file ())
508                     saved = true;
509                 }
510               else
511                 return false;
512             }
513           else
514             {
515               if (check_save_valid (false))
516                 saved = save_current_cityset_file_as ();
517               else
518                 return false;
519             }
520           if (!saved)
521             return false;
522         }
523     }
524   window->hide ();
525   if (d_cityset)
526     delete d_cityset;
527   return true;
528 }
529 
on_window_closed(GdkEventAny *)530 bool CitySetWindow::on_window_closed(GdkEventAny*)
531 {
532   return !quit();
533 }
534 
on_quit_activated()535 void CitySetWindow::on_quit_activated()
536 {
537   quit();
538 }
539 
on_city_tile_width_text_changed()540 void CitySetWindow::on_city_tile_width_text_changed()
541 {
542   city_tile_width_spinbutton->set_value(atoi(city_tile_width_spinbutton->get_text().c_str()));
543   on_city_tile_width_changed();
544 }
545 
on_city_tile_width_changed()546 void CitySetWindow::on_city_tile_width_changed()
547 {
548   if (!d_cityset)
549     return;
550   d_cityset->setCityTileWidth(city_tile_width_spinbutton->get_value());
551   needs_saving = true;
552   update_window_title();
553 }
554 
on_ruin_tile_width_text_changed()555 void CitySetWindow::on_ruin_tile_width_text_changed()
556 {
557   ruin_tile_width_spinbutton->set_value(atoi(ruin_tile_width_spinbutton->get_text().c_str()));
558   on_ruin_tile_width_changed();
559 }
560 
on_ruin_tile_width_changed()561 void CitySetWindow::on_ruin_tile_width_changed()
562 {
563   if (!d_cityset)
564     return;
565   d_cityset->setRuinTileWidth(ruin_tile_width_spinbutton->get_value());
566   needs_saving = true;
567   update_window_title();
568 }
569 
on_temple_tile_width_text_changed()570 void CitySetWindow::on_temple_tile_width_text_changed()
571 {
572   temple_tile_width_spinbutton->set_value(atoi(temple_tile_width_spinbutton->get_text().c_str()));
573   on_temple_tile_width_changed();
574 }
575 
on_temple_tile_width_changed()576 void CitySetWindow::on_temple_tile_width_changed()
577 {
578   if (!d_cityset)
579     return;
580   d_cityset->setTempleTileWidth(temple_tile_width_spinbutton->get_value());
581   needs_saving = true;
582   update_window_title();
583 }
584 
on_change_citypics_clicked()585 void CitySetWindow::on_change_citypics_clicked()
586 {
587   bool cleared = false;
588   std::vector<PixMask *> frames;
589   for (guint32 i = 0; i < MAX_PLAYERS + 1; i++)
590     if (d_cityset->getCityImage (i))
591       frames.push_back (d_cityset->getCityImage (i));
592   Glib::ustring imgname = d_cityset->getCitiesFilename();
593   Glib::ustring f =
594     change_image(_("Select a Cities image"), imgname, MAX_PLAYERS + 1,
595                  frames, cleared, d_cityset->getCityTileWidth ());
596   if (cleared)
597     d_cityset->uninstantiateSameNamedImages (imgname);
598   else
599     {
600       if (f != "")
601         {
602           d_cityset->setCitiesFilename (f);
603           d_cityset->instantiateCityImages();
604         }
605     }
606   update_cityset_panel();
607 }
608 
on_change_razedcitypics_clicked()609 void CitySetWindow::on_change_razedcitypics_clicked()
610 {
611   bool cleared = false;
612   std::vector<PixMask *> frames;
613   for (guint32 i = 0; i < MAX_PLAYERS; i++)
614     if (d_cityset->getRazedCityImage (i))
615       frames.push_back (d_cityset->getRazedCityImage (i));
616   Glib::ustring imgname = d_cityset->getRazedCitiesFilename();
617   Glib::ustring f =
618     change_image(_("Select a Razed Cities image"), imgname, MAX_PLAYERS,
619                  frames, cleared, d_cityset->getCityTileWidth ());
620   if (cleared)
621     d_cityset->uninstantiateSameNamedImages (imgname);
622   else
623     {
624       if (f != "")
625         {
626           d_cityset->setRazedCitiesFilename (f);
627           d_cityset->instantiateRazedCityImages();
628         }
629     }
630   update_cityset_panel();
631 }
632 
on_change_portpic_clicked()633 void CitySetWindow::on_change_portpic_clicked()
634 {
635   bool cleared = false;
636   std::vector<PixMask *> frames;
637   if (d_cityset->getPortImage ())
638     frames.push_back (d_cityset->getPortImage ());
639   Glib::ustring imgname = d_cityset->getPortFilename();
640   Glib::ustring f = change_image(_("Select a Port image"), imgname, 1, frames,
641                                  cleared, 1);
642   if (cleared)
643     d_cityset->uninstantiateSameNamedImages (imgname);
644   else
645     {
646       if (f != "")
647         {
648           d_cityset->setPortFilename (f);
649           d_cityset->instantiatePortImage();
650         }
651     }
652   update_cityset_panel();
653 }
654 
on_change_signpostpic_clicked()655 void CitySetWindow::on_change_signpostpic_clicked()
656 {
657   bool cleared = false;
658   std::vector<PixMask *> frames;
659   if (d_cityset->getSignpostImage ())
660     frames.push_back (d_cityset->getSignpostImage ());
661   Glib::ustring imgname = d_cityset->getSignpostFilename();
662   Glib::ustring f =
663     change_image(_("Select a Signpost image"), imgname, 1, frames, cleared, 1);
664   if (cleared)
665     d_cityset->uninstantiateSameNamedImages (imgname);
666   else
667     {
668       if (f != "")
669         {
670           d_cityset->setSignpostFilename (f);
671           d_cityset->instantiateSignpostImage();
672         }
673     }
674   update_cityset_panel();
675 }
676 
on_change_ruinpics_clicked()677 void CitySetWindow::on_change_ruinpics_clicked()
678 {
679   bool cleared = false;
680   std::vector<PixMask *> frames;
681   for (guint32 i = 0; i < RUIN_TYPES; i++)
682     if (d_cityset->getRuinImage (i))
683       frames.push_back (d_cityset->getRuinImage (i));
684   Glib::ustring imgname = d_cityset->getRuinsFilename();
685   Glib::ustring f =
686     change_image(_("Select a Ruins image"), imgname, RUIN_TYPES, frames,
687                  cleared, d_cityset->getRuinTileWidth ());
688   if (cleared)
689     d_cityset->uninstantiateSameNamedImages (imgname);
690   else
691     {
692       if (f != "")
693         {
694           d_cityset->setRuinsFilename (f);
695           d_cityset->instantiateRuinImages();
696         }
697     }
698   update_cityset_panel();
699 }
700 
on_change_templepic_clicked()701 void CitySetWindow::on_change_templepic_clicked()
702 {
703   bool cleared = false;
704   std::vector<PixMask *> frames;
705   for (guint32 i = 0; i < TEMPLE_TYPES; i++)
706     if (d_cityset->getTempleImage (i))
707       frames.push_back (d_cityset->getTempleImage (i));
708   Glib::ustring imgname = d_cityset->getTemplesFilename();
709   Glib::ustring f =
710     change_image(_("Select a Temples image"), imgname, TEMPLE_TYPES, frames,
711                  cleared, d_cityset->getTempleTileWidth ());
712   if (cleared)
713     d_cityset->uninstantiateSameNamedImages (imgname);
714   else
715     {
716       if (f != "")
717         {
718           d_cityset->setTemplesFilename (f);
719           d_cityset->instantiateTempleImages();
720         }
721     }
722   update_cityset_panel();
723 }
724 
on_change_towerpics_clicked()725 void CitySetWindow::on_change_towerpics_clicked()
726 {
727   bool cleared = false;
728   std::vector<PixMask *> frames;
729   for (guint32 i = 0; i < MAX_PLAYERS; i++)
730     if (d_cityset->getTowerImage (i))
731       frames.push_back (d_cityset->getTowerImage (i));
732   Glib::ustring imgname = d_cityset->getTowersFilename();
733   Glib::ustring f =
734     change_image(_("Select a Towers image"), imgname, MAX_PLAYERS, frames,
735                  cleared, 1);
736   if (cleared)
737     d_cityset->uninstantiateSameNamedImages (imgname);
738   else
739     {
740       if (f != "")
741         {
742           d_cityset->setTowersFilename (f);
743           d_cityset->instantiateTowerImages();
744         }
745     }
746   update_cityset_panel();
747 }
748 
change_image(Glib::ustring msg,Glib::ustring imgname,int num,std::vector<PixMask * > frames,bool & cleared,int tw)749 Glib::ustring CitySetWindow::change_image(Glib::ustring msg, Glib::ustring imgname, int num, std::vector<PixMask *> frames, bool &cleared, int tw)
750 {
751   Glib::ustring newfile = "";
752 
753   ImageEditorDialog d(*window, imgname, num, frames,
754                       EDITOR_DIALOG_TILE_PIC_FONTSIZE_MULTIPLE * (double)tw);
755   d.set_title(msg);
756   int response = d.run();
757   if (response == Gtk::RESPONSE_ACCEPT && d.get_filename() != "")
758     {
759       Glib::ustring newname = "";
760       bool success = false;
761       if (imgname.empty () == true)
762         success = d_cityset->addFileInCfgFile(d.get_filename(), newname);
763       else
764         success =
765           d_cityset->replaceFileInCfgFile(imgname, d.get_filename(), newname);
766       if (success)
767         {
768           newfile = newname;
769           needs_saving = true;
770           update_window_title();
771         }
772       else
773         show_add_file_error(*d.get_dialog(), d.get_filename ());
774     }
775   else if (response == Gtk::RESPONSE_REJECT)
776     {
777       if (d_cityset->removeFileInCfgFile(imgname))
778         {
779           needs_saving = true;
780           update_window_title();
781           cleared = true;
782           newfile = "";
783         }
784       else
785         show_remove_file_error(*d.get_dialog(), imgname);
786     }
787   return newfile;
788 }
789 
update_window_title()790 void CitySetWindow::update_window_title()
791 {
792   Glib::ustring title = "";
793   if (needs_saving)
794     title += "*";
795   title += d_cityset->getName();
796   title += " - ";
797   title += _("City Set Editor");
798   window->set_title(title);
799 }
800 
show_add_file_error(Gtk::Dialog & d,Glib::ustring file)801 void CitySetWindow::show_add_file_error(Gtk::Dialog &d, Glib::ustring file)
802 {
803   Glib::ustring errmsg = Glib::strerror(errno);
804   Glib::ustring m =
805     String::ucompose(_("Couldn't add %1 to:\n%2\n%3"),
806                      file, d_cityset->getConfigurationFile(), errmsg);
807   TimedMessageDialog td(d, m, 0);
808   td.run_and_hide();
809 }
810 
show_remove_file_error(Gtk::Dialog & d,Glib::ustring file)811 void CitySetWindow::show_remove_file_error(Gtk::Dialog &d, Glib::ustring file)
812 {
813   Glib::ustring errmsg = Glib::strerror(errno);
814   Glib::ustring m =
815     String::ucompose(_("Couldn't remove %1 from:\n%2\n%3"),
816                      file, d_cityset->getConfigurationFile(), errmsg);
817   TimedMessageDialog td(d, m, 0);
818   td.run_and_hide();
819 }
820 
~CitySetWindow()821 CitySetWindow::~CitySetWindow()
822 {
823   notebook->property_show_tabs () = false;
824   delete window;
825 }
826 
on_tutorial_video_activated()827 void CitySetWindow::on_tutorial_video_activated()
828 {
829   GError *errs = NULL;
830   gtk_show_uri(window->get_screen()->gobj(),
831                "http://vimeo.com/406899445", 0, &errs);
832 }
833 
check_discard(Glib::ustring msg)834 bool CitySetWindow::check_discard (Glib::ustring msg)
835 {
836   if (needs_saving)
837     {
838       EditorSaveChangesDialog d (*window, msg);
839       int response = d.run_and_hide();
840 
841       if (response == Gtk::RESPONSE_CANCEL) // we don't want to new
842         return false;
843 
844       else if (response == Gtk::RESPONSE_ACCEPT) // save it
845         {
846           if (check_save_valid (true))
847             {
848               bool saved = false;
849               if (d_cityset->getDirectory ().empty () == false)
850                   saved = save_current_cityset_file_as ();
851               else
852                 {
853                   if (save_current_cityset_file ())
854                     saved = true;
855                 }
856               if (!saved)
857                 return false;
858             }
859           else
860             return false;
861         }
862     }
863   return true;
864 }
865 
check_save_valid(bool existing)866 bool CitySetWindow::check_save_valid (bool existing)
867 {
868   if (check_name_valid (existing) == false)
869     return false;
870 
871   if (d_cityset->validate () == false)
872     {
873       if (existing &&
874           GameMap::getInstance()->getCitysetId() == d_cityset->getId())
875         {
876           Glib::ustring errmsg =
877             _("City Set is invalid, and is also the current working City Set.");
878           Glib::ustring msg = _("Error!  City Set could not be saved.");
879           msg += "\n" + current_save_filename + "\n" + errmsg;
880           TimedMessageDialog dialog(*window, msg, 0);
881           dialog.run_and_hide();
882           return false;
883         }
884       else
885         {
886           TimedMessageDialog
887             dialog(*window,
888                    _("The City Set is invalid.  Do you want to proceed?"), 0);
889           dialog.add_cancel_button ();
890           dialog.run_and_hide ();
891           if (dialog.get_response () == Gtk::RESPONSE_CANCEL)
892             return false;
893         }
894     }
895   return true;
896 }
897 
check_name_valid(bool existing)898 bool CitySetWindow::check_name_valid (bool existing)
899 {
900   Glib::ustring name = d_cityset->getName ();
901   Glib::ustring newname = "";
902   if (existing)
903     {
904       Cityset *oldcityset =
905         Citysetlist::getInstance ()->get(d_cityset->getId());
906       if (oldcityset && oldcityset->getName () != name)
907           newname = oldcityset->getName ();
908     }
909   guint32 num = 0;
910   Glib::ustring n = String::utrim (String::strip_trailing_numbers (name));
911   if (n == "")
912     n = _("Untitled");
913   if (newname.empty () == true)
914     newname =
915       Citysetlist::getInstance()->findFreeName(n, 100, num,
916                                                d_cityset->getTileSize ());
917   if (name == "")
918     {
919       if (newname.empty() == true)
920         {
921           Glib::ustring msg =
922             _("The City Set has an invalid name.\nChange it and save again.");
923           TimedMessageDialog d(*window, msg, 0);
924           d.run_and_hide();
925           on_edit_cityset_info_activated ();
926           return false;
927         }
928       else
929         {
930           Glib::ustring msg =
931             String::ucompose (_("The City Set has an invalid name.\nChange it to '%1'?"), newname);
932           TimedMessageDialog d(*window, msg, 0);
933           d.add_cancel_button ();
934           d.run_and_hide ();
935           if (d.get_response () == Gtk::RESPONSE_CANCEL)
936             return false;
937           d_cityset->setName (newname);
938         }
939     }
940 
941   //okay the question is whether or not the name is already used.
942   bool same_name = false;
943   Glib::ustring file =
944     Citysetlist::getInstance()->lookupConfigurationFileByName(d_cityset);
945   if (file == "")
946     return true;
947 
948   Glib::ustring cfgfile = d_cityset->getConfigurationFile(true);
949 
950   if (existing) // this means we're doing File->Save
951     {
952       if (file == cfgfile)
953         return true;
954       same_name = true;
955     }
956   else // this means we're doing File->Save As
957     same_name = true;
958 
959   if (same_name)
960     {
961       if (newname.empty() == true)
962         {
963           Glib::ustring msg =
964             _("The City Set has the same name as another one.\nChange it and save again.");
965           TimedMessageDialog d(*window, msg, 0);
966           d.run_and_hide();
967           on_edit_cityset_info_activated ();
968           return false;
969         }
970       else
971         {
972           Glib::ustring msg =
973             String::ucompose (_("The City Set has the same name as another one.\nChange it to '%1' instead?."), newname);
974 
975           TimedMessageDialog d(*window, msg, 0);
976           d.add_cancel_button ();
977           d.run_and_hide ();
978           if (d.get_response () == Gtk::RESPONSE_CANCEL)
979             return false;
980           d_cityset->setName (newname);
981         }
982     }
983 
984   return true;
985 }
986 
isValidName()987 bool CitySetWindow::isValidName ()
988 {
989   Glib::ustring file =
990     Citysetlist::getInstance()->lookupConfigurationFileByName(d_cityset);
991   if (file == "")
992     return true;
993   if (file == d_cityset->getConfigurationFile (true))
994     return true;
995   return false;
996 }
997 
998 /*
999  some test cases
1000   1. create a new cityset from scratch, save invalid set, close, load it
1001   2. create a new cityset from scratch, save valid set, then switch sets
1002   3. save a copy of the default cityset, and switch sets
1003   4. modify the working cityset so we can see it change in scenario builder
1004   5. modify the working cityset so that it's invalid, try to save
1005   6. try adding an image file that isn't a .png
1006   7. try adding an image file that says it's a .png but is actually a .jpg
1007   8. try adding an image file that says it's a .png but is actually random data
1008   9. try saving a new cityset that has a same name
1009  10. try saving an existing cityset that has a same name
1010  11. validate a cityset without: the port picture
1011  12. validate a cityset without: the towers picture
1012  13. try saving a new cityset that has an empty name
1013  14. validate a cityset with a same name
1014  15. validate a cityset with an empty name
1015  16. make a new invalid cityset and quit save it
1016  17. load a writable cityset, modify and quit save it
1017  18. load a writable cityset, make it invalid, and then quit save it
1018  19. try saving a cityset we don't have permission to save
1019  20. try quit-saving a cityset we don't have permission to save
1020 */
1021 
1022 
1023