25#include <wx/clipbrd.h>
26#include <wx/dcclient.h>
29#include <wx/textctrl.h>
38#include <wx/msw/winundef.h>
47static bool IsFilterMatch(
const struct Logline& ll,
const std::string& s) {
48 if (ll.navmsg->source->iface.find(s) != std::string::npos)
return true;
49 return ll.message.find(s) != std::string::npos;
52static void UpdateColor(wxColour& colour,
unsigned rgb) {
53 if (rgb == kUndefinedColor) {
54 colour = wxNullColour;
60static std::string Timestamp(
const NavmsgTimePoint& when) {
62 using namespace std::chrono;
64 auto now = when.time_since_epoch();
65 auto _hours = duration_cast<hours>(now);
67 auto _minutes = duration_cast<minutes>(now);
69 auto _seconds = duration_cast<seconds>(now);
71 auto ms = duration_cast<milliseconds>(now);
72#ifdef CSTDIO_TTYSCROLL_TIMESTAMP
75 snprintf(buf,
sizeof(buf),
"%02ld:%02ld:%02ld.%03ld", _hours.count() % 24,
76 _minutes.count(), _seconds.count(), ms.count());
80 ss << setw(2) << setfill(
'0') << _hours.count() % 24 <<
":" << setw(2)
81 << _minutes.count() <<
":" << setw(2) << _seconds.count() <<
"." << setw(3)
89 static const wxColor kDarkGreen(30, 72, 56);
90 if (ns.status != NavmsgStatus::State::kOk)
91 color = wxColour(
"RED");
92 else if (ns.accepted == NavmsgStatus::Accepted::kFilteredNoOutput)
93 color = wxColour(
"CORAL");
94 else if (ns.accepted == NavmsgStatus::Accepted::kFilteredDropped)
95 color = wxColour(
"MAROON");
96 else if (ns.direction == NavmsgStatus::Direction::kOutput)
97 color = wxColour(
"BLUE");
98 else if (ns.direction == NavmsgStatus::Direction::kHandled)
101 color = wxColour(
"ORANGE");
107 if (ns.status != NavmsgStatus::State::kOk)
108 UpdateColor(color, g_dm_not_ok);
109 else if (ns.accepted == NavmsgStatus::Accepted::kFilteredNoOutput)
110 UpdateColor(color, g_dm_filtered);
111 else if (ns.accepted == NavmsgStatus::Accepted::kFilteredDropped)
112 UpdateColor(color, g_dm_dropped);
113 else if (ns.direction == NavmsgStatus::Direction::kOutput)
114 UpdateColor(color, g_dm_output);
115 else if (ns.direction == NavmsgStatus::Direction::kInput)
116 UpdateColor(color, g_dm_input);
118 UpdateColor(color, g_dm_ok);
119 return color.IsOk() ? color : defaults(ns);
125 if (!ll.message.empty()) ws << Timestamp(ll.navmsg->created_at) <<
" ";
126 if (ll.message.empty())
128 else if (ll.state.direction == NavmsgStatus::Direction::kOutput)
129 ws <<
" " << kUtfRightArrow <<
" ";
130 else if (ll.state.direction == NavmsgStatus::Direction::kInput)
131 ws <<
" " << kUtfLeftwardsArrowToBar <<
" ";
132 else if (ll.state.direction == NavmsgStatus::Direction::kInternal)
133 ws <<
" " << kUtfLeftRightArrow <<
" ";
135 ws <<
" " << kUtfLeftArrow <<
" ";
137 if (ll.message.empty())
139 else if (ll.state.status != NavmsgStatus::State::kOk)
140 ws << kUtfMultiplicationX;
141 else if (ll.state.accepted == NavmsgStatus::Accepted::kFilteredNoOutput)
142 ws << kUtfFallingDiagonal;
143 else if (ll.state.accepted == NavmsgStatus::Accepted::kFilteredDropped)
144 ws << kUtfCircledDivisionSlash;
148 std::stringstream error_msg;
149 if (ll.state.status != NavmsgStatus::State::kOk) {
151 << (ll.error_msg.empty() ?
"Unknown errror" : ll.error_msg);
153 std::string iface(ll.navmsg ? ll.navmsg->source->iface :
"");
154 if (iface.size() > 30)
155 iface = std::string(
"...") + iface.substr(iface.size() - 27);
158 dc.DrawText(ws, 0, y);
160 ws << ll.message << error_msg.str();
161 dc.DrawText(ws, data_pos, y);
163 std::max(m_text_width, GetTextExtent(ws).GetWidth() + data_pos);
167 : wxScrolledWindow(parent), m_n_lines(n_lines), m_is_paused(false) {
168 SetName(
"TtyScroll");
170 dc.GetTextExtent(
"Line Height", NULL, &m_line_height);
171 SetScrollRate(m_line_height, m_line_height);
172 for (
unsigned i = 0; i < m_n_lines; i++) m_lines.push_back(
Logline());
173 SetColors(std::make_unique<UserColorsByState>());
174 Bind(wxEVT_SIZE, [&](wxSizeEvent& ev) { OnSize(ev); });
177void TtyScroll::OnSize(wxSizeEvent& ev) {
178 m_n_lines = ev.GetSize().y / GetCharHeight();
179 while (m_lines.size() < m_n_lines) m_lines.push_back(
Logline());
185 for (
size_t i = 0; i < m_n_lines; ++i) m_lines.push_back(
Logline());
190 if (m_is_paused || !m_filter.
Pass(ll.state, ll.navmsg))
return;
191 if (!m_quick_filter.empty() && !IsFilterMatch(ll, m_quick_filter))
return;
192 while (m_lines.size() > m_n_lines - 1) m_lines.pop_front();
193 m_lines.push_back(ll);
198 m_color_by_state = std::move(color_by_state);
201void TtyScroll::OnDraw(wxDC& dc) {
203 wxRect rect_update = GetUpdateRegion().GetBox();
204 CalcUnscrolledPosition(rect_update.x, rect_update.y, &rect_update.x,
206 size_t line_from = rect_update.y / m_line_height;
207 size_t line_to = rect_update.GetBottom() / m_line_height;
208 if (line_to > m_n_lines - 1) line_to = m_n_lines - 1;
210 wxCoord y = line_from * m_line_height;
212 for (
size_t line = line_from; line <= line_to; line++) {
214 dc.SetTextForeground((*m_color_by_state)(m_lines[line].state));
215 DrawLine(dc, m_lines[line], 40 * GetCharWidth(), y);
218 SetVirtualSize(m_text_width, (m_n_lines + 1) * m_line_height);
222 std::stringstream ss;
223 for (
auto& line : m_lines) ss << line.message <<
"\n";
224 if (wxTheClipboard->Open()) {
225 wxTheClipboard->SetData(
new wxTextDataObject(ss.str()));
226 wxTheClipboard->Close();
bool Pass(NavmsgStatus status, const std::shared_ptr< const NavMsg > &msg)
Return true if message is not matched by filter.
Representation of message status as determined by the multiplexer.
Global variables stored in configuration file.
General observable implementation with several specializations.