initial commit, lordsawar source, slightly modified
[lordsawar] / src / gui / city-window.cpp
1 //  Copyright (C) 2007 Ole Laursen
2 //  Copyright (C) 2007, 2008, 2009 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 <assert.h>
22
23 #include <gtkmm.h>
24 #include <sigc++/functors/mem_fun.h>
25
26 #include "city-window.h"
27
28 #include "glade-helpers.h"
29 #include "image-helpers.h"
30 #include "input-helpers.h"
31 #include "ucompose.hpp"
32 #include "defs.h"
33 #include "army.h"
34 #include "armyprodbase.h"
35 #include "player.h"
36 #include "city.h"
37 #include "GraphicsCache.h"
38 #include "armysetlist.h"
39 #include "buy-production-dialog.h"
40 #include "destination-dialog.h"
41 #include "GameMap.h"
42 #include "citylist.h"
43 #include "playerlist.h"
44 #include "File.h"
45
46 CityWindow::CityWindow(City *c, bool razing_possible, 
47                        bool see_opponents_production)
48 {
49   army_info_tip = NULL;
50     city = c;
51     
52     Glib::RefPtr<Gtk::Builder> xml
53         = Gtk::Builder::create_from_file(get_glade_path() + "/city-window.ui");
54
55     xml->get_widget("dialog", dialog);
56     decorate(dialog);
57     window_closed.connect(sigc::mem_fun(dialog, &Gtk::Dialog::hide));
58     set_title(c->getName());
59     
60     xml->get_widget("map_image", map_image);
61
62     prodmap = new VectorMap(c, VectorMap::SHOW_ORIGIN_CITY_VECTORING,
63                   see_opponents_production);
64     prodmap->map_changed.connect(
65         sigc::mem_fun(this, &CityWindow::on_map_changed));
66
67     Gtk::EventBox *map_eventbox;
68     xml->get_widget("map_eventbox", map_eventbox);
69     map_eventbox->add_events(Gdk::BUTTON_PRESS_MASK);
70     map_eventbox->signal_button_press_event().connect(
71         sigc::mem_fun(*this, &CityWindow::on_map_mouse_button_event));
72     xml->get_widget("status_label", status_label);
73     xml->get_widget("turns_left_label", turns_left_label);
74     xml->get_widget("current_label", current_label);
75     xml->get_widget("current_image", current_image);
76     xml->get_widget("production_info_label1", production_info_label1);
77     xml->get_widget("production_info_label2", production_info_label2);
78     xml->get_widget("buy_button", buy_button);
79     xml->get_widget("on_hold_button", on_hold_button);
80     on_hold_button->signal_clicked().connect(
81             sigc::mem_fun(this, &CityWindow::on_on_hold_clicked));
82     buy_button->signal_clicked().connect(
83         sigc::mem_fun(this, &CityWindow::on_buy_clicked));
84     xml->get_widget("destination_button", destination_button);
85     destination_button->signal_clicked().connect(
86         sigc::mem_fun(this, &CityWindow::on_destination_clicked));
87     xml->get_widget("rename_button", rename_button);
88     rename_button->signal_clicked().connect(
89         sigc::mem_fun(this, &CityWindow::on_rename_clicked));
90     xml->get_widget("raze_button", raze_button);
91     raze_button->signal_clicked().connect(
92         sigc::mem_fun(this, &CityWindow::on_raze_clicked));
93
94     xml->get_widget("production_toggles_hbox", production_toggles_hbox);
95     for (unsigned int i = 1; i <= city->getMaxNoOfProductionBases(); ++i) {
96         Gtk::ToggleButton *toggle = new Gtk::ToggleButton();
97         production_toggles_hbox->pack_start(*manage(toggle), false, false, 0);
98         production_toggles.push_back(toggle);
99         toggle->signal_toggled().connect(
100             sigc::bind(sigc::mem_fun(this, &CityWindow::on_production_toggled),
101                        toggle));
102         toggle->add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
103         toggle->signal_button_press_event().connect(
104             sigc::bind(sigc::mem_fun(*this, &CityWindow::on_production_button_event),
105                        toggle), false);
106         
107         toggle->signal_button_release_event().connect(
108             sigc::bind(sigc::mem_fun(*this, &CityWindow::on_production_button_event),
109                        toggle), false);
110         
111     }
112
113     d_razing_possible = razing_possible;
114     fill_in_city_info();
115     fill_in_production_toggles();
116
117     ignore_toggles = false;
118 }
119
120 CityWindow::~CityWindow()
121 {
122   delete dialog;
123   delete prodmap;
124   if (army_info_tip != NULL)
125     delete army_info_tip;
126 }
127
128 bool CityWindow::on_map_mouse_button_event(GdkEventButton *e)
129 {
130     if (e->type != GDK_BUTTON_PRESS)
131         return true;    // useless event
132     
133     prodmap->mouse_button_event(to_input_event(e));
134     
135     city = prodmap->getCity();
136     fill_in_city_info();
137     fill_in_production_toggles();
138     fill_in_production_info();
139     return true;
140 }
141
142 void CityWindow::set_parent_window(Gtk::Window &parent)
143 {
144     dialog->set_transient_for(parent);
145     //dialog->set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
146 }
147
148 void CityWindow::hide()
149 {
150   dialog->hide();
151 }
152
153 void CityWindow::run()
154 {
155     prodmap->resize();
156     prodmap->draw(Playerlist::getActiveplayer());
157     dialog->show();
158     dialog->run();
159 }
160
161 void CityWindow::fill_in_city_info()
162 {
163     
164     set_title(city->getName());
165
166     // fill in status label
167     Glib::ustring s;
168     if (city->isCapital())
169     {
170         s += String::ucompose(_("Capital city of %1"),
171                               city->getCapitalOwner()->getName());
172         s += "\n";
173     }
174
175     s += String::ucompose(_("Defence: %1"), city->getDefenseLevel());
176     s += "\n";
177     s += String::ucompose(_("Income: %1"), city->getGold());
178     status_label->set_text(s);
179 }
180
181 void CityWindow::fill_in_production_toggles()
182 {
183     Player *player = city->getOwner();
184     unsigned int as = player->getArmyset();
185     int production_index = city->getActiveProductionSlot();
186     int type;
187     Glib::RefPtr<Gdk::Pixbuf> pic;
188     GraphicsCache *gc = GraphicsCache::getInstance();
189
190     Glib::RefPtr<Gdk::Pixbuf> s
191         = GraphicsCache::getInstance()->getArmyPic(as, 0, player, NULL)->to_pixbuf();
192     Glib::RefPtr<Gdk::Pixbuf> empty_pic
193         = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, s->get_width(), s->get_height());
194     empty_pic->fill(0x00000000);
195     
196     ignore_toggles = true;
197     for (unsigned int i = 0; i < city->getMaxNoOfProductionBases(); i++)
198     {
199         Gtk::ToggleButton *toggle = production_toggles[i];
200         toggle->foreach(sigc::mem_fun(toggle, &Gtk::Container::remove));
201
202         type = city->getArmytype(i);
203         if (type != -1)
204             // use GraphicsCache to load army pics because of player-specific
205             // colors
206             pic = gc->getArmyPic(as, type, player, NULL)->to_pixbuf();
207         else
208             pic = empty_pic;
209         
210         Gtk::Image *image = new Gtk::Image();
211         image->property_pixbuf() = pic;
212         toggle->add(*manage(image));
213
214         toggle->set_active((int)i == production_index);
215         toggle->show_all();
216     }
217     ignore_toggles = false;
218
219     on_hold_button->set_sensitive(production_index != -1);
220     fill_in_production_info();
221
222 }
223
224 void CityWindow::on_production_toggled(Gtk::ToggleButton *toggle)
225 {
226     if (city->getOwner() != Playerlist::getActiveplayer())
227     {
228         toggle->set_active(false);
229         return;
230     }
231     if (ignore_toggles)
232         return;
233     
234     int slot = -1;
235     ignore_toggles = true;
236     for (unsigned int i = 0; i < production_toggles.size(); ++i) {
237         if (toggle == production_toggles[i])
238             slot = i;
239         
240         production_toggles[i]->set_active(toggle == production_toggles[i]);
241     }
242     ignore_toggles = false;
243
244     bool is_empty = city->getArmytype(slot) == -1;
245     
246     if (is_empty)
247         city->getOwner()->cityChangeProduction(city, -1);
248     else
249         city->getOwner()->cityChangeProduction(city, slot);
250
251     on_hold_button->set_sensitive(!is_empty);
252     
253     fill_in_production_info();
254 }
255
256 void CityWindow::fill_in_production_info()
257 {
258     Player *player = city->getOwner();
259     unsigned int as = player->getArmyset();
260     Glib::RefPtr<Gdk::Pixbuf> pic;
261     GraphicsCache *gc = GraphicsCache::getInstance();
262     int slot = city->getActiveProductionSlot();
263     Glib::RefPtr<Gdk::Pixbuf> s
264         = GraphicsCache::getInstance()->getArmyPic(as, 0, player, NULL)->to_pixbuf();
265     Glib::RefPtr<Gdk::Pixbuf> empty_pic
266         = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, s->get_width(), s->get_height());
267     empty_pic->fill(0x00000000);
268     
269     Glib::ustring s1, s2, s3;
270     Glib::ustring s4 = _("Current:");
271     
272     if (slot == -1)
273     {
274         pic = empty_pic;
275         s1 = _("No production");
276         s1 += "\n\n";
277         s2 = "\n\n";
278         s3 = "";
279     }
280     else
281     {
282         const ArmyProdBase * a = city->getProductionBase(slot);
283
284         // fill in first column
285         s1 += a->getName();
286         s1 += "\n";
287         // note to translators: %1/%2 is the no. of steps completed out of the
288         // total no. of steps in the production
289         s1 += String::ucompose(_("Time: %1"), a->getProduction());
290         s1 += "\n";
291         s1 += String::ucompose(_("Strength: %1"),
292                               a->getStrength());
293         
294         // fill in second column
295         s2 += "\n";
296         s2 += String::ucompose(_("Moves: %1"), a->getMaxMoves());
297         s2 += "\n";
298         s2 += String::ucompose(_("Cost: %1"), a->getUpkeep());
299
300         s3 = String::ucompose(_("%1t"), city->getDuration());
301         if (city->getVectoring() != Vector<int>(-1, -1))
302           {
303             Citylist *cl = Citylist::getInstance();
304             City *dest = cl->getNearestFriendlyCity(city->getVectoring(), 4);
305             s3 += String::ucompose(_(", then to %1"), 
306                                    dest ? dest->getName() : "Standard");
307           }
308       pic = gc->getArmyPic(as, a->getTypeId(), player, NULL)->to_pixbuf();
309     }
310     
311     current_image->property_pixbuf() = pic;
312     production_info_label1->set_markup(s1);
313     production_info_label2->set_markup(s2);
314     turns_left_label->set_markup("<i>" + s3 + "</i>");
315     current_label->set_markup("<i>" + s4 + "</i>");
316
317     if (city->getOwner () != Playerlist::getActiveplayer())
318       {
319         turns_left_label->hide();
320         current_label->hide();
321         current_image->hide();
322         buy_button->set_sensitive(false);
323         raze_button->set_sensitive(false);
324         rename_button->set_sensitive(false);
325         destination_button->set_sensitive(false);
326         on_hold_button->set_sensitive(false);
327         for (unsigned int i = 0; i < production_toggles.size(); ++i) 
328           {
329            // production_toggles[i]->set_sensitive(false);
330             production_toggles[i]->set_active(false);
331           }
332         production_info_label1->hide();
333         production_info_label2->hide();
334       }
335     else
336       {
337         turns_left_label->show();
338         current_label->show();
339         current_image->show();
340         buy_button->set_sensitive(true);
341         raze_button->set_sensitive (d_razing_possible);
342         rename_button->set_sensitive(true);
343         destination_button->set_sensitive(true);
344         on_hold_button->set_sensitive(true);
345         //for (unsigned int i = 0; i < production_toggles.size(); ++i) 
346           //production_toggles[i]->set_sensitive(true);
347         production_info_label1->show();
348         production_info_label2->show();
349       }
350 }
351
352 bool CityWindow::on_production_button_event(GdkEventButton *e, Gtk::ToggleButton *toggle)
353 {
354     MouseButtonEvent event = to_input_event(e);
355     if (event.button == MouseButtonEvent::RIGHT_BUTTON
356         && event.state == MouseButtonEvent::PRESSED) {
357         int slot = -1;
358         for (unsigned int i = 0; i < production_toggles.size(); ++i) {
359             if (toggle == production_toggles[i])
360                 slot = i;
361         }
362         assert(slot != -1);
363
364         const ArmyProdBase *prodbase = city->getProductionBase(slot);
365
366         if (prodbase)
367           {
368             if (army_info_tip != NULL)
369                 delete army_info_tip;
370             army_info_tip = new ArmyInfoTip(toggle, prodbase, city);
371           }
372         return true;
373     }
374     else if (event.button == MouseButtonEvent::RIGHT_BUTTON
375              && event.state == MouseButtonEvent::RELEASED) {
376         {
377           if (army_info_tip != NULL)
378             {
379               delete army_info_tip;
380               army_info_tip = NULL;
381             }
382         }
383         return true;
384     }
385     
386     return false;
387 }
388
389 void CityWindow::on_on_hold_clicked() //stop button
390 {
391     city->setVectoring(Vector<int>(-1,-1));
392     city->getOwner()->cityChangeProduction(city, -1);
393     on_hold_button->set_sensitive(false);
394     ignore_toggles = true;
395     for (unsigned int i = 0; i < production_toggles.size(); ++i)
396         production_toggles[i]->set_active(false);
397     ignore_toggles = false;
398     fill_in_production_info();
399     prodmap->draw(Playerlist::getActiveplayer());
400 }
401
402 void CityWindow::on_buy_clicked()
403 {
404     BuyProductionDialog d(city);
405     d.set_parent_window(*dialog);
406     d.run();
407
408     int army = d.get_selected_army();
409     d.hide();
410     if (army != BuyProductionDialog::NO_ARMY_SELECTED)
411     {
412         int slot = -1;
413         slot = city->getFreeSlot();
414         
415         if  (slot == -1)
416           {
417             //no free slots available.  change the one we're on.
418             slot = city->getActiveProductionSlot();
419             if (slot == -1) 
420               slot = 0;
421           }
422         city->getOwner()->cityBuyProduction(city, slot, army);
423         city->getOwner()->cityChangeProduction(city, slot);
424
425         fill_in_production_toggles();
426         fill_in_production_info();
427
428     }
429 }
430
431 void CityWindow::on_destination_clicked()
432 {
433     DestinationDialog d(city, &d_see_all);
434     d.set_parent_window(*dialog);
435     d.run();
436     fill_in_production_info();
437     prodmap->draw(Playerlist::getActiveplayer());
438 }
439
440 void CityWindow::on_map_changed(Glib::RefPtr<Gdk::Pixmap> map)
441 {
442     map_image->property_pixmap() = map;
443 }
444
445 void CityWindow::on_rename_clicked ()
446 {
447     Gtk::Dialog* subdialog;
448     
449     Glib::RefPtr<Gtk::Builder> renamexml
450         = Gtk::Builder::create_from_file(get_glade_path() + "/city-rename-dialog.ui");
451         
452     renamexml->get_widget("dialog", subdialog);
453     Decorated decorator;
454     decorator.decorate(subdialog);
455     decorator.window_closed.connect(sigc::mem_fun(subdialog, &Gtk::Dialog::hide));
456     subdialog->set_transient_for(*dialog);
457     
458     Glib::ustring s = _("Rename City");
459
460     Gtk::Label *l;
461     renamexml->get_widget("label", l);
462     Gtk::Entry *e;
463     renamexml->get_widget("name_entry", e);
464
465     decorator.set_title(s);
466     s = _("Type the new name for this city:");
467     l->set_text(s);
468
469     e->set_text(city->getName());
470     subdialog->show_all();
471     int response = subdialog->run();
472
473     if (response == Gtk::RESPONSE_ACCEPT)               // changed city name
474       {
475         Playerlist::getActiveplayer()->cityRename(city, e->get_text());
476         fill_in_city_info();
477       }
478     subdialog->hide();
479     delete subdialog;
480   return;
481 }
482
483 void CityWindow::on_raze_clicked ()
484 {
485   on_raze_clicked (city, dialog);
486 }
487
488 bool CityWindow::on_raze_clicked (City *city, Gtk::Dialog *parent)
489 {
490     Gtk::Dialog* subdialog;
491     
492     Glib::RefPtr<Gtk::Builder> razexml
493         = Gtk::Builder::create_from_file(get_glade_path() + "/city-raze-dialog.ui");
494         
495     razexml->get_widget("dialog", subdialog);
496     Decorated decorator;
497     decorator.decorate(subdialog);
498     decorator.window_closed.connect(sigc::mem_fun(subdialog, &Gtk::Dialog::hide));
499     subdialog->set_transient_for(*parent);
500     subdialog->set_icon_from_file(File::getMiscFile("various/castle_icon.png"));
501     
502     Glib::ustring s = _("Raze City");
503
504     Gtk::Label *l;
505     razexml->get_widget("label", l);
506
507     decorator.set_title(s);
508     s = String::ucompose(_("Are you sure that you want to raze %1?"), 
509                            city->getName());
510     s += "\n";
511     s += _("You won't be popular!");
512     l->set_text(s);
513
514     subdialog->show_all();
515     int response = subdialog->run();
516     subdialog->hide();
517     delete subdialog;
518
519     if (response == Gtk::RESPONSE_ACCEPT) // burn it to the ground ralphie boy!
520       {
521         Playerlist::getActiveplayer()->cityRaze(city);
522         parent->hide();
523         return true;
524       }
525   return false;
526 }