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 "bug_report_replay.h"
33 #include "gameplay_actions.h"
34 
35 #include "ui.h"
36 #include "thrower.h"
37 #include "bug_report.h"
38 #include "main_window.h"
39 #include "cards.h"
40 
41 #include "../../os/bug_report_replay.h"
42 #include "../../game/game.h"
43 #include "../../player/player.h"
44 #include "../../player/aiconfig.h"
45 
46 #include "../../utils/string.h"
47 
48 #include "game_summary.h"
49 #include <gtkmm/notebook.h>
50 #include <gtkmm/label.h>
51 #include <gtkmm/textview.h>
52 #include <gtkmm/scrolledwindow.h>
53 
54 namespace UI_GTKMM_NS {
55 
56 /** Constructor
57  **
58  ** @param    parent   the parent widget
59  **/
BugReportReplay(Base * const parent)60 BugReportReplay::BugReportReplay(Base* const parent) :
61   Base(parent),
62   StickyDialog("FreeDoko – " + _("Window::Bug report replay"), false)
63 {
64   this->ui->add_window(*this);
65 
66   this->actions       = Gtk::manage(new GameplayActions(this));
67   this->human_actions = Gtk::manage(new GameplayActions(this));
68   this->game_summary  = Gtk::manage(new GameSummary(this));
69 
70   this->signal_realize().connect(sigc::mem_fun(*this, &BugReportReplay::init));
71 
72   this->ui->bug_report->set_dnd_destination(*this);
73 
74   Party::signal_open().connect_back([this](Party& party) {
75                                     party.signal_get_settings().connect_back(*this, &BugReportReplay::update_info, this->disconnector_);
76                                     },
77                                     this->disconnector_);
78   Game::signal_open().connect_back([this](Game& game) {
79                                    this->update_info();
80                                    game.signal_redistribute().connect_back(*this, &BugReportReplay::update_info, this->disconnector_);
81                                    game.signal_gameplay_action().connect_back(*this, &BugReportReplay::gameplay_action, this->disconnector_);
82                                    },
83                                    this->disconnector_);
84   OS_NS::BugReportReplay::signal_open().connect_back(*this,
85                                                      &UI_GTKMM_NS::BugReportReplay::bug_report_replay_open,
86                                                      this->disconnector_);
87 } // BugReportReplay::BugReportReplay(Base* parent)
88 
89 /** destructor
90  **/
91 BugReportReplay::~BugReportReplay() = default;
92 
93 /** create all subelements
94  **/
95 void
init()96 BugReportReplay::init()
97 {
98   this->set_icon(this->ui->icon);
99 
100   { // action area
101     auto end_button
102       = Gtk::manage(new Gtk::Button(_("Button::end bug report")));
103 
104     end_button->signal_clicked().connect(sigc::mem_fun(*this,
105                                                        &BugReportReplay::end_bug_report));
106 
107     add_close_button(*this);
108   } // action area
109 
110   { // informations
111     this->version = Gtk::manage(new Gtk::Label(""));
112     this->version->set_justify(Gtk::JUSTIFY_LEFT);
113     this->compiled = Gtk::manage(new Gtk::Label(""));
114     this->compiled->set_justify(Gtk::JUSTIFY_LEFT);
115     this->system = Gtk::manage(new Gtk::Label(""));
116     this->system->set_justify(Gtk::JUSTIFY_LEFT);
117     this->time = Gtk::manage(new Gtk::Label(""));
118     this->time->set_justify(Gtk::JUSTIFY_LEFT);
119     this->language = Gtk::manage(new Gtk::Label(""));
120     this->language->set_justify(Gtk::JUSTIFY_LEFT);
121     this->trick = Gtk::manage(new Gtk::Label(""));
122     this->trick->set_justify(Gtk::JUSTIFY_LEFT);
123     this->reset_ais_button = Gtk::manage(new Gtk::Button(_("Button::reset ais")));
124     this->reset_ais_button->set_image_from_icon_name("edit-clear-all");
125     this->reset_ais_button->set_always_show_image();
126     this->message = Gtk::manage(new Gtk::TextView());
127     this->message->get_buffer()->set_text("");
128     this->message->set_wrap_mode(Gtk::WRAP_WORD);
129 #ifdef RELEASE
130     this->message->set_editable(false);
131     this->message->set_cursor_visible(false);
132 #else
133     // Note: the changes in the message box are not saved.
134 #endif
135 
136     this->seed = Gtk::manage(new Gtk::Label(""));
137     this->seed->set_justify(Gtk::JUSTIFY_LEFT);
138 
139     this->startplayer = Gtk::manage(new Gtk::Label(""));
140     this->startplayer->set_justify(Gtk::JUSTIFY_LEFT);
141 
142     this->soloplayer = Gtk::manage(new Gtk::Label(""));
143     this->soloplayer->set_justify(Gtk::JUSTIFY_LEFT);
144 
145     // hide the 'player' column
146     this->human_actions->get_column(1)->set_visible(false);
147   } // informations
148   { // vbox
149     this->notebook = Gtk::manage(new Gtk::Notebook());
150     { // general
151       auto label = Gtk::manage(new Gtk::Label(_("BugReportReplay::Group::general")));
152 
153       auto general_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 1 EM));
154       general_box->set_border_width(1 EM);
155 
156       { // game box
157         auto game_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 1 EX));
158 
159         game_box->pack_start(*this->version, Gtk::PACK_EXPAND_WIDGET);
160         game_box->pack_start(*this->system, Gtk::PACK_EXPAND_WIDGET);
161         game_box->pack_start(*this->seed, Gtk::PACK_EXPAND_WIDGET);
162         game_box->pack_start(*this->startplayer, Gtk::PACK_EXPAND_WIDGET);
163         game_box->pack_start(*this->soloplayer, Gtk::PACK_EXPAND_WIDGET);
164         game_box->pack_start(*this->trick, Gtk::PACK_EXPAND_WIDGET);
165         this->reset_ais_button->set_halign(Gtk::ALIGN_CENTER);
166         game_box->pack_start(*this->reset_ais_button, Gtk::PACK_EXPAND_WIDGET);
167 
168         general_box->pack_start(*game_box, false, true);
169       } // game box
170       { // message
171         general_box->add(*this->message);
172       } // message
173 
174       this->notebook->append_page(*general_box, *label);
175     } // general
176     { // actions
177       auto label = Gtk::manage(new Gtk::Label(_("BugReportReplay::Group::actions")));
178 
179       auto vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 1 EX));
180 
181       auto scrolled_window = Gtk::manage(new Gtk::ScrolledWindow);
182       scrolled_window->set_policy(Gtk::POLICY_AUTOMATIC,
183                                   Gtk::POLICY_AUTOMATIC);
184 
185       this->actions->get_selection()->set_mode(Gtk::SELECTION_SINGLE);
186       scrolled_window->add(*this->actions);
187 
188       vbox->pack_start(*scrolled_window, Gtk::PACK_EXPAND_WIDGET);
189 
190       this->auto_actions_button
191         = Gtk::manage(new Gtk::Button(_("Button::auto actions")));
192       vbox->pack_end(*this->auto_actions_button, Gtk::PACK_SHRINK);
193 
194       this->notebook->append_page(*vbox, *label);
195     } // actions
196     { // human actions
197       auto label = Gtk::manage(new Gtk::Label(_("BugReportReplay::Group::human actions")));
198       auto scrolled_window = Gtk::manage(new Gtk::ScrolledWindow);
199       scrolled_window->set_policy(Gtk::POLICY_AUTOMATIC,
200                                   Gtk::POLICY_AUTOMATIC);
201 
202       scrolled_window->add(*this->human_actions);
203 
204       this->notebook->append_page(*scrolled_window, *label);
205     } // human actions
206     { // game summary
207       auto label = Gtk::manage(new Gtk::Label(_("BugReportReplay::Group::game summary")));
208 
209       this->notebook->append_page(*this->game_summary, *label);
210     } // game summary
211 
212     this->get_content_area()->pack_start(*this->notebook, true, true);
213   } // vbox
214 
215   { // signals
216     reset_ais_button->signal_clicked().connect(sigc::mem_fun(*this, &BugReportReplay::reset_ais));
217     this->actions->get_selection()->signal_changed().connect(sigc::mem_fun(*this, &BugReportReplay::update_auto_actions_button));
218     this->auto_actions_button->signal_clicked().connect(sigc::mem_fun(*this, &BugReportReplay::set_auto_actions));
219     this->update_auto_actions_button();
220   } // signals
221 
222   this->show_all_children();
223 
224 
225   {
226     Gdk::Geometry geometry;
227     geometry.min_width = 3 * this->ui->cards->height();
228     geometry.min_height = 4 * this->ui->cards->height();
229 
230     this->set_geometry_hints(*this, geometry, Gdk::HINT_MIN_SIZE);
231   }
232 } // void BugReportReplay::init()
233 
234 /** a bug report replay is opened
235  **
236  ** @param    bug_report_replay   the bug report replay that is opened
237  **/
238 void
bug_report_replay_open(OS_NS::BugReportReplay & bug_report_replay)239 BugReportReplay::bug_report_replay_open(OS_NS::BugReportReplay&
240                                         bug_report_replay)
241 {
242   DEBUG_ASSERTION((&bug_report_replay == ::bug_report_replay.get()),
243                   "BugReportReplay::bug_report_replay_open(bug_report_replay)\n"
244                   "  the bug report replay "
245                   " = " << &bug_report_replay
246                   << " != " << ::bug_report_replay.get()
247                   << " = global one");
248   this->bug_report_replay_ = &bug_report_replay;
249   this->bug_report_replay_->signal_close().connect_back(*this,
250                                                         &UI_GTKMM_NS::BugReportReplay::bug_report_replay_close,
251                                                         this->disconnector_);
252 
253   if (!this->get_realized())
254     this->realize();
255 
256   this->actions->set_actions(bug_report_replay.actions());
257   this->actions->set_discrepancies(bug_report_replay.actions_discrepancies());
258   this->human_actions->set_actions(bug_report_replay.human_actions());
259   this->human_actions->set_discrepancies(bug_report_replay.human_actions_discrepancies());
260   if (bug_report_replay.game_summary()) {
261     this->game_summary->set_game_summary(*bug_report_replay.game_summary());
262     this->game_summary->show();
263   } else {
264     this->game_summary->hide();
265   }
266   // ToDo: hide the last page if there is no game summary in the bug report
267 
268   this->update_info();
269   this->notebook->set_current_page(0);
270 
271   if (!(::fast_play & FastPlay::hide_bug_report_window)) {
272     this->show();
273   }
274 } // void BugReportReplay::bug_report_replay_open(OS_NS::BugReportReplay bug_report_replay)
275 
276 /** the bug report replay is closed
277  **/
278 void
bug_report_replay_close()279 BugReportReplay::bug_report_replay_close()
280 {
281   this->actions->remove_actions();
282   this->human_actions->remove_actions();
283   if (   this->bug_report_replay_
284       && ::bug_report_replay
285       && this->bug_report_replay_ == ::bug_report_replay.get()) {
286     // 'if' is workaround: first hide then show leaves the window hidden
287     this->bug_report_replay_ = nullptr;
288     this->hide();
289   }
290 } // BugReportReplay::bug_report_replay_close()
291 
292 /** there happened a gameplay action
293  **
294  ** @param    action   gameplay action
295  **/
296 void
gameplay_action(GameplayAction const & action)297 BugReportReplay::gameplay_action(GameplayAction const& action)
298 {
299   if (!this->bug_report_replay_)
300     return ;
301 
302   this->actions->set_current_action_no(this->bug_report_replay_->current_action_no());
303   this->human_actions->set_current_action_no(this->bug_report_replay_->current_human_action_no());
304   this->update_auto_actions_button();
305 } // void BugReportReplay::gameplay_action(GameplayAction action)
306 
307 /** updates the information
308  **/
309 void
update_info()310 BugReportReplay::update_info()
311 {
312   if (!this->bug_report_replay_)
313     return ;
314 
315   DEBUG_ASSERTION((this->bug_report_replay_ == ::bug_report_replay.get()),
316                   "BugReportReplay::update_info():\n"
317                   "  this->bug_report_replay_ != ::bug_report_replay:\n"
318                   "  " << this->bug_report_replay_
319                   << " != " << ::bug_report_replay.get());
320 
321   if (::game_status == GameStatus::programstart)
322     return ;
323 #if 0
324   if (!&this->party())
325     return ;
326 #endif
327 
328 
329 #ifdef WORKAROUND
330   // This function can be called before this party is set
331   // (by ::bug_report_replay). So we take a party here that always exists.
332   // An alternitive would be to check, whether this party is set, but
333   // 'UI::party_' is private.
334   Party const& party = *::party;
335 #else
336   Party const& party = this->ui->party();
337 #endif
338 
339   this->version->set_label(_("Version: %s",
340                              static_cast<string const>(this->bug_report_replay_->version())));
341   this->compiled->set_label(_("Compiled: %s",
342                               this->bug_report_replay_->compiled()));
343   this->system->set_label(_("System: %s",
344                             this->bug_report_replay_->system()));
345   this->time->set_label(_("Time: %s",
346                           this->bug_report_replay_->time()));
347   this->language->set_label(_("Language: %s",
348                               this->bug_report_replay_->language()));
349   this->trick->set_label(_("Trick: %u",
350                            this->bug_report_replay_->trickno()));
351 
352 #ifdef WORKAROUND
353   // message: change iso to utf8
354   this->message->get_buffer()->set_text(UI_GTKMM::to_utf8(this->bug_report_replay_->message()));
355 #else
356   this->message->get_buffer()->set_text(this->bug_report_replay_->message());
357 #endif
358 
359 
360   this->seed->set_label(String::to_string(this->bug_report_replay_->seed()));
361 
362   this->startplayer->set_label(String::to_string(this->bug_report_replay_->startplayer_no()));
363   this->seed->set_label(_("Seed: %u", this->bug_report_replay_->seed()));
364 
365   if (this->bug_report_replay_->startplayer_no() != UINT_MAX)
366     this->startplayer->set_label(_("Startplayer: %u",
367                                    this->bug_report_replay_->startplayer_no())
368                                  + " (" + party.players()[this->bug_report_replay_->startplayer_no()].name() + ")");
369   else
370     this->startplayer->set_label(_("Startplayer: %u",
371                                    this->bug_report_replay_->startplayer_no())
372                                  + " (-)"
373                                 );
374 
375   if (this->bug_report_replay_->soloplayer_no() != UINT_MAX)
376     this->soloplayer->set_label(_("Soloplayer: %u",
377                                   this->bug_report_replay_->soloplayer_no())
378                                 + " (" + party.players()[this->bug_report_replay_->soloplayer_no()].name() + ")"
379                                );
380   else
381     this->soloplayer->set_label(_("Soloplayer: -"));
382 
383   this->actions->update();
384   this->human_actions->update();
385 
386   if (this->bug_report_replay_->game_summary())
387     this->game_summary->update();
388 
389   this->update_actions_past();
390 } // void BugReportReplay::update_info()
391 
392 /** update the showing of the actions already made
393  **
394  ** @todo      all
395  **/
396 void
update_actions_past()397 BugReportReplay::update_actions_past()
398 {
399   if (!this->get_realized())
400     return ;
401 
402 #if 0
403   for (auto& row : this->actions->get_model()->children()) {
404     for (auto col : this->actions->get_columns()) {
405       // ToDo
406     } // for (col \in this->actsion->get_columns())
407   } // for (row \in this->actions)
408 #endif
409 } // void BugReportReplay::update_actions_past()
410 
411 /** update the auto actions button
412  **/
413 void
update_auto_actions_button()414 BugReportReplay::update_auto_actions_button()
415 {
416   if (!this->get_realized())
417     return ;
418 
419   auto selection = this->actions->get_selection();
420 
421   if (selection->get_selected()) {
422     auto row = *(selection->get_selected());
423 
424     unsigned const actionno = row[this->actions->model.no];
425 
426     if (actionno > this->bug_report_replay_->current_action_no()) {
427       this->auto_actions_button->set_sensitive(true);
428     } else {
429       this->auto_actions_button->set_sensitive(false);
430     }
431   } else {	// if !(selection->get_selected())
432     this->auto_actions_button->set_sensitive(false);
433   }	// if !(selection->get_selected())
434 } // void BugReportReplay::update_auto_actions_button()
435 
436 /** automatic execute till the selected action
437  **/
438 void
set_auto_actions()439 BugReportReplay::set_auto_actions()
440 {
441   auto selection = this->actions->get_selection();
442 
443   DEBUG_ASSERTION(selection->get_selected(),
444                   "BugReportReplay::set_auto_actions()\n"
445                   "  no action selected");
446   auto row = *(selection->get_selected());
447 
448   unsigned const actionno = row[this->actions->model.no];
449 
450   this->bug_report_replay_->set_auto_action_end(actionno);
451   if (   (::game_status == GameStatus::game_play)
452       && (this->ui->game().players().current_player().type() == Player::Type::human) ) {
453     auto& player = this->ui->game().players().current_player();
454     player.hand().request_card(player.card_get());
455     this->ui->thrower(player.hand().requested_card(), __FILE__, __LINE__);
456   } // if (human has to play a card)
457 } // void BugReportReplay::set_auto_actions()
458 
459 /** end the bug report
460  **/
461 void
end_bug_report()462 BugReportReplay::end_bug_report()
463 {
464   this->bug_report_replay_ = nullptr;
465 } // void BugReportReplay::end_bug_report()
466 
467 /** reset the ais
468  **/
469 void
reset_ais()470 BugReportReplay::reset_ais()
471 {
472   int n = 0;
473   for (auto& p : this->ui->party().players()) {
474     if (dynamic_cast<Aiconfig*>(&p))
475       dynamic_cast<Aiconfig&>(p).reset_to_hardcoded(n);
476     n += 1;
477   }
478 } // void BugReportReplay::reset_ais()
479 
480 /** the name of 'player' has changed
481  **
482  ** @param    player   player whose name has changed
483  **/
484 void
name_changed(Player const & player)485 BugReportReplay::name_changed(Player const& player)
486 {
487   if (!this->get_realized())
488     return ;
489 
490   if (!this->bug_report_replay_)
491     return ;
492 
493   if (::game_status & GameStatus::game) {
494     if (this->bug_report_replay_->startplayer_no()
495         == player.no())
496       this->startplayer->set_label(_("Startplayer: %u",
497                                      player.no())
498                                    + " (" + player.name() + ")"
499                                   );
500 
501     if (this->bug_report_replay_->soloplayer_no() == player.no())
502       this->soloplayer->set_label(_("Soloplayer: %u",
503                                     player.no())
504                                   + " (" + player.name() + ")"
505                                  );
506   } // if (::game_status & GameStatus::game)
507 
508   this->actions->name_changed(player);
509   this->human_actions->name_changed(player);
510   this->game_summary->name_changed(player);
511 } // void BugReportReplay::name_changed(Player player)
512 
513 } // namespace UI_GTKMM_NS
514 
515 #endif // #ifdef USE_UI_GTKMM
516