1 // Copyright (C) 2007 Ole Laursen
2 // Copyright (C) 2007-2009, 2012, 2014, 2015, 2017, 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 <gtkmm.h>
22 #include <sigc++/functors/mem_fun.h>
23
24 #include "stack-editor-dialog.h"
25
26 #include "ucompose.hpp"
27 #include "defs.h"
28 #include "stack.h"
29 #include "army.h"
30 #include "armyproto.h"
31 #include "hero.h"
32 #include "heroproto.h"
33 #include "ImageCache.h"
34 #include "playerlist.h"
35 #include "stacklist.h"
36 #include "stacktile.h"
37 #include "hero-editor-dialog.h"
38 #include "GameMap.h"
39 #include "font-size.h"
40
41 #include "select-army-dialog.h"
42
43 #define method(x) sigc::mem_fun(*this, &StackEditorDialog::x)
44
45 namespace
46 {
47 //FIXME this should be MAX_STACK_SIZE from defs.h
48 int const max_stack_size = 8;
49 }
50
StackEditorDialog(Gtk::Window & parent,Stack * s,int m)51 StackEditorDialog::StackEditorDialog(Gtk::Window &parent, Stack *s, int m)
52 : LwEditorDialog(parent, "stack-editor-dialog.ui"),
53 strength_column(_("Strength"), strength_renderer),
54 moves_column(_("Max Moves"), moves_renderer),
55 upkeep_column(_("Upkeep"), upkeep_renderer)
56 {
57 stack = s;
58 min_size = m;
59 player_combobox = 0;
60
61 if (stack->getOwner())
62 {
63 // setup the player combo
64 player_combobox = manage(new Gtk::ComboBoxText);
65
66 int c = 0, player_no = 0;
67 for (Playerlist::iterator i = Playerlist::getInstance()->begin(),
68 end = Playerlist::getInstance()->end(); i != end; ++i, ++c)
69 {
70 Player *player = *i;
71 player_combobox->append(player->getName());
72 if (player == stack->getOwner())
73 player_no = c;
74 }
75
76 player_combobox->set_active(player_no);
77 player_combobox->signal_changed().connect (method(on_player_changed));
78
79 Gtk::Box *box;
80 xml->get_widget("player_hbox", box);
81 box->pack_start(*player_combobox, Gtk::PACK_SHRINK);
82 }
83
84 // setup the army list
85 army_list = Gtk::ListStore::create(army_columns);
86
87 xml->get_widget("army_treeview", army_treeview);
88 army_treeview->set_model(army_list);
89
90 army_treeview->append_column("", army_columns.image);
91
92 strength_renderer.property_editable() = true;
93 strength_renderer.signal_edited().connect(method(on_strength_edited));
94 strength_column.set_cell_data_func(strength_renderer, method(cell_data_strength));
95 army_treeview->append_column(strength_column);
96
97 moves_renderer.property_editable() = true;
98 moves_renderer.signal_edited().connect(method(on_moves_edited));
99 moves_column.set_cell_data_func(moves_renderer, method(cell_data_moves));
100 army_treeview->append_column(moves_column);
101
102 upkeep_renderer.property_editable() = true;
103 upkeep_renderer.signal_edited().connect(method(on_upkeep_edited));
104 upkeep_column.set_cell_data_func(upkeep_renderer, method(cell_data_upkeep));
105 army_treeview->append_column(upkeep_column);
106
107 army_treeview->append_column(_("Name"), army_columns.name);
108
109 xml->get_widget("fortified_switch", fortified_switch);
110 fortified_switch->set_active(stack->getFortified());
111 fortified_switch->property_active().signal_changed().connect(method(on_fortified_toggled));
112
113 xml->get_widget("add_button", add_button);
114 xml->get_widget("remove_button", remove_button);
115 xml->get_widget("copy_button", copy_button);
116 xml->get_widget("edit_hero_button", edit_hero_button);
117
118 add_button->signal_clicked().connect(method(on_add_clicked));
119 remove_button->signal_clicked().connect(method(on_remove_clicked));
120 copy_button->signal_clicked().connect(method(on_copy_clicked));
121 edit_hero_button->signal_clicked().connect(method(on_edit_hero_clicked));
122
123 army_treeview->get_selection()->signal_changed().connect(method(on_selection_changed));
124
125 for (Stack::iterator i = stack->begin(), end = stack->end(); i != end; ++i)
126 add_army(*i);
127 set_button_sensitivity();
128 }
129
run()130 int StackEditorDialog::run()
131 {
132 dialog->show_all();
133 return dialog->run();
134 }
135
update_armies()136 void StackEditorDialog::update_armies ()
137 {
138 // remove removed armies from stack
139 for (Stack::iterator i = stack->begin(), end = stack->end(); i != end;)
140 {
141 Army *a = *i;
142 ++i;
143
144 bool found = false;
145 for (Gtk::TreeIter j = army_list->children().begin(),
146 jend = army_list->children().end(); j != jend; ++j)
147 if ((*j)[army_columns.army] == a)
148 {
149 found = true;
150 break;
151 }
152
153 if (!found)
154 {
155 stack->remove(a);
156 delete a;
157 }
158 }
159
160 //set the stats for all of the armies in our list
161 for (Gtk::TreeIter j = army_list->children().begin(),
162 jend = army_list->children().end(); j != jend; ++j)
163 {
164 Army *a = (*j)[army_columns.army];
165 a->setStat(Army::STRENGTH, (*j)[army_columns.strength]);
166 a->setMaxMoves((*j)[army_columns.moves]);
167 a->setStat(Army::MOVES, (*j)[army_columns.moves]);
168 a->setUpkeep((*j)[army_columns.upkeep]);
169 }
170 bool ship = stack->hasShip();
171 // add added armies to stack
172 for (Gtk::TreeIter j = army_list->children().begin(),
173 jend = army_list->children().end(); j != jend; ++j)
174 {
175 Army *a = (*j)[army_columns.army];
176
177 a->setInShip(ship);
178 if (std::find(stack->begin(), stack->end(), a) == stack->end())
179 stack->push_back(a);
180 }
181
182 // now set allegiance, it's important to do it after possibly new stack
183 // armies have been added
184 // this also helps the stack ship icon show up when it's needed.
185
186 if (get_selected_player()->getId() != stack->getOwner()->getId())
187 {
188 Player *player = get_selected_player();
189 Player *old_active = Playerlist::getActiveplayer();
190 Playerlist::getInstance()->setActiveplayer(player);
191 Stack *new_stack = new Stack(*stack);
192 GameMap::getStacks(new_stack->getPos())->remove(stack);
193 stack->sdying.emit(stack);
194 stack->getOwner()->deleteStack(stack);
195 new_stack->setPlayer(player);
196 GameMap::getInstance()->putStack(new_stack);
197 Playerlist::getInstance()->setActiveplayer(old_active);
198 stack = new_stack;
199
200 Stack::iterator i = stack->begin ();
201 for (Gtk::TreeIter j = army_list->children().begin(),
202 jend = army_list->children().end(); j != jend; ++j, ++i)
203 (*j)[army_columns.army] = *i;
204 }
205 }
206
get_selected_player()207 Player *StackEditorDialog::get_selected_player()
208 {
209 int c = 0, row = player_combobox->get_active_row_number();
210 Player *player = Playerlist::getInstance()->getNeutral();
211 for (Playerlist::iterator i = Playerlist::getInstance()->begin(),
212 end = Playerlist::getInstance()->end(); i != end; ++i, ++c)
213 if (c == row)
214 {
215 player = *i;
216 break;
217 }
218 return player;
219 }
220
on_copy_clicked()221 void StackEditorDialog::on_copy_clicked()
222 {
223 Gtk::TreeIter i = army_treeview->get_selection()->get_selected();
224 if (i)
225 {
226 Player *player = get_selected_player();
227 Army *army = (*i)[army_columns.army];
228 Army *new_army = new Army(*army, player);
229 new_army->assignNewId();
230 add_army(new_army);
231 update_armies ();
232 }
233
234 set_button_sensitivity();
235 }
236
on_add_clicked()237 void StackEditorDialog::on_add_clicked()
238 {
239 SelectArmyDialog d(*dialog, SelectArmyDialog::SELECT_NORMAL_WITH_HERO,
240 stack->getOwner(), -1);
241 d.run();
242
243 Player *player = get_selected_player();
244 const ArmyProto *army = d.get_selected_army();
245 if (army)
246 {
247 if (army->isHero() == true)
248 {
249 HeroProto *hp = new HeroProto(*army);
250 hp->setOwnerId(player->getId());
251 add_army(new Hero(*hp));
252 delete hp;
253 }
254 else
255 add_army(new Army(*army, player));
256 update_armies ();
257 }
258 }
259
on_edit_hero_clicked()260 void StackEditorDialog::on_edit_hero_clicked()
261 {
262 Gtk::TreeIter i = army_treeview->get_selection()->get_selected();
263 if (i)
264 {
265 Army *army = (*i)[army_columns.army];
266 Hero *hero = dynamic_cast<Hero*>(army);
267 HeroEditorDialog d(*dialog, hero);
268 d.run();
269 (*i)[army_columns.name] = hero->getName();
270 update_armies ();
271 }
272
273 }
274
on_remove_clicked()275 void StackEditorDialog::on_remove_clicked()
276 {
277 Gtk::TreeIter i = army_treeview->get_selection()->get_selected();
278 if (i)
279 {
280 Army *army = (*i)[army_columns.army];
281 army_list->erase(i);
282 if (std::find(stack->begin(), stack->end(), army) == stack->end())
283 delete army;
284 update_armies ();
285 }
286
287 set_button_sensitivity();
288 }
289
add_army(Army * a)290 void StackEditorDialog::add_army(Army *a)
291 {
292 ImageCache *gc = ImageCache::getInstance();
293 Gtk::TreeIter i = army_list->append();
294 (*i)[army_columns.army] = a;
295 guint32 fs = FontSize::getInstance ()->get_height ();
296 (*i)[army_columns.image] = gc->getArmyPic(a->getOwner()->getArmyset(),
297 a->getTypeId(), a->getOwner(),
298 NULL, false, fs)->to_pixbuf();
299 (*i)[army_columns.strength] = a->getStat(Army::STRENGTH, false);
300 (*i)[army_columns.moves] = a->getStat(Army::MOVES, false);
301 (*i)[army_columns.upkeep] = a->getUpkeep();
302 (*i)[army_columns.name] = a->getName();
303
304 army_treeview->get_selection()->select(i);
305
306 set_button_sensitivity();
307 }
308
on_selection_changed()309 void StackEditorDialog::on_selection_changed()
310 {
311 set_button_sensitivity();
312 }
313
set_button_sensitivity()314 void StackEditorDialog::set_button_sensitivity()
315 {
316 Gtk::TreeIter i = army_treeview->get_selection()->get_selected();
317 int armies = army_list->children().size();
318 add_button->set_sensitive(armies < max_stack_size);
319 copy_button->set_sensitive(armies < max_stack_size);
320 remove_button->set_sensitive(armies > min_size && i);
321 if (i)
322 {
323 Army *army = (*i)[army_columns.army];
324 if (army->isHero())
325 {
326 edit_hero_button->set_sensitive(true);
327 copy_button->set_sensitive(false);
328 }
329 else
330 edit_hero_button->set_sensitive(false);
331 }
332 Player *player = get_selected_player();
333 bool neutral = player == Playerlist::getInstance()->getNeutral();
334 bool can_defend = GameMap::getInstance()->can_defend (stack);
335 if (neutral || !can_defend)
336 fortified_switch->set_sensitive(false);
337 else
338 fortified_switch->set_sensitive(true);
339 }
340
on_fortified_toggled()341 void StackEditorDialog::on_fortified_toggled()
342 {
343 stack->setFortified(fortified_switch->get_active());
344 }
345
on_player_changed()346 void StackEditorDialog::on_player_changed()
347 {
348 ImageCache *gc = ImageCache::getInstance();
349 Player *player = get_selected_player();
350 if (player == Playerlist::getInstance()->getNeutral())
351 fortified_switch->set_active(false);
352 set_button_sensitivity();
353
354 guint32 fs = FontSize::getInstance ()->get_height ();
355 for (Gtk::TreeIter j = army_list->children().begin(),
356 jend = army_list->children().end(); j != jend; ++j)
357 {
358 Army *a = (*j)[army_columns.army];
359 (*j)[army_columns.image] = gc->getArmyPic(player->getArmyset(),
360 a->getTypeId(),
361 player, NULL, false,
362 fs)->to_pixbuf();
363 }
364
365 update_armies ();
366 }
367
cell_data_strength(Gtk::CellRenderer * renderer,const Gtk::TreeIter & i)368 void StackEditorDialog::cell_data_strength(Gtk::CellRenderer *renderer,
369 const Gtk::TreeIter& i)
370 {
371 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_adjustment()
372 = Gtk::Adjustment::create((*i)[army_columns.strength],
373 MIN_STRENGTH_FOR_ARMY_UNITS,
374 MAX_STRENGTH_FOR_ARMY_UNITS, 1);
375 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_text() =
376 String::ucompose("%1", (*i)[army_columns.strength]);
377 }
378
on_strength_edited(const Glib::ustring & path,const Glib::ustring & new_text)379 void StackEditorDialog::on_strength_edited(const Glib::ustring &path,
380 const Glib::ustring &new_text)
381 {
382 int str = atoi(new_text.c_str());
383 if (str < (int)MIN_STRENGTH_FOR_ARMY_UNITS || str >
384 (int)MAX_STRENGTH_FOR_ARMY_UNITS)
385 return;
386 (*army_list->get_iter(Gtk::TreePath(path)))[army_columns.strength] = str;
387 update_armies ();
388 }
389
cell_data_moves(Gtk::CellRenderer * renderer,const Gtk::TreeIter & i)390 void StackEditorDialog::cell_data_moves(Gtk::CellRenderer *renderer,
391 const Gtk::TreeIter& i)
392 {
393 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_adjustment()
394 = Gtk::Adjustment::create((*i)[army_columns.moves],
395 MIN_MOVES_FOR_ARMY_UNITS,
396 MAX_MOVES_FOR_ARMY_UNITS, 1);
397 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_text() =
398 String::ucompose("%1", (*i)[army_columns.moves]);
399 }
400
on_moves_edited(const Glib::ustring & path,const Glib::ustring & new_text)401 void StackEditorDialog::on_moves_edited(const Glib::ustring &path,
402 const Glib::ustring &new_text)
403 {
404 int moves = atoi(new_text.c_str());
405 if (moves < (int)MIN_MOVES_FOR_ARMY_UNITS || moves >
406 (int)MAX_MOVES_FOR_ARMY_UNITS)
407 return;
408 (*army_list->get_iter(Gtk::TreePath(path)))[army_columns.moves] = moves;
409 update_armies ();
410 }
411
cell_data_upkeep(Gtk::CellRenderer * renderer,const Gtk::TreeIter & i)412 void StackEditorDialog::cell_data_upkeep(Gtk::CellRenderer *renderer,
413 const Gtk::TreeIter& i)
414 {
415 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_adjustment()
416 = Gtk::Adjustment::create((*i)[army_columns.upkeep],
417 MIN_UPKEEP_FOR_ARMY_UNITS,
418 MAX_UPKEEP_FOR_ARMY_UNITS, 1);
419 dynamic_cast<Gtk::CellRendererSpin*>(renderer)->property_text() =
420 String::ucompose("%1", (*i)[army_columns.upkeep]);
421 }
422
on_upkeep_edited(const Glib::ustring & path,const Glib::ustring & new_text)423 void StackEditorDialog::on_upkeep_edited(const Glib::ustring &path,
424 const Glib::ustring &new_text)
425 {
426 int upkeep = atoi(new_text.c_str());
427 if (upkeep < (int)MIN_UPKEEP_FOR_ARMY_UNITS ||
428 upkeep > (int)MAX_UPKEEP_FOR_ARMY_UNITS)
429 return;
430 (*army_list->get_iter(Gtk::TreePath(path)))[army_columns.upkeep] = upkeep;
431 update_armies ();
432 }
433