OpenCPN Partial API docs
Loading...
Searching...
No Matches
tty_scroll.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2013 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 **************************************************************************/
16
17#include <iomanip>
18#include <sstream>
19#include <string>
20
21#ifdef _WIN32
22#define NOMINMAX // Disable min/max compiler nacros.
23#endif
24
25#include <wx/clipbrd.h>
26#include <wx/dcclient.h>
27#include <wx/gdicmn.h>
28#include <wx/string.h>
29#include <wx/textctrl.h>
30
31#include "observable/observable.h"
32
33#include "tty_scroll.h"
34#include "model/config_vars.h"
35
36// Recursive include of winsock2.h -> redefined DrawText.
37// See: https://forums.wxwidgets.org/viewtopic.php?f=19&t=51849
38#ifdef _WIN32
39#include <wx/msw/winundef.h>
40#endif
41
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;
51}
52
53static void UpdateColor(wxColour& colour, unsigned rgb) {
54 if (rgb == kUndefinedColor) {
55 colour = wxNullColour;
56 } else {
57 colour.SetRGB(rgb);
58 }
59}
60
61static std::string Timestamp(const NavmsgTimePoint& when) {
62 using namespace std;
63 using namespace std::chrono;
64
65 auto now = when.time_since_epoch();
66 auto _hours = duration_cast<hours>(now);
67 now -= _hours;
68 auto _minutes = duration_cast<minutes>(now);
69 now -= _minutes;
70 auto _seconds = duration_cast<seconds>(now);
71 now -= _seconds;
72 auto ms = duration_cast<milliseconds>(now);
73#ifdef CSTDIO_TTYSCROLL_TIMESTAMP
74 // Perhaps faster, but not type safe. Needs <cstdio>
75 char buf[128];
76 snprintf(buf, sizeof(buf), "%02ld:%02ld:%02ld.%03ld", _hours.count() % 24,
77 _minutes.count(), _seconds.count(), ms.count());
78 return buf;
79#else
80 stringstream ss;
81 ss << setw(2) << setfill('0') << _hours.count() % 24 << ":" << setw(2)
82 << _minutes.count() << ":" << setw(2) << _seconds.count() << "." << setw(3)
83 << ms.count();
84 return ss.str();
85#endif
86}
87
88wxColor StdColorsByState::operator()(NavmsgStatus ns) {
89 wxColour color;
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)
100 color = kDarkGreen;
101 else // input event
102 color = wxColour("ORANGE");
103 return color;
104}
105
106wxColor UserColorsByState::operator()(NavmsgStatus ns) {
107 wxColour color;
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);
118 else
119 UpdateColor(color, g_dm_ok);
120 return color.IsOk() ? color : defaults(ns);
121}
122
124void TtyScroll::DrawLine(wxDC& dc, const Logline& ll, int data_pos, int y) {
125 wxString ws;
126 if (!ll.message.empty()) ws << Timestamp(ll.navmsg->created_at) << " ";
127 if (ll.message.empty())
128 ;
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 << " ";
135 else
136 ws << " " << kUtfLeftArrow << " ";
137
138 if (ll.message.empty())
139 ;
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;
146 else
147 ws << kUtfCheckMark;
148
149 std::stringstream error_msg;
150 if (ll.state.status != NavmsgStatus::State::kOk) {
151 error_msg << " - "
152 << (ll.error_msg.empty() ? "Unknown errror" : ll.error_msg);
153 }
154 std::string iface(ll.navmsg ? ll.navmsg->source->iface : "");
155 if (iface.size() > 30)
156 iface = std::string("...") + iface.substr(iface.size() - 27);
157 ws << iface << " ";
158
159 dc.DrawText(ws, 0, y);
160 ws = "";
161 ws << ll.message << error_msg.str();
162 dc.DrawText(ws, data_pos, y);
163 m_text_width =
164 std::max(m_text_width, GetTextExtent(ws).GetWidth() + data_pos);
165}
166
167TtyScroll::TtyScroll(wxWindow* parent, int n_lines)
168 : wxScrolledWindow(parent), m_n_lines(n_lines), m_is_paused(false) {
169 SetName("TtyScroll");
170 wxClientDC dc(this);
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); });
176}
177
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());
181 ev.Skip();
182}
183
185 m_lines.clear();
186 for (size_t i = 0; i < m_n_lines; ++i) m_lines.push_back(Logline());
187 Refresh(true);
188}
189
190void TtyScroll::Add(const Logline& ll) {
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);
195 Refresh(true);
196}
197
198void TtyScroll::SetColors(std::unique_ptr<ColorByState> color_by_state) {
199 m_color_by_state = std::move(color_by_state);
200}
201
202void TtyScroll::OnDraw(wxDC& dc) {
203 // Update region is always in device coords, translate to logical ones
204 wxRect rect_update = GetUpdateRegion().GetBox();
205 CalcUnscrolledPosition(rect_update.x, rect_update.y, &rect_update.x,
206 &rect_update.y);
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;
210
211 wxCoord y = line_from * m_line_height;
212 m_text_width = 0;
213 for (size_t line = line_from; line <= line_to; line++) {
214 wxString ws;
215 dc.SetTextForeground((*m_color_by_state)(m_lines[line].state));
216 DrawLine(dc, m_lines[line], 40 * GetCharWidth(), y);
217 y += m_line_height;
218 }
219 SetVirtualSize(m_text_width, (m_n_lines + 1) * m_line_height);
220}
221
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();
228 }
229}
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.
void SetColors(std::unique_ptr< ColorByState > color_by_state)
Set color scheme.
virtual void Clear()
Clear the log window.
virtual void Add(const Logline &line)
Add a line to bottom of window, typically discarding top-most line.
void DrawLine(wxDC &dc, const Logline &ll, int data_pos, int y)
Draw a single line in the log window.
void CopyToClipboard() const
Copy message contents to clipboard.
TtyScroll(wxWindow *parent, int n_lines)
Create a TtyScroll instance.
Global variables stored in configuration file.
Item in the log window.
Definition nmea_log.h:32
Scrolled tty like window for logging.