1 // Copyright (C) 2007 Ole Laursen
2 // Copyright (C) 2007, 2008, 2009, 2012, 2014, 2015, 2020 Ben Asselstine
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Library General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 // 02110-1301, USA.
18
19 #include <config.h>
20
21 #include <sigc++/functors/mem_fun.h>
22 #include <gtkmm.h>
23
24 #include "city-editor-dialog.h"
25
26 #include "ucompose.hpp"
27 #include "defs.h"
28 #include "city.h"
29 #include "armyprodbase.h"
30 #include "army.h"
31 #include "armyproto.h"
32 #include "playerlist.h"
33 #include "stacklist.h"
34 #include "citylist.h"
35 #include "CreateScenarioRandomize.h"
36 #include "ImageCache.h"
37 #include "GameMap.h"
38 #include "font-size.h"
39 #include "select-army-dialog.h"
40
41 #define method(x) sigc::mem_fun(*this, &CityEditorDialog::x)
42
CityEditorDialog(Gtk::Window & parent,City * cit,CreateScenarioRandomize * randomizer)43 CityEditorDialog::CityEditorDialog(Gtk::Window &parent, City *cit, CreateScenarioRandomize *randomizer)
44 : LwEditorDialog(parent, "city-editor-dialog.ui"),
45 strength_column(_("Strength"), strength_renderer),
46 moves_column(_("Max Moves"), moves_renderer),
47 duration_column(_("Turns"), duration_renderer),
48 upkeep_column(_("Upkeep"), upkeep_renderer)
49 {
50 city = cit;
51 d_randomizer = randomizer;
52
53 xml->get_widget("capital_switch", capital_switch);
54 capital_switch->set_active(city->isCapital());
55 capital_switch->property_active ().signal_changed ().connect (method (on_capital_changed));
56
57 xml->get_widget("name_entry", name_entry);
58 name_entry->set_text(city->getName());
59 name_entry->signal_changed ().connect (method (on_name_changed));
60
61 xml->get_widget("income_spinbutton", income_spinbutton);
62 income_spinbutton->set_value(city->getGold());
63 income_spinbutton->signal_changed().connect (method (on_income_changed));
64
65 xml->get_widget("burned_switch", burned_switch);
66 burned_switch->set_active(city->isBurnt());
67 burned_switch->property_active ().signal_changed ().connect (method (on_burned_changed));
68
69 xml->get_widget("build_production_switch", build_production_switch);
70 build_production_switch->set_active(city->getBuildProduction());
71 build_production_switch->property_active ().signal_changed ().connect (method (on_build_production_changed));
72
73 // setup the player combo
74 player_combobox = manage(new Gtk::ComboBoxText);
75
76 int c = 0, player_no = 0;
77 for (Playerlist::iterator i = Playerlist::getInstance()->begin(),
78 end = Playerlist::getInstance()->end(); i != end; ++i, ++c)
79 {
80 Player *player = *i;
81 player_combobox->append(player->getName());
82 if (player == city->getOwner())
83 player_no = c;
84 }
85
86 player_combobox->set_active(player_no);
87 player_combobox->signal_changed().connect (method (on_player_changed));
88 Gtk::Alignment *alignment;
89 xml->get_widget("player_alignment", alignment);
90 alignment->add(*player_combobox);
91
92
93 // setup the army list
94 army_list = Gtk::ListStore::create(army_columns);
95
96 xml->get_widget("army_treeview", army_treeview);
97 army_treeview->set_model(army_list);
98
99 army_treeview->append_column("", army_columns.image);
100 strength_renderer.property_editable() = true;
101 strength_renderer.signal_edited().connect(method (on_strength_edited));
102 strength_column.set_cell_data_func
103 (strength_renderer, method (cell_data_strength));
104 army_treeview->append_column(strength_column);
105
106 moves_renderer.property_editable() = true;
107 moves_renderer.signal_edited().connect(method (on_moves_edited));
108 moves_column.set_cell_data_func (moves_renderer, method (cell_data_moves));
109 army_treeview->append_column(moves_column);
110
111 upkeep_renderer.property_editable() = true;
112 upkeep_renderer.signal_edited().connect(method (on_upkeep_edited));
113 upkeep_column.set_cell_data_func (upkeep_renderer, method (cell_data_upkeep));
114 army_treeview->append_column(upkeep_column);
115
116 duration_renderer.property_editable() = true;
117 duration_renderer.signal_edited().connect(method (on_turns_edited));
118 duration_column.set_cell_data_func
119 (duration_renderer, method (CityEditorDialog::cell_data_turns));
120
121 army_treeview->append_column(_("Name"), army_columns.name);
122
123 xml->get_widget("add_button", add_button);
124 xml->get_widget("remove_button", remove_button);
125 xml->get_widget("randomize_armies_button", randomize_armies_button);
126 xml->get_widget("randomize_name_button", randomize_name_button);
127 xml->get_widget("randomize_income_button", randomize_income_button);
128
129 add_button->signal_clicked().connect (method (on_add_clicked));
130 remove_button->signal_clicked().connect (method (on_remove_clicked));
131 randomize_armies_button->signal_clicked().connect
132 (method (on_randomize_armies_clicked));
133 randomize_name_button->signal_clicked().connect
134 (method (on_randomize_name_clicked));
135 randomize_income_button->signal_clicked().connect
136 (method (on_randomize_income_clicked));
137
138 army_treeview->get_selection()->signal_changed().connect
139 (method (on_selection_changed));
140
141 for (unsigned int i = 0; i < city->getMaxNoOfProductionBases(); i++)
142 {
143 const ArmyProdBase* a = city->getProductionBase(i);
144 if (a)
145 add_army(a);
146 }
147
148 Player *player = get_selected_player ();
149 bool neutral = player == Playerlist::getInstance ()->getNeutral ();
150 if (city->isBurnt () && neutral)
151 burned_switch->set_active (false);
152 burned_switch->set_sensitive (!neutral);
153
154 update_buttons();
155 }
156
~CityEditorDialog()157 CityEditorDialog::~CityEditorDialog ()
158 {
159 for (Gtk::TreeIter i = army_list->children().begin(),
160 end = army_list->children().end(); i != end; ++i)
161 {
162 const ArmyProdBase *a = (*i)[army_columns.army];
163 delete a;
164 }
165 }
166
change_city_ownership()167 void CityEditorDialog::change_city_ownership()
168 {
169 // set allegiance
170 Player *player = get_selected_player();
171 if (player == city->getOwner()) //no change? do nothing.
172 return;
173 city->setOwner(player);
174 //look for stacks in the city, and set them to this player
175 for (unsigned int x = 0; x < city->getSize(); x++)
176 {
177 for (unsigned int y = 0; y < city->getSize(); y++)
178 {
179 Stack *s = GameMap::getStack(city->getPos() + Vector<int>(x,y));
180 if (s)
181 Stacklist::changeOwnership(s, player);
182 }
183 }
184 }
185
run()186 int CityEditorDialog::run()
187 {
188 dialog->show_all();
189 return dialog->run();
190 }
191
on_add_clicked()192 void CityEditorDialog::on_add_clicked()
193 {
194 SelectArmyDialog d(*dialog, SelectArmyDialog::SELECT_NORMAL,
195 city->getOwner(), -1);
196 d.run();
197
198 const ArmyProto *army = d.get_selected_army();
199 if (army)
200 add_army(new ArmyProdBase(*army));
201 update_armies ();
202 }
203
on_remove_clicked()204 void CityEditorDialog::on_remove_clicked()
205 {
206 Gtk::TreeIter i = army_treeview->get_selection()->get_selected();
207 if (i)
208 {
209 const ArmyProdBase *a = (*i)[army_columns.army];
210 delete a;
211 (*i)[army_columns.army] = NULL;
212 army_list->erase(i);
213 }
214
215 update_armies ();
216 set_button_sensitivity();
217 }
218
on_randomize_armies_clicked()219 void CityEditorDialog::on_randomize_armies_clicked()
220 {
221 const ArmyProdBase *army;
222 army_list->clear();
223 city->setRandomArmytypes(true, 1);
224 for (unsigned int i = 0; i < city->getMaxNoOfProductionBases(); i++)
225 {
226 army = city->getProductionBase(i);
227 if (army)
228 add_army(army);
229 }
230 set_button_sensitivity();
231 }
232
on_randomize_name_clicked()233 void CityEditorDialog::on_randomize_name_clicked()
234 {
235 Glib::ustring existing_name = name_entry->get_text();
236 if (existing_name == City::getDefaultName())
237 name_entry->set_text(d_randomizer->popRandomCityName());
238 else
239 {
240 name_entry->set_text(d_randomizer->popRandomCityName());
241 d_randomizer->pushRandomCityName(existing_name);
242 }
243 }
244
on_randomize_income_clicked()245 void CityEditorDialog::on_randomize_income_clicked()
246 {
247 int gold = d_randomizer->getRandomCityIncome(capital_switch->get_active());
248 income_spinbutton->set_value(gold);
249 }
250
add_army(const ArmyProdBase * a)251 void CityEditorDialog::add_army(const ArmyProdBase *a)
252 {
253 Player *player = get_selected_player();
254 ImageCache *gc = ImageCache::getInstance();
255 Gtk::TreeIter i = army_list->append();
256 const ArmyProdBase *aa = new ArmyProdBase (*a);
257 (*i)[army_columns.army] = aa;
258 guint32 fs = FontSize::getInstance ()->get_height ();
259 (*i)[army_columns.image] = gc->getArmyPic(player->getArmyset(),
260 aa->getTypeId(), player,
261 NULL, false, fs)->to_pixbuf();
262 (*i)[army_columns.strength] = aa->getStrength();
263 (*i)[army_columns.moves] = aa->getMaxMoves();
264 (*i)[army_columns.upkeep] = a->getUpkeep();
265 (*i)[army_columns.duration] = aa->getProduction();
266 (*i)[army_columns.name] = aa->getName();
267 army_treeview->get_selection()->select(i);
268
269 set_button_sensitivity();
270 }
271
update_armies()272 void CityEditorDialog::update_armies ()
273 {
274 guint32 c = 0;
275 for (; c < city->getMaxNoOfProductionBases(); ++c)
276 city->removeProductionBase(c);
277 c = 0;
278 for (Gtk::TreeIter i = army_list->children().begin(),
279 end = army_list->children().end(); i != end; ++i, ++c)
280 {
281 const ArmyProdBase *a = (*i)[army_columns.army];
282 ArmyProdBase *army = new ArmyProdBase(*a);
283 army->setStrength((*i)[army_columns.strength]);
284 army->setProduction((*i)[army_columns.duration]);
285 army->setMaxMoves((*i)[army_columns.moves]);
286 army->setUpkeep ((*i)[army_columns.upkeep]);
287 city->addProductionBase(c, army);
288 }
289 }
290
on_selection_changed()291 void CityEditorDialog::on_selection_changed()
292 {
293 set_button_sensitivity();
294 }
295
set_button_sensitivity()296 void CityEditorDialog::set_button_sensitivity()
297 {
298 Gtk::TreeIter i = army_treeview->get_selection()->get_selected();
299 unsigned int armies = army_list->children().size();
300 add_button->set_sensitive(armies < city->getMaxNoOfProductionBases());
301 remove_button->set_sensitive(i);
302 }
303
cell_data_strength(Gtk::CellRenderer * renderer,const Gtk::TreeIter & i)304 void CityEditorDialog::cell_data_strength(Gtk::CellRenderer *renderer,
305 const Gtk::TreeIter& i)
306 {
307 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_adjustment()
308 = Gtk::Adjustment::create((*i)[army_columns.strength],
309 MIN_STRENGTH_FOR_ARMY_UNITS,
310 MAX_STRENGTH_FOR_ARMY_UNITS, 1);
311 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_text() =
312 String::ucompose("%1", (*i)[army_columns.strength]);
313 }
314
on_strength_edited(const Glib::ustring & path,const Glib::ustring & new_text)315 void CityEditorDialog::on_strength_edited(const Glib::ustring &path,
316 const Glib::ustring &new_text)
317 {
318 int str = atoi(new_text.c_str());
319 if (str < (int)MIN_STRENGTH_FOR_ARMY_UNITS ||
320 str > (int)MAX_STRENGTH_FOR_ARMY_UNITS)
321 return;
322 (*army_list->get_iter(Gtk::TreePath(path)))[army_columns.strength] = str;
323 update_armies ();
324 }
325
cell_data_moves(Gtk::CellRenderer * renderer,const Gtk::TreeIter & i)326 void CityEditorDialog::cell_data_moves(Gtk::CellRenderer *renderer,
327 const Gtk::TreeIter& i)
328 {
329 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_adjustment()
330 = Gtk::Adjustment::create((*i)[army_columns.moves],
331 MIN_MOVES_FOR_ARMY_UNITS,
332 MAX_MOVES_FOR_ARMY_UNITS, 1);
333 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_text() =
334 String::ucompose("%1", (*i)[army_columns.moves]);
335 }
336
on_moves_edited(const Glib::ustring & path,const Glib::ustring & new_text)337 void CityEditorDialog::on_moves_edited(const Glib::ustring &path,
338 const Glib::ustring &new_text)
339 {
340 int moves = atoi(new_text.c_str());
341 if (moves < (int)MIN_MOVES_FOR_ARMY_UNITS ||
342 moves > (int)MAX_MOVES_FOR_ARMY_UNITS)
343 return;
344 (*army_list->get_iter(Gtk::TreePath(path)))[army_columns.moves] = moves;
345 update_armies ();
346 }
347
cell_data_turns(Gtk::CellRenderer * renderer,const Gtk::TreeIter & i)348 void CityEditorDialog::cell_data_turns(Gtk::CellRenderer *renderer,
349 const Gtk::TreeIter& i)
350 {
351 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_adjustment()
352 = Gtk::Adjustment::create((*i)[army_columns.duration],
353 MIN_PRODUCTION_TURNS_FOR_ARMY_UNITS,
354 MAX_PRODUCTION_TURNS_FOR_ARMY_UNITS, 1);
355 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_text() =
356 String::ucompose("%1", (*i)[army_columns.duration]);
357 }
358
on_turns_edited(const Glib::ustring & path,const Glib::ustring & new_text)359 void CityEditorDialog::on_turns_edited(const Glib::ustring &path,
360 const Glib::ustring &new_text)
361 {
362 int turns = atoi(new_text.c_str());
363 if (turns < (int)MIN_PRODUCTION_TURNS_FOR_ARMY_UNITS ||
364 turns > (int)MAX_PRODUCTION_TURNS_FOR_ARMY_UNITS)
365 return;
366 (*army_list->get_iter(Gtk::TreePath(path)))[army_columns.duration] = turns;
367 update_armies ();
368 }
369
cell_data_upkeep(Gtk::CellRenderer * renderer,const Gtk::TreeIter & i)370 void CityEditorDialog::cell_data_upkeep(Gtk::CellRenderer *renderer,
371 const Gtk::TreeIter& i)
372 {
373 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_adjustment()
374 = Gtk::Adjustment::create((*i)[army_columns.upkeep], 0, 20, 1);
375 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_text() =
376 String::ucompose("%1", (*i)[army_columns.upkeep]);
377 }
378
on_upkeep_edited(const Glib::ustring & path,const Glib::ustring & new_text)379 void CityEditorDialog::on_upkeep_edited(const Glib::ustring &path,
380 const Glib::ustring &new_text)
381 {
382 int upkeep = atoi(new_text.c_str());
383 if (upkeep < (int) MIN_UPKEEP_FOR_ARMY_UNITS ||
384 upkeep > (int) MAX_UPKEEP_FOR_ARMY_UNITS)
385 return;
386 (*army_list->get_iter(Gtk::TreePath(path)))[army_columns.upkeep] = upkeep;
387 update_armies ();
388 }
389
get_selected_player()390 Player *CityEditorDialog::get_selected_player()
391 {
392 int c = 0, row = player_combobox->get_active_row_number();
393 Player *player = Playerlist::getInstance()->getNeutral();
394 for (Playerlist::iterator i = Playerlist::getInstance()->begin(),
395 end = Playerlist::getInstance()->end(); i != end; ++i, ++c)
396 if (c == row)
397 {
398 player = *i;
399 break;
400 }
401 return player;
402 }
403
on_player_changed()404 void CityEditorDialog::on_player_changed()
405 {
406 ImageCache *gc = ImageCache::getInstance();
407 Player *player = get_selected_player();
408 guint32 fs = FontSize::getInstance ()->get_height ();
409 for (Gtk::TreeIter j = army_list->children().begin(),
410 jend = army_list->children().end(); j != jend; ++j)
411 {
412 const ArmyProdBase *a = (*j)[army_columns.army];
413 (*j)[army_columns.image] = gc->getArmyPic(player->getArmyset(),
414 a->getTypeId(),
415 player, NULL, false,
416 fs)->to_pixbuf();
417 }
418 if (capital_switch->get_active())
419 capital_switch->set_active(false);
420
421 bool neutral = player == Playerlist::getInstance ()->getNeutral ();
422 if (city->isBurnt () && neutral)
423 burned_switch->set_active (false);
424 burned_switch->set_sensitive (!neutral);
425 change_city_ownership ();
426 update_buttons();
427 }
428
update_buttons()429 void CityEditorDialog::update_buttons ()
430 {
431 Player *player = get_selected_player();
432 capital_switch->set_sensitive
433 (player != Playerlist::getInstance()->getNeutral());
434 bool burned = burned_switch->get_active ();
435 add_button->set_sensitive (!burned);
436 remove_button->set_sensitive (!burned);
437 randomize_armies_button->set_sensitive (!burned);
438 }
439
on_burned_changed()440 void CityEditorDialog::on_burned_changed ()
441 {
442 city->setBurnt (burned_switch->get_active ());
443 if (city->isBurnt ())
444 {
445 guint32 c = 0;
446 for (; c < city->getMaxNoOfProductionBases(); ++c)
447 city->removeProductionBase(c);
448 army_list->clear ();
449 }
450 update_buttons ();
451 }
452
on_capital_changed()453 void CityEditorDialog::on_capital_changed ()
454 {
455 Player *player = get_selected_player();
456 // make sure player doesn't have other capitals
457 Citylist* cl = Citylist::getInstance();
458 for (Citylist::iterator i = cl->begin(); i != cl->end(); ++i)
459 if ((*i)->isCapital() && (*i)->getOwner() == player)
460 {
461 (*i)->setCapital(false);
462 (*i)->setCapitalOwner(NULL);
463 }
464 if (capital_switch->get_active ())
465 {
466 city->setCapital(true);
467 city->setCapitalOwner(player);
468 }
469 else
470 {
471 city->setCapital(false);
472 city->setCapitalOwner(NULL);
473 }
474 }
475
on_name_changed()476 void CityEditorDialog::on_name_changed ()
477 {
478 city->setName (String::utrim (name_entry->get_text ()));
479 }
480
on_income_changed()481 void CityEditorDialog::on_income_changed ()
482 {
483 city->setGold(income_spinbutton->get_value_as_int());
484 }
485
on_income_text_changed()486 void CityEditorDialog::on_income_text_changed ()
487 {
488 income_spinbutton->set_value(atoi(income_spinbutton->get_text().c_str()));
489 on_income_changed();
490 }
491
on_build_production_changed()492 void CityEditorDialog::on_build_production_changed ()
493 {
494 city->setBuildProduction(build_production_switch->get_active ());
495 }
496