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 "icongroup.h"
33 #include "table.h"
34 #include "ui.h"
35
36 #include "cards.h"
37 #include "icons.h"
38 #include "hand.h"
39 #include "name.h"
40
41 #include "../../game/game.h"
42 #include "../../party/rule.h"
43 #include "../../player/player.h"
44 #include "../../player/human/human.h"
45 #include "../../misc/preferences.h"
46
47 namespace UI_GTKMM_NS {
48
49 /** constructor
50 **
51 ** @param table the table
52 ** @param position the position of the icongroup
53 **/
Icongroup(Table & table,Position const position)54 Icongroup::Icongroup(Table& table, Position const position) :
55 HTIN(table, position)
56 { }
57
58 /** destructor
59 **/
60 Icongroup::~Icongroup()
61 = default;
62
63 /**
64 **/
65 bool
changed() const66 Icongroup::changed() const
67 {
68 if (!this->surface_)
69 return true;
70 if (::game_status & GameStatus::game)
71 return (this->last_status_ != this->current_status());
72 return true;
73 } // bool Icongroup::changed() const
74
75 /** draws the icongroup
76 **
77 ** @param cr drawing context
78 **/
79 void
draw(Cairo::RefPtr<::Cairo::Context> cr)80 Icongroup::draw(Cairo::RefPtr<::Cairo::Context> cr)
81 {
82 cr->save();
83 cr->push_group();
84 this->last_status_ = this->current_status();
85 this->draw_team(cr);
86 this->draw_swines(cr);
87 this->draw_announcement(cr);
88 cr->pop_group_to_source();
89 cr->paint();
90 cr->restore();
91
92 return ;
93 } // void Icongroup::draw(Cairo::RefPtr<::Cairo::Context> cr)
94
95 /** draws the team/gametype icon
96 **
97 ** @param cr cairo context
98 **
99 ** @todo silent marriage
100 **/
101 void
draw_team(Cairo::RefPtr<::Cairo::Context> cr)102 Icongroup::draw_team(Cairo::RefPtr<::Cairo::Context> cr)
103 {
104 if(!(::game_status & GameStatus::game))
105 return ;
106
107 Game const& game = this->game();
108 if ( (::game_status < GameStatus::game_reservation)
109 && !( game.players().exists_soloplayer()
110 && (this->player() == game.players().soloplayer()) ) )
111 return ;
112 if (!this->last_status_.team_icon)
113 return ;
114
115 auto icon = this->ui->icons->icon(this->last_status_.team_icon);
116 if (!icon)
117 return;
118
119 if (this->last_status_.team_guessed) {
120 Glib::RefPtr<Gdk::Pixbuf> copy = icon->copy();
121 icon->saturate_and_pixelate(copy, 5, true);
122 icon = copy;
123 }
124
125 cr->save();
126 cr->push_group();
127 auto geometry = this->geometry();
128 geometry.transform(cr);
129 draw_pixbuf(cr, icon,
130 ( geometry.flip_x()
131 ? geometry.width() - icon->get_width()
132 : 0),
133 -icon->get_height());
134 cr->pop_group_to_source();
135 cr->paint();
136 cr->restore();
137 } // void Icongroup::draw_team(Cairo::RefPtr<::Cairo::Context> cr)
138
139 /** draws the announcement icon
140 **
141 ** @param cr cairo context
142 **/
143 void
draw_announcement(Cairo::RefPtr<::Cairo::Context> cr)144 Icongroup::draw_announcement(Cairo::RefPtr<::Cairo::Context> cr)
145 {
146 if (! ( (::game_status & GameStatus::game)
147 && (::game_status > GameStatus::game_reservation)) )
148 return ;
149
150 auto const& announcement = this->last_status_.announcement;
151 if (announcement == Announcement::noannouncement)
152 return ;
153
154 auto icon = this->ui->icons->icon(announcement);
155 if (this->last_status_.next_announcement) {
156 Glib::RefPtr<Gdk::Pixbuf> copy = icon->copy();
157 icon->saturate_and_pixelate(copy, 4, true);
158 icon = copy;
159 }
160
161 cr->save();
162 cr->push_group();
163 auto const geometry = this->geometry();
164 geometry.transform(cr);
165 draw_pixbuf(cr, icon,
166 (geometry.width() - icon->get_width()) / 2,
167 -icon->get_height());
168 cr->pop_group_to_source();
169 cr->paint();
170 cr->restore();
171 } // void Icongroup::draw_announcement(Cairo::RefPtr<::Cairo::Context> cr)
172
173 /** draws the swines/hyperswines icon
174 **
175 ** @param cr cairo context
176 **/
177 void
draw_swines(Cairo::RefPtr<::Cairo::Context> cr)178 Icongroup::draw_swines(Cairo::RefPtr<::Cairo::Context> cr)
179 {
180 if (!(::game_status & GameStatus::game))
181 return ;
182
183 Glib::RefPtr<Gdk::Pixbuf> icon;
184
185 auto const& swines = this->last_status_.swines;
186 auto const& hyperswines = this->last_status_.hyperswines;
187 auto const& trumpcolor = this->last_status_.trumpcolor;
188 if (swines && hyperswines) {
189 icon = this->ui->icons->swines_hyperswines(trumpcolor);
190 } else if (swines) {
191 icon = this->ui->icons->swines(trumpcolor);
192 } else if (hyperswines) {
193 icon = this->ui->icons->hyperswines(trumpcolor);
194 } else {
195 return ;
196 }
197
198 auto const geometry = this->geometry();
199 cr->save();
200 cr->push_group();
201 geometry.transform(cr);
202 draw_pixbuf(cr, icon,
203 ( geometry.flip_x()
204 ? 0
205 : geometry.width() - icon->get_width()),
206 -icon->get_height());
207
208 cr->pop_group_to_source();
209 cr->paint();
210 cr->restore();
211 } // void Icongroup::draw_swines(Cairo::RefPtr<::Cairo::Context> cr)
212
213 /** @return the geometry of the icongroup
214 **/
215 Icongroup::Geometry
geometry() const216 Icongroup::geometry() const
217 {
218 Geometry geometry;
219 auto const hand_geometry = this->hand().geometry();
220 geometry.height = [=](){
221 return this->ui->icons->max_height();
222 };
223 switch (this->position()) {
224 case Position::south:
225 geometry.rotation = Rotation::up;
226 geometry.width = [=](){
227 return hand_geometry.width() * 3 / 4;
228 };
229 geometry.pos_x = [=](){
230 return (hand_geometry.pos_x()
231 + hand_geometry.width() / 4);
232 };
233 geometry.pos_y = [=](){
234 return (hand_geometry.pos_y()
235 - hand_geometry.height()
236 - hand_geometry.margin_y());
237 };
238 break;
239
240 case Position::north:
241 geometry.rotation = Rotation::down;
242 geometry.flip_x = [](){
243 return true;
244 };
245 geometry.width = [=](){
246 return hand_geometry.width() * 3 / 4;
247 };
248 geometry.pos_x = [=](){
249 return hand_geometry.pos_x();
250 };
251 geometry.pos_y = [=](){
252 return (hand_geometry.pos_y()
253 + hand_geometry.height()
254 + hand_geometry.margin_y());
255 };
256 break;
257
258 case Position::west:
259 geometry.rotation = Rotation::right;
260 geometry.pos_x = [=](){
261 return (hand_geometry.pos_x()
262 + hand_geometry.height()
263 + hand_geometry.margin_y());
264 };
265 geometry.pos_y = [=](){
266 return (hand_geometry.pos_y());
267 };
268 geometry.width = [=](){
269 return hand_geometry.width();
270 };
271 break;
272
273 case Position::east:
274 geometry.rotation = Rotation::left;
275 geometry.pos_x = [=](){
276 return (hand_geometry.pos_x()
277 - hand_geometry.height()
278 - hand_geometry.margin_y());
279 };
280 geometry.pos_y = [=](){
281 return (hand_geometry.pos_y());
282 };
283 geometry.width = [=](){
284 return hand_geometry.width();
285 };
286 break;
287
288 case Position::center:
289 DEBUG_ASSERTION(false,
290 "Icongroup::geometry()\n"
291 " wrong position 'center' "
292 << "(" << this->position() << ")");
293 } // switch (this->position())
294
295 switch (this->table().layout()) {
296 case Table::Layout::standard:
297 break;
298 case Table::Layout::widescreen:
299 switch (this->position()) {
300 case Position::north:
301 geometry.width = [=](){
302 return hand_geometry.width();
303 };
304 break;
305 case Position::east:
306 geometry.width = [=](){
307 return (hand_geometry.width()
308 + hand_geometry.margin_x()
309 + this->name().geometry().height());
310 };
311 break;
312 default:
313 break;
314 } // switch (this->position())
315 } // switch (this->table().layout())
316 return geometry;
317 } // Icongroup::Geometry Icongroup::geometry() const
318
319 /** @return the geometry of the icongroup
320 **/
321 Icongroup::Outline
outline() const322 Icongroup::outline() const
323 {
324 return this->geometry().outline();
325 }
326
327 /** @return whether the mouse is over the next announcement
328 **/
329 bool
mouse_over_next_announcement() const330 Icongroup::mouse_over_next_announcement() const
331 {
332 if (!::preferences(::Preferences::Type::announce_in_table))
333 return false;
334
335 if (!::is_real(this->player().team()))
336 return false;
337
338 Announcement const announcement
339 = this->player().next_announcement();
340
341 if (announcement == Announcement::noannouncement)
342 return false;
343
344 int x, y;
345 this->table().get_pointer(x, y);
346
347 Glib::RefPtr<Gdk::Pixbuf> icon
348 = this->ui->icons->icon(announcement);
349 auto const geometry = this->geometry();
350 geometry.retransform(x, y);
351 return ( (x >= (geometry.width() - icon->get_width()) / 2)
352 && (x < (geometry.width() + icon->get_width()) / 2)
353 && (y < 0)
354 && (y >= -icon->get_height()) );
355 } // bool Icongroup::mouse_over_next_announcement() const
356
357 /** @return the current status
358 **/
359 Icongroup::Status
current_status() const360 Icongroup::current_status() const
361 {
362 #ifdef TEST_LAYOUT
363 return {Icons::Type::thrown_nines_and_kings, false,
364 Announcement::no120, false,
365 Card::Color::club, true, true};
366 #endif
367 auto const& game = this->game();
368 Team const team = game.teaminfo().get_human_teaminfo(this->player());
369 auto const* const soloplayer
370 = (game.players().exists_soloplayer()
371 ? &game.players().soloplayer()
372 : ((game.type() == GameType::normal)
373 ? nullptr
374 : &game.players().current_player()));
375
376 auto team_icon = Icons::Type::none;
377 auto team_guessed = false;
378 if (::game_status == GameStatus::game_reservation) {
379 if (this->player().type() == Player::Type::human) {
380 ::Reservation const& reservation = this->player().reservation();
381 switch (reservation.game_type) {
382 case GameType::normal:
383 team_icon = Icons::type(this->player().hand().contains(Card::club_queen)
384 ? Team::re : Team::contra);
385 break;
386 case GameType::poverty:
387 {
388 switch (this->player().hand().count_trumps()) {
389 case 0:
390 team_icon = Icons::Type::poverty_trumps_0;
391 break;
392 case 1:
393 team_icon = Icons::Type::poverty_trumps_1;
394 break;
395 case 2:
396 team_icon = Icons::Type::poverty_trumps_2;
397 break;
398 case 3:
399 case 4:
400 case 5:
401 team_icon = Icons::Type::poverty_trumps_3;
402 break;
403 case UINT_MAX:
404 team_icon = Icons::Type::poverty;
405 break;
406 default:
407 DEBUG_ASSERTION(false,
408 "IconGroup::draw_team():\n"
409 " gametype: poverty\n"
410 " number of poverty cards for player " << this->player().no() << ' ' << this->player().name() << " invalid: "
411 << this->player().hand().count_trumps() << '\n'
412 << "reservation:\n"
413 << this->player().reservation() << '\n'
414 << "game status: " << ::game_status);
415 team_icon = Icons::Type::poverty;
416 break;
417 } // switch (this->player().hand().count_trumps())
418 }
419 break;
420 case GameType::genscher:
421 team_icon = Icons::Type::genscher;
422 break;
423 case GameType::marriage:
424 team_icon = Icons::type(reservation.marriage_selector);
425 break;
426 case GameType::marriage_solo:
427 case GameType::marriage_silent:
428 case GameType::thrown_nines:
429 case GameType::thrown_kings:
430 case GameType::thrown_nines_and_kings:
431 case GameType::thrown_richness:
432 case GameType::fox_highest_trump:
433 case GameType::redistribute:
434 case GameType::solo_diamond:
435 case GameType::solo_jack:
436 case GameType::solo_queen:
437 case GameType::solo_king:
438 case GameType::solo_queen_jack:
439 case GameType::solo_king_jack:
440 case GameType::solo_king_queen:
441 case GameType::solo_koehler:
442 case GameType::solo_club:
443 case GameType::solo_heart:
444 case GameType::solo_spade:
445 case GameType::solo_meatless:
446 team_icon = Icons::type(reservation.game_type);
447 break;
448 } // switch (this->player().reservation().game_type())
449
450 } // if (this->player().type() == Player::Type::human)
451
452 } else if ( game.is_finished()
453 || ::preferences(::Preferences::Type::show_soloplayer_in_game)) {
454 switch (game.type()) {
455 case GameType::normal:
456 break;
457 case GameType::poverty:
458 if (this->player() == *soloplayer) {
459 team_icon = Icons::type(game.type());
460 } else if ( (::game_status >= GameStatus::game_start)
461 && (this->player().team() == Team::re) ) {
462 team_icon = Icons::Type::poverty_partner;
463 }
464 break;
465 case GameType::genscher:
466 if (this->player() == *soloplayer)
467 team_icon = Icons::Type::genscher;
468 else if (this->player().team() == Team::re)
469 team_icon = Icons::type(Team::re);
470 else
471 team_icon = Icons::type(Team::contra);
472 break;
473 case GameType::marriage:
474 case GameType::marriage_solo:
475 if (this->player() == *soloplayer) {
476 team_icon = Icons::type(game.marriage().selector());
477 } else if (game.marriage().selector() == MarriageSelector::team_set) {
478 if (team == Team::re) {
479 team_icon = Icons::Type::marriage_partner;
480 } else {
481 team_icon = Icons::type(team);
482 }
483 } // if !(this->player == soloplayer)
484 break;
485 case GameType::marriage_silent:
486 if (this->player() == *soloplayer) {
487 team_icon = Icons::type(game.type());
488 } else {
489 team_icon = Icons::Type::none;
490 }
491 break;
492 case GameType::thrown_nines:
493 case GameType::thrown_kings:
494 case GameType::thrown_nines_and_kings:
495 case GameType::thrown_richness:
496 case GameType::fox_highest_trump:
497 case GameType::redistribute:
498 case GameType::solo_diamond:
499 case GameType::solo_jack:
500 case GameType::solo_queen:
501 case GameType::solo_king:
502 case GameType::solo_queen_jack:
503 case GameType::solo_king_jack:
504 case GameType::solo_king_queen:
505 case GameType::solo_koehler:
506 case GameType::solo_club:
507 case GameType::solo_heart:
508 case GameType::solo_spade:
509 case GameType::solo_meatless:
510 if (this->player() == *soloplayer)
511 team_icon = Icons::type(game.type());
512 else
513 team_icon = Icons::Type::none;
514 break;
515 } // switch(game.type())
516 } // if (icon shall be shown for soloplayer)
517
518 // special case: silent marriage
519 if ( !team_icon
520 && (game.players().count_humans() == 1)
521 && (this->player().type() == Player::Type::human)
522 && ( ( (game.type() == GameType::marriage_silent)
523 && (this->player() == game.players().soloplayer()) )
524 || ( (game.type() == GameType::normal)
525 && (this->player().hand().count_all(Card::club_queen)
526 == game.rule()(Rule::Type::number_of_same_cards)))
527 )
528 ) {
529 team_icon = Icons::type(GameType::marriage_silent);
530 }
531
532 if (!team_icon) {
533 if ( game.is_finished()
534 || ::preferences(::Preferences::Type::show_known_teams_in_game)) {
535 team_icon = Icons::type(team);
536 } // if (show teams)
537 } // if (!team_icon)
538
539 if ( !team_icon
540 && ::preferences(::Preferences::Type::show_ai_information_teams)
541 && (game.players().count_humans() == 1)
542 && (game.players().human().teaminfo(this->player()) != Team::unknown)
543 && (game.type() == GameType::normal)
544 ) {
545 team_icon = Icons::type(::maybe_to_team(game.players().human().teaminfo(this->player())));
546 if (!!team_icon
547 && !::is_real(game.players().human().teaminfo(this->player()))) {
548 team_guessed = true;
549 } // if (team is maybe)
550 } // if (team_icon)
551
552
553 bool next_announcement = false;
554 auto announcement = this->player().announcement();
555 if ( ::preferences(::Preferences::Type::announce_in_table)
556 && (this->player().type() == Player::Type::human)
557 && (this->mouse_over_next_announcement()
558 || ( (this->player().announcement()
559 == Announcement::noannouncement)
560 && (this->player().next_announcement()
561 != Announcement::noannouncement)))) {
562 announcement = this->player().next_announcement();
563 if ( !announcement
564 && this->player().game().announcements().is_valid(Announcement::reply, this->player()))
565 announcement = Announcement::reply;
566 next_announcement = true;
567 }
568
569 if (announcement == Announcement::reply)
570 announcement = to_reply(this->game().announcements().last(::opposite(this->player().team())));
571
572 if (this->table().in_game_review()) {
573 announcement = Announcement::noannouncement;
574 for (auto a = this->game().announcements().all(this->player()).rbegin();
575 a != this->game().announcements().all(this->player()).rend();
576 ++a)
577 if (a->trickno < this->table().game_review_trickno()) {
578 announcement = a->announcement;
579 break;
580 }
581 } // if (this->table().in_game_review())
582
583 // Swines
584
585 bool swines = this->player().has_swines();
586 bool hyperswines = this->player().has_hyperswines();
587 if ( (::game_status == GameStatus::game_reservation)
588 || (::game_status == GameStatus::game_start) ) {
589 swines = swines || (this->player().reservation().swines
590 && this->game().swines().swines_announcement_valid(this->player()));
591 hyperswines = hyperswines || (this->player().reservation().hyperswines
592 && this->game().swines().hyperswines_announcement_valid(this->player()));
593 }
594
595 return {team_icon, team_guessed,
596 announcement, next_announcement,
597 this->game().cards().trumpcolor(), swines, hyperswines};
598 } // Icongroup::Status Icongroup::current_status() const
599
600 /** comparison operator
601 **/
602 bool
operator !=(Icongroup::Status const & lhs,Icongroup::Status const & rhs)603 operator!=(Icongroup::Status const& lhs, Icongroup::Status const& rhs)
604 {
605 return ( (lhs.team_icon != rhs.team_icon)
606 || (lhs.team_guessed != rhs.team_guessed)
607 || (lhs.announcement != rhs.announcement)
608 || (lhs.next_announcement != rhs.next_announcement)
609 || (lhs.trumpcolor != rhs.trumpcolor)
610 || (lhs.swines != rhs.swines)
611 || (lhs.hyperswines != rhs.hyperswines) );
612 } // bool operator!=(Icongroup::Status lhs, Icongroup::Status rhs)
613
614 /** @return the geometry of the icongroup
615 **/
616 Icongroup::Outline
outline() const617 Icongroup::Geometry::outline() const
618 {
619 return this->transform({0, -this->height(), this->width(), this->height()});
620 } // Outline Icongroup::Geometry::outline() const
621
622 } // namespace UI_GTKMM_NS
623
624 #endif // #ifdef USE_UI_GTKMM
625