Freeciv-3.3
Loading...
Searching...
No Matches
chatline.cpp
Go to the documentation of this file.
1/***********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12***********************************************************************/
13
14#ifdef HAVE_CONFIG_H
15#include <fc_config.h>
16#endif
17
18// Qt
19#include <QApplication>
20#include <QCheckBox>
21#include <QCompleter>
22#include <QGridLayout>
23#include <QKeyEvent>
24#include <QPainter>
25#include <QScrollBar>
26#include <QStyleFactory>
27#include <QTextBlock>
28#include <QTextLayout>
29#include <QTextBrowser>
30
31// common
32#include "chat.h"
33
34// client
35#include "audio.h"
36#include "climap.h"
37#include "climisc.h" // For write_chatline_content()
38#include "connectdlg_common.h"
39#include "control.h"
40#include "game.h"
41
42// gui-qt
43#include "chatline.h"
44#include "colors.h"
45#include "fc_client.h"
46#include "gui_main.h"
47#include "qtg_cxxside.h"
48
49static bool is_plain_public_message(QString s);
50
54
55/***********************************************************************/
59{
61
63 if (pconn->playing) {
64 word_list << pconn->playing->name;
65 word_list << pconn->playing->username;
66 } else {
67 word_list << pconn->username;
68 }
70
71 players_iterate(pplayer) {
72 str = pplayer->name;
73 if (!word_list.contains(str)) {
74 word_list << str;
75 }
77
79}
80
81/***********************************************************************/
85 position(HISTORY_END)
86{}
87
88/***********************************************************************/
93 const struct text_tag_list *)
94{}
95
96/***********************************************************************/
102
103/***********************************************************************/
110{
111 int index;
112 QString splayer, s;
113
114 /* FIXME
115 * Key == PICK: used for picking nation, it was put here cause those
116 * Qt slots are a bit limited ...I'm unable to pass custom player pointer
117 * or idk how to do that
118 */
119 s = message;
120 index = message.indexOf("PICK:");
121
122 if (index != -1) {
123 s = s.remove("PICK:");
124 // Now should be playername left in string
125 players_iterate(pplayer) {
126 splayer = QString(pplayer->name);
127
128 if (!splayer.compare(s)) {
129 popup_races_dialog(pplayer);
130 }
132 return;
133 }
134
135 history << message;
137
138 // If client send commands to take ai, set /away to disable AI
139 if (message.startsWith("/take ")) {
140 s = s.remove("/take ");
141 players_iterate(pplayer) {
142 splayer = QString(pplayer->name);
143 splayer = "\"" + splayer + "\"";
144
145 if (!splayer.compare(s)) {
146 if (is_ai(pplayer)) {
147 send_chat(message.toUtf8());
148 send_chat("/away");
149 return;
150 }
151 }
153 }
154
155 // Option to send to allies by default
156 if (!message.isEmpty()) {
160 + " " + message).toUtf8());
161 } else {
162 send_chat(message.toUtf8());
163 }
164 }
165 // Empty messages aren't sent
166 // FIXME Inconsistent behavior: "." will send an empty message to allies
167}
168
169/***********************************************************************/
174{
175 if (!history.empty() && position == HISTORY_END) {
176 position = history.size() - 1;
177 } else if (position > 0) {
178 position--;
179 }
180 return history.empty() ? "" : history.at(position);
181}
182
183/***********************************************************************/
188{
189 if (position == HISTORY_END) {
190 return "";
191 }
192 position++;
193 if (position >= history.size()) {
195 return "";
196 } else {
197 return history.at(position);
198 }
199}
200
201/***********************************************************************/
208
209/***********************************************************************/
213 QLineEdit(parent)
214{
215 connect(this, &QLineEdit::returnPressed, this, &chat_input::send);
218}
219
220/***********************************************************************/
224{
225 send_chat_message(text());
226 clear();
227}
228
229/***********************************************************************/
233{
235
236 if (cmplt != nullptr) {
237 delete cmplt;
238 }
239
241 cmplt->setCaseSensitivity(Qt::CaseInsensitive);
242 cmplt->setCompletionMode(QCompleter::InlineCompletion);
244}
245
246/***********************************************************************/
250{
251 if (event->type() == QEvent::KeyPress) {
252 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
253 switch (keyEvent->key()) {
254 case Qt::Key_Up:
256 return true;
257 case Qt::Key_Down:
259 return true;
260 }
261 }
262 return QLineEdit::event(event);
263}
264
265/***********************************************************************/
269{
271
273 cb = new QCheckBox("");
274 cb->setToolTip(_("Allies only"));
276 gl = new QGridLayout;
277 chat_line = new chat_input;
280 remove_links = new QPushButton("");
281 remove_links->setIcon(style()->standardPixmap(QStyle::SP_DialogCancelButton));
282 remove_links->setToolTip(_("Clear links"));
283 gl->setVerticalSpacing(0);
284 gl->addWidget(chat_output,0 , 0, 1 ,3);
285 gl->addWidget(chat_line,1, 0);
286 gl->addWidget(cb,1,1);
287 gl->addWidget(remove_links,1,2);
288 gl->setContentsMargins(0, 0, 6, 0);
289 setLayout(gl);
290 chat_output->setReadOnly(true);
291 chat_line->installEventFilter(this);
292 chat_output->setVisible(true);
293 chat_output->setAcceptRichText(true);
294 chat_output->setOpenLinks(false);
295 chat_output->setReadOnly(true);
296 connect(chat_output, &QTextBrowser::anchorClicked,
298 connect(chat_output, &QTextBrowser::anchorClicked,
301 this, &chatwdg::toggle_size);
302 connect(remove_links, &QAbstractButton::clicked, this, &chatwdg::rm_links);
303#ifdef FC_QT6X_MODE
304 // Qt-6.7
305 connect(cb, &QCheckBox::checkStateChanged, this, &chatwdg::state_changed);
306#else // FC_QT6X_MODE
307 connect(cb, &QCheckBox::stateChanged, this, &chatwdg::state_changed_depr);
308#endif // FC_QT6X_MODE
309 setMouseTracking(true);
310
312}
313
314/***********************************************************************/
317void chatwdg::state_changed(Qt::CheckState state)
318{
319 if (state != Qt::Unchecked) {
321 } else {
323 }
324}
325
326/***********************************************************************/
331{
332 if (state > 0) {
334 } else {
336 }
337}
338
339/***********************************************************************/
343{
344 if (gui()->infotab->chat_maximized) {
345 gui()->infotab->restore_chat();
346 return;
347 } else {
348 gui()->infotab->maximize_chat();
349 chat_line->setFocus();
350 }
351}
352
353/***********************************************************************/
357{
358 chat_output->verticalScrollBar()->setSliderPosition(
359 chat_output->verticalScrollBar()->maximum());
360}
361
362
363/***********************************************************************/
367{
368 QFont *qf;
370 chat_output->setFont(*qf);
371}
372
373/***********************************************************************/
380
381/***********************************************************************/
385{
386 int n;
388 int id;
389 enum text_link_type type;
390 struct tile *ptile = nullptr;
391
392 sl = link.toString().split(",");
393 n = sl.at(0).toInt();
394 id = sl.at(1).toInt();
395
396 type = static_cast<text_link_type>(n);
397
398 switch (type) {
399 case TLT_CITY: {
400 struct city *pcity = game_city_by_number(id);
401
402 if (pcity) {
403 ptile = client_city_tile(pcity);
404 } else {
405 output_window_append(ftc_client, _("This city isn't known!"));
406 }
407 }
408 break;
409 case TLT_TILE:
410 ptile = index_to_tile(&(wld.map), id);
411
412 if (!ptile) {
414 _("This tile doesn't exist in this game!"));
415 }
416 break;
417 case TLT_UNIT: {
418 struct unit *punit = game_unit_by_number(id);
419
420 if (punit) {
421 ptile = unit_tile(punit);
422 } else {
423 output_window_append(ftc_client, _("This unit isn't known!"));
424 }
425 }
426 break;
427 }
428 if (ptile != nullptr) {
431 }
432}
433
434/***********************************************************************/
438 const struct text_tag_list *tags)
439{
440 QColor col = chat_output->palette().color(QPalette::Text);
441
442 append(apply_tags(message, tags, col));
443}
444
445/***********************************************************************/
449{
450 QTextCursor cursor;
451
452 chat_output->append(str);
453 chat_output->verticalScrollBar()->setSliderPosition(
454 chat_output->verticalScrollBar()->maximum());
455}
456
457/***********************************************************************/
461{
462 painter->setBrush(QColor(0, 0, 0, 35));
463 painter->drawRect(0, 0, width(), height());
464}
465
466/***********************************************************************/
470{
472
473 painter.begin(this);
475 painter.end();
476}
477
478/***********************************************************************/
481bool chatwdg::eventFilter(QObject *obj, QEvent *event)
482{
483 if (obj == chat_line) {
484 if (event->type() == QEvent::KeyPress) {
485 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
486
487 if (keyEvent->key() == Qt::Key_Escape) {
488 gui()->infotab->restore_chat();
489 gui()->mapview_wdg->setFocus();
490 return true;
491 }
492 }
493 if (event->type() == QEvent::ShortcutOverride) {
494 event->setAccepted(true);
495 }
496 }
497
498 return QObject::eventFilter(obj, event);
499}
500
501/***********************************************************************/
505{
506 if (is_server_running()) {
507 cb->hide();
508 remove_links->hide();
509 } else {
510 cb->show();
511 remove_links->show();
512 }
513}
514
515/***********************************************************************/
520{
521 int line_count = 0;
522 int line_height;
523 int size;
525
526 qtb = chat_output->document()->firstBlock();
527 /* Count all lines in all text blocks layouts
528 * document()->lineCount returns number of lines without wordwrap */
529
530 while (qtb.isValid()) {
531 line_count = line_count + qtb.layout()->lineCount();
532 qtb = qtb.next();
533 }
534
535 if (line_count == 0) {
536 return 0;
537 }
538
539 line_height = (chat_output->document()->size().height()
540 - 2 * chat_output->document()->documentMargin())
541 / line_count;
542
544 + chat_line->size().height() + chat_output->document()->documentMargin();
545 size = qMax(0, size);
546
547 return size;
548}
549
550/***********************************************************************/
553void chatwdg::make_link(struct tile *ptile)
554{
555 struct unit *punit;
556 char buf[MAX_LEN_MSG];
557
558 punit = find_visible_unit(ptile);
559 if (tile_city(ptile)) {
561 } else if (punit) {
563 } else {
564 sz_strlcpy(buf, tile_link(ptile));
565 }
566 chat_line->insert(QString(buf));
567 chat_line->setFocus();
568}
569
570/***********************************************************************/
575{
576 int start, stop, last_i;
581 QColor qc;
584
585 if (tags == nullptr) {
586 return str;
587 }
588 str_bytes = str.toUtf8();
589 qba = str_bytes.data();
590
593 stop = qba.length();
594 } else {
596 }
597
599 start = 0;
600 } else {
602 }
603 switch (text_tag_type(ptag)) {
604 case TTT_BOLD:
605 mm.insert(stop, "</b>");
606 mm.insert(start, "<b>");
607 break;
608 case TTT_ITALIC:
609 mm.insert(stop, "</i>");
610 mm.insert(start, "<i>");
611 break;
612 case TTT_STRIKE:
613 mm.insert(stop, "</s>");
614 mm.insert(start, "<s>");
615 break;
616 case TTT_UNDERLINE:
617 mm.insert(stop, "</u>");
618 mm.insert(start, "<u>");
619 break;
620 case TTT_COLOR:
623 if (color == "#00008B") {
624 color = bg_color.name();
625 } else {
626#ifdef FC_QT6X_MODE
627 // Qt-6.4
628 qc = QColor::fromString(color);
629#else // FC_QT6X_MODE
630 qc.setNamedColor(color);
631#endif // FC_QT6X_MODE
632 qc = qc.lighter(200);
633 color = qc.name();
634 }
635 str_col = QString("<span style=color:%1>").arg(color);
636 mm.insert(stop, "</span>");
637 mm.insert(start, str_col);
638 }
641 if (
643 // Qt-6.4
644 QColor::isValidColorName(color)
645#else // FC_QT6X_MODE
646 QColor::isValidColor(color)
647#endif // FC_QT6X_MODE
648 ) {
649 str_col = QString("<span style= background-color:%1;>").arg(color);
650 mm.insert(stop, "</span>");
651 mm.insert(start, str_col);
652 }
653 }
654 break;
655 case TTT_LINK: {
656 struct color *pcolor = nullptr;
657
658 switch (text_tag_link_type(ptag)) {
659 case TLT_CITY:
661 break;
662 case TLT_TILE:
664 break;
665 case TLT_UNIT:
667 break;
668 }
669
670 if (pcolor == nullptr) {
671 break; // Not a valid link type case.
672 }
673 color = pcolor->qcolor.name(QColor::HexRgb);
674 str_col = QString("<font color=\"%1\">").arg(color);
675 mm.insert(stop, "</a></font>");
676
677 color = QString(str_col + "<a href=%1,%2>").
678 arg(QString::number(text_tag_link_type(ptag)),
679 QString::number(text_tag_link_id(ptag)));
680 mm.insert(start, color);
681 }
682 }
684
685 // Insert html starting from last items
686 last_i = str.length();
687 QMultiMap<int, QString>::const_iterator i = mm.constEnd();
688 QMultiMap<int, QString>::const_iterator j = mm.constEnd();
689
690 while (i != mm.constBegin()) {
691 i--;
692 if (i.key() < last_i) {
693 final_string = final_string.prepend(QString(qba.mid(i.key(),
694 last_i - i.key()))
695 .toHtmlEscaped());
696 }
697 last_i = i.key();
698 j = i;
699 if (i != mm.constBegin()) {
700 j--;
701 }
702 if (j.key() == i.key() && i != j) {
703 final_string = final_string.prepend(j.value());
704 final_string = final_string.prepend(i.value());
705 i--;
706 } else {
707 final_string = final_string.prepend(i.value());
708 }
709 }
710
711 if (last_i == str.length()) {
712 return str;
713 }
714
715 return final_string;
716}
717
718/***********************************************************************/
723{
724 QString s1, str;
725 int i;
726
727 str = s.trimmed();
728 if (str.at(0) == SERVER_COMMAND_PREFIX
729 || str.at(0) == CHAT_ALLIES_PREFIX
730 || str.at(0) == CHAT_DIRECT_PREFIX) {
731 return false;
732 }
733
734 // Search for private message
735 if (!str.contains(CHAT_DIRECT_PREFIX)) {
736 return true;
737 }
738 i = str.indexOf(CHAT_DIRECT_PREFIX);
739 str = str.left(i);
740
741 // Compare all players and connections looking for match
743 s1 = pconn->username;
744 if (s1.length() < i) {
745 continue;
746 }
747 if (!QString::compare(s1.left(i), str, Qt::CaseInsensitive)) {
748 return false;
749 }
751 players_iterate(pplayer) {
752 s1 = pplayer->name;
753 if (s1.length() < i) {
754 continue;
755 }
756 if (!QString::compare(s1.left(i), str, Qt::CaseInsensitive)) {
757 return false;
758 }
760
761 return true;
762}
763
764/***********************************************************************/
769 const struct text_tag_list *tags,
770 int conn_id)
771{
772 QString str;
775
776 str = QString::fromUtf8(astring);
777 gui()->set_status_bar(str);
778
780
781 // Format wakeup string if needed
782 if (wakeup.contains("%1")) {
784 }
785
786 qapp = current_app();
787
788 if (str.contains(client.conn.username)) {
789 qapp->alert(gui()->central_wdg);
790 }
791
792 // Play sound if we encountered wakeup string
793 if (str.contains(wakeup) && client_state() < C_S_RUNNING
794 && !wakeup.isEmpty()) {
795 qapp->alert(gui()->central_wdg);
796 audio_play_sound("e_player_wake", nullptr, nullptr);
797 }
798
801 str, tags);
802}
803
804/***********************************************************************/
809{
810 // PORTME
811 write_chatline_content(nullptr);
812}
813
814/***********************************************************************/
818{
819 // PORTME
820#if 0
821 set_output_window_text(_("Cleared output window."));
822#endif
823}
824
825/***********************************************************************/
829 QEvent(QEvent::User),
830 message(msg)
831{}
832
833/***********************************************************************/
837{
838 current_app()->postEvent(gui(), new version_message_event(vertext));
839}
#define str
Definition astring.c:76
#define n
Definition astring.c:77
void audio_play_sound(const char *const tag, const char *const alt_tag, const char *const alt_tag2)
Definition audio.c:528
static QFont * get_font(enum client_font font)
Definition canvas.cpp:379
struct canvas int int struct sprite int int int int height
Definition canvas_g.h:44
struct canvas int int int int struct sprite *sprite struct canvas struct color * pcolor
Definition canvas_g.h:56
struct canvas int int struct sprite int int int width
Definition canvas_g.h:44
#define CHAT_DIRECT_PREFIX
Definition chat.h:31
#define CHAT_ALLIES_PREFIX
Definition chat.h:30
#define SERVER_COMMAND_PREFIX
Definition chat.h:28
void log_output_window(void)
Definition chatline.cpp:808
void clear_output_window(void)
Definition chatline.cpp:817
QString apply_tags(QString str, const struct text_tag_list *tags, QColor bg_color)
Definition chatline.cpp:573
void qtg_version_message(const char *vertext)
Definition chatline.cpp:836
void qtg_real_output_window_append(const char *astring, const struct text_tag_list *tags, int conn_id)
Definition chatline.cpp:768
static bool is_plain_public_message(QString s)
Definition chatline.cpp:722
int send_chat(const char *message)
void output_window_append(const struct ft_color color, const char *featured_text)
void write_chatline_content(const char *txt)
bool event(QEvent *event)
Definition chatline.cpp:249
virtual void chat_word_list_changed(const QStringList &cmplt_word_list)
Definition chatline.cpp:232
void send()
Definition chatline.cpp:223
chat_input(QWidget *parent=nullptr)
Definition chatline.cpp:212
QStringList current_word_list()
Definition chatline.h:75
static const int HISTORY_END
Definition chatline.h:58
QString forward_in_history()
Definition chatline.cpp:187
void send_chat_message(const QString &message)
Definition chatline.cpp:109
void reset_history_position()
Definition chatline.cpp:204
QString back_in_history()
Definition chatline.cpp:173
static QStringList history
Definition chatline.h:49
virtual void chat_word_list_changed(const QStringList &cmplt_word_list)
Definition chatline.cpp:100
static void update_word_list()
Definition chatline.cpp:58
static QStringList word_list
Definition chatline.h:54
virtual void chat_message_received(const QString &, const struct text_tag_list *)
Definition chatline.cpp:92
bool eventFilter(QObject *obj, QEvent *event)
Definition chatline.cpp:481
chat_input * chat_line
Definition chatline.h:121
chatwdg(QWidget *parent)
Definition chatline.cpp:268
void paintEvent(QPaintEvent *event)
Definition chatline.cpp:469
void update_font()
Definition chatline.cpp:366
void anchor_clicked(const QUrl &link)
Definition chatline.cpp:384
int default_size(int lines)
Definition chatline.cpp:519
text_browser_dblclck * chat_output
Definition chatline.h:141
void make_link(struct tile *ptile)
Definition chatline.cpp:553
void chat_message_received(const QString &message, const struct text_tag_list *tags)
Definition chatline.cpp:437
void scroll_to_bottom()
Definition chatline.cpp:356
void state_changed(Qt::CheckState state)
Definition chatline.cpp:317
void paint(QPainter *painter, QPaintEvent *event)
Definition chatline.cpp:460
void update_widgets()
Definition chatline.cpp:504
void state_changed_depr(int state)
Definition chatline.cpp:330
QCheckBox * cb
Definition chatline.h:143
void rm_links()
Definition chatline.cpp:376
QPushButton * remove_links
Definition chatline.h:142
void append(const QString &str)
Definition chatline.cpp:448
void toggle_size()
Definition chatline.cpp:342
static fc_font * instance()
Definition fonts.cpp:41
QFont * get_font(QString name)
Definition fonts.cpp:63
static void invoke(_member_fct_ function)
Definition listener.h:175
version_message_event(const QString &msg)
Definition chatline.cpp:828
struct civclient client
enum client_states client_state(void)
@ C_S_RUNNING
Definition client_main.h:47
struct tile * client_city_tile(const struct city *pcity)
Definition climap.c:87
struct color * get_color(const struct tileset *t, enum color_std stdcolor)
char * incite_cost
Definition comments.c:76
#define MAX_LEN_MSG
Definition conn_types.h:37
bool is_server_running(void)
#define conn_list_iterate(connlist, pconn)
Definition connection.h:108
#define conn_list_iterate_end
Definition connection.h:110
struct unit * find_visible_unit(struct tile *ptile)
Definition control.c:822
struct unit struct city struct unit struct tile struct extra_type const struct act_prob *act_probs int actor_unit_id struct unit struct unit * punit
Definition dialogs_g.h:74
struct unit struct city struct unit struct tile struct extra_type const struct act_prob *act_probs int actor_unit_id struct unit struct unit int const struct action *paction struct unit struct city * pcity
Definition dialogs_g.h:78
int int id
Definition editgui_g.h:28
enum event_type event
Definition events.c:81
#define _(String)
Definition fcintl.h:67
enum text_link_type text_tag_link_type(const struct text_tag *ptag)
const char * tile_link(const struct tile *ptile)
ft_offset_t text_tag_stop_offset(const struct text_tag *ptag)
const char * text_tag_color_foreground(const struct text_tag *ptag)
const struct ft_color ftc_client
const char * city_link(const struct city *pcity)
int text_tag_link_id(const struct text_tag *ptag)
ft_offset_t text_tag_start_offset(const struct text_tag *ptag)
const char * unit_link(const struct unit *punit)
const char * text_tag_color_background(const struct text_tag *ptag)
#define text_tag_list_iterate_end
#define text_tag_list_iterate(tags, ptag)
#define FT_OFFSET_UNSET
text_link_type
@ TLT_TILE
@ TLT_UNIT
@ TLT_CITY
text_tag_type
@ TTT_LINK
@ TTT_BOLD
@ TTT_ITALIC
@ TTT_STRIKE
@ TTT_COLOR
@ TTT_UNDERLINE
struct civ_game game
Definition game.c:61
struct world wld
Definition game.c:62
struct unit * game_unit_by_number(int id)
Definition game.c:115
struct city * game_city_by_number(int id)
Definition game.c:106
static bool is_plain_public_message(const char *s)
Definition chatline.c:98
void popup_races_dialog(struct player *pplayer)
Definition dialogs.c:1215
GType type
Definition repodlgs.c:1313
QString apply_tags(QString str, const struct text_tag_list *tags, QColor bg_color)
Definition chatline.cpp:573
#define set_output_window_text(_pstr_)
Definition chatline.h:31
static QApplication * qapp
Definition gui_main.cpp:69
QApplication * current_app()
Definition gui_main.cpp:227
#define FC_CPP_DECLARE_LISTENER(_type_)
Definition listener.h:134
struct tile * index_to_tile(const struct civ_map *imap, int mindex)
Definition map.c:471
void link_mark_restore(enum text_link_type type, int id)
void center_tile_mapcanvas(const struct tile *ptile)
void link_marks_clear_all(void)
static mpgui * gui
Definition mpgui_qt.cpp:52
const char *const chatline
Definition fonts.h:31
struct client_options gui_options
Definition options.c:71
char * lines
Definition packhand.c:131
#define players_iterate_end
Definition player.h:542
#define players_iterate(_pplayer)
Definition player.h:537
#define is_ai(plr)
Definition player.h:232
size_t size
Definition specvec.h:72
Definition city.h:317
struct conn_list * est_connections
Definition game.h:97
struct conn_list * all_connections
Definition game.h:96
struct connection conn
Definition client_main.h:96
bool gui_qt_allied_chat_only
Definition options.h:456
char gui_qt_wakeup_text[512]
Definition options.h:468
Definition colors.h:21
char username[MAX_LEN_NAME]
Definition connection.h:164
Definition tile.h:50
Definition unit.h:140
struct civ_map map
#define sz_strlcpy(dest, src)
Definition support.h:195
struct city * tile_city(const struct tile *ptile)
Definition tile.c:83
#define unit_tile(_pu)
Definition unit.h:407