25#include <wx/clipbrd.h>
26#include <wx/dcclient.h>
29#include <wx/textctrl.h>
39#include <wx/msw/winundef.h>
48static bool IsFilterMatch(
const struct Logline& ll,
const std::string& s) {
49 if (ll.navmsg->source->iface.find(s) != std::string::npos)
return true;
50 return ll.message.find(s) != std::string::npos;
53static void UpdateColor(wxColour& colour,
unsigned rgb) {
54 if (rgb == kUndefinedColor) {
55 colour = wxNullColour;
61static std::string Timestamp(
const NavmsgTimePoint& when) {
63 using namespace std::chrono;
65 auto now = when.time_since_epoch();
66 auto _hours = duration_cast<hours>(now);
68 auto _minutes = duration_cast<minutes>(now);
70 auto _seconds = duration_cast<seconds>(now);
72 auto ms = duration_cast<milliseconds>(now);
73#ifdef CSTDIO_TTYSCROLL_TIMESTAMP
76 snprintf(buf,
sizeof(buf),
"%02ld:%02ld:%02ld.%03ld", _hours.count() % 24,
77 _minutes.count(), _seconds.count(), ms.count());
81 ss << setw(2) << setfill(
'0') << _hours.count() % 24 <<
":" << setw(2)
82 << _minutes.count() <<
":" << setw(2) << _seconds.count() <<
"." << setw(3)
90 static const wxColor kDarkGreen(30, 72, 56);
91 if (ns.status != NavmsgStatus::State::kOk)
92 color = wxColour(
"RED");
93 else if (ns.accepted == NavmsgStatus::Accepted::kFilteredNoOutput)
94 color = wxColour(
"CORAL");
95 else if (ns.accepted == NavmsgStatus::Accepted::kFilteredDropped)
96 color = wxColour(
"MAROON");
97 else if (ns.direction == NavmsgStatus::Direction::kOutput)
98 color = wxColour(
"BLUE");
99 else if (ns.direction == NavmsgStatus::Direction::kHandled)
102 color = wxColour(
"ORANGE");
108 if (ns.status != NavmsgStatus::State::kOk)
109 UpdateColor(color, g_dm_not_ok);
110 else if (ns.accepted == NavmsgStatus::Accepted::kFilteredNoOutput)
111 UpdateColor(color, g_dm_filtered);
112 else if (ns.accepted == NavmsgStatus::Accepted::kFilteredDropped)
113 UpdateColor(color, g_dm_dropped);
114 else if (ns.direction == NavmsgStatus::Direction::kOutput)
115 UpdateColor(color, g_dm_output);
116 else if (ns.direction == NavmsgStatus::Direction::kInput)
117 UpdateColor(color, g_dm_input);
119 UpdateColor(color, g_dm_ok);
120 return color.IsOk() ? color : defaults(ns);
126 if (!ll.message.empty()) ws << Timestamp(ll.navmsg->created_at) <<
" ";
127 if (ll.message.empty())
129 else if (ll.state.direction == NavmsgStatus::Direction::kOutput)
130 ws <<
" " << kUtfRightArrow <<
" ";
131 else if (ll.state.direction == NavmsgStatus::Direction::kInput)
132 ws <<
" " << kUtfLeftwardsArrowToBar <<
" ";
133 else if (ll.state.direction == NavmsgStatus::Direction::kInternal)
134 ws <<
" " << kUtfLeftRightArrow <<
" ";
136 ws <<
" " << kUtfLeftArrow <<
" ";
138 if (ll.message.empty())
140 else if (ll.state.status != NavmsgStatus::State::kOk)
141 ws << kUtfMultiplicationX;
142 else if (ll.state.accepted == NavmsgStatus::Accepted::kFilteredNoOutput)
143 ws << kUtfFallingDiagonal;
144 else if (ll.state.accepted == NavmsgStatus::Accepted::kFilteredDropped)
145 ws << kUtfCircledDivisionSlash;
149 std::stringstream error_msg;
150 if (ll.state.status != NavmsgStatus::State::kOk) {
152 << (ll.error_msg.empty() ?
"Unknown errror" : ll.error_msg);
154 std::string iface(ll.navmsg ? ll.navmsg->source->iface :
"");
155 if (iface.size() > 30)
156 iface = std::string(
"...") + iface.substr(iface.size() - 27);
159 dc.DrawText(ws, 0, y);
161 ws << ll.message << error_msg.str();
162 dc.DrawText(ws, data_pos, y);
164 std::max(m_text_width, GetTextExtent(ws).GetWidth() + data_pos);
168 : wxScrolledWindow(parent), m_n_lines(n_lines), m_is_paused(false) {
169 SetName(
"TtyScroll");
171 dc.GetTextExtent(
"Line Height", NULL, &m_line_height);
172 SetScrollRate(m_line_height, m_line_height);
173 for (
unsigned i = 0; i < m_n_lines; i++) m_lines.push_back(
Logline());
174 SetColors(std::make_unique<UserColorsByState>());
175 Bind(wxEVT_SIZE, [&](wxSizeEvent& ev) { OnSize(ev); });
178void TtyScroll::OnSize(wxSizeEvent& ev) {
179 m_n_lines = ev.GetSize().y / GetCharHeight();
180 while (m_lines.size() < m_n_lines) m_lines.push_back(
Logline());
186 for (
size_t i = 0; i < m_n_lines; ++i) m_lines.push_back(
Logline());
191 if (m_is_paused || !m_filter.
Pass(ll.state, ll.navmsg))
return;
192 if (!m_quick_filter.empty() && !IsFilterMatch(ll, m_quick_filter))
return;
193 while (m_lines.size() > m_n_lines - 1) m_lines.pop_front();
194 m_lines.push_back(ll);
199 m_color_by_state = std::move(color_by_state);
202void TtyScroll::OnDraw(wxDC& dc) {
204 wxRect rect_update = GetUpdateRegion().GetBox();
205 CalcUnscrolledPosition(rect_update.x, rect_update.y, &rect_update.x,
207 size_t line_from = rect_update.y / m_line_height;
208 size_t line_to = rect_update.GetBottom() / m_line_height;
209 if (line_to > m_n_lines - 1) line_to = m_n_lines - 1;
211 wxCoord y = line_from * m_line_height;
213 for (
size_t line = line_from; line <= line_to; line++) {
215 dc.SetTextForeground((*m_color_by_state)(m_lines[line].state));
216 DrawLine(dc, m_lines[line], 40 * GetCharWidth(), y);
219 SetVirtualSize(m_text_width, (m_n_lines + 1) * m_line_height);
223 std::stringstream ss;
224 for (
auto& line : m_lines) ss << line.message <<
"\n";
225 if (wxTheClipboard->Open()) {
226 wxTheClipboard->SetData(
new wxTextDataObject(ss.str()));
227 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 pattern implementation built on top of wxWidgets event handling.