1 /*
2 * Copyright (C) 2021 Robin Gareus <robin@gareus.org>
3 * Copyright (C) 2021 Ben Loftis <ben@harrisonconsoles.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23
24 #include <cassert>
25
26 #include "ardour/session.h"
27 #include "ardour/session_route.h"
28 #include "ardour/track.h"
29
30 #include "gtkmm2ext/utils.h"
31 #include "temporal/time.h"
32
33 #include "audio_clock.h"
34 #include "gui_thread.h"
35 #include "rec_info_box.h"
36 #include "timers.h"
37 #include "ui_config.h"
38
39 #include "pbd/i18n.h"
40
41 using namespace ARDOUR;
42
RecInfoBox()43 RecInfoBox::RecInfoBox ()
44 {
45 set_name (X_("RecInfoBox"));
46 _layout_label = Pango::Layout::create (get_pango_context ());
47 _layout_value = Pango::Layout::create (get_pango_context ());
48
49 UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &RecInfoBox::dpi_reset));
50 dpi_reset ();
51 }
52
53 void
on_size_request(Gtk::Requisition * r)54 RecInfoBox::on_size_request (Gtk::Requisition* r)
55 {
56 r->width = _width;
57 r->height = std::max (12, _height);
58 }
59
60 void
on_size_allocate(Gtk::Allocation & a)61 RecInfoBox::on_size_allocate (Gtk::Allocation& a)
62 {
63 CairoWidget::on_size_allocate (a);
64 }
65
66 void
set_session(Session * s)67 RecInfoBox::set_session (Session* s)
68 {
69 SessionHandlePtr::set_session (s);
70
71 if (_session) {
72 update ();
73 }
74 }
75
76 void
update()77 RecInfoBox::update ()
78 {
79 set_dirty ();
80 }
81
82 /* ****************************************************************************/
83
84 void
set_session(Session * s)85 DurationInfoBox::set_session (Session* s)
86 {
87 RecInfoBox::set_session (s);
88
89 if (!_session) {
90 _rectime_connection.disconnect ();
91 return;
92 }
93 _session->RecordStateChanged.connect (_session_connections, invalidator (*this), boost::bind (&DurationInfoBox::rec_state_changed, this), gui_context());
94 _session->UpdateRouteRecordState.connect (_session_connections, invalidator (*this), boost::bind (&DurationInfoBox::update, this), gui_context());
95 }
96
97 void
rec_state_changed()98 DurationInfoBox::rec_state_changed ()
99 {
100 if (_session && _session->actively_recording ()) {
101 if (!_rectime_connection.connected ()) {
102 _rectime_connection = Timers::rapid_connect (sigc::mem_fun (*this, &DurationInfoBox::update));
103 }
104 } else {
105 _rectime_connection.disconnect ();
106 }
107 update ();
108 }
109
110 void
dpi_reset()111 DurationInfoBox::dpi_reset ()
112 {
113 int wv, hv;
114 _layout_value->set_font_description (UIConfiguration::instance ().get_NormalMonospaceFont ());
115 _layout_value->set_text ("<00:00:00:0>");
116 _layout_value->get_pixel_size (wv, hv);
117 _width = 8 + wv;
118 _height = 4 + hv;
119 queue_resize ();
120 }
121
122 void
render(Cairo::RefPtr<Cairo::Context> const & cr,cairo_rectangle_t * r)123 DurationInfoBox::render (Cairo::RefPtr<Cairo::Context> const& cr, cairo_rectangle_t* r)
124 {
125 int ww = get_width ();
126 int hh = get_height ();
127
128 cr->rectangle (r->x, r->y, r->width, r->height);
129 cr->clip ();
130 cr->set_operator (Cairo::OPERATOR_OVER);
131
132 bool recording;
133 if (_session && _session->actively_recording ()) {
134 Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("alert:red"), .7);
135 recording = true;
136 } else {
137 Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("widget:bg"), .7);
138 recording = false;
139 }
140
141 Gtkmm2ext::rounded_rectangle (cr, 1 , 1, ww - 1, hh - 1, /*_height / 4.0 */ 4);
142 cr->fill ();
143
144 if (!_session) {
145 return;
146 }
147
148 Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("neutral:foreground"));
149
150 samplecnt_t capture_duration = _session->capture_duration ();
151 samplecnt_t sample_rate = _session->nominal_sample_rate ();
152
153 int w, h;
154
155 if (capture_duration > 0) {
156 char buf[32];
157 AudioClock::print_minsec (capture_duration, buf, sizeof (buf), sample_rate, 1);
158 if (recording) {
159 _layout_value->set_text (string_compose(" %1 ", std::string(buf).substr(1)));
160 } else {
161 _layout_value->set_text (string_compose("<%1>", std::string(buf).substr(1)));
162 }
163 } else {
164 _layout_value->set_text (" --:--:--:- ");
165 }
166 _layout_value->get_pixel_size (w, h);
167 cr->move_to (.5 * (ww - w), hh/2 - h/2);
168 _layout_value->show_in_cairo_context (cr);
169 }
170
171 void
update()172 DurationInfoBox::update ()
173 {
174 RecInfoBox::update ();
175 }
176
177 /* ****************************************************************************/
178
179 void
set_session(Session * s)180 XrunInfoBox::set_session (Session* s)
181 {
182 RecInfoBox::set_session (s);
183
184 if (!_session) {
185 return;
186 }
187
188 _session->Xrun.connect (_session_connections, invalidator (*this), boost::bind (&XrunInfoBox::update, this), gui_context());
189 _session->RecordStateChanged.connect (_session_connections, invalidator (*this), boost::bind (&XrunInfoBox::update, this), gui_context());
190 }
191
192 void
dpi_reset()193 XrunInfoBox::dpi_reset ()
194 {
195 int wv, hv;
196 _layout_value->set_font_description (UIConfiguration::instance ().get_NormalFont ());
197 _layout_value->set_text ("<99+>");
198 _layout_value->get_pixel_size (wv, hv);
199 _width = 8 + wv;
200 _height = 8 + hv;
201 queue_resize ();
202 }
203
204 void
render(Cairo::RefPtr<Cairo::Context> const & cr,cairo_rectangle_t * r)205 XrunInfoBox::render (Cairo::RefPtr<Cairo::Context> const& cr, cairo_rectangle_t* r)
206 {
207 if (!_session) {
208 return;
209 }
210
211 int ww = get_width ();
212 int hh = get_height ();
213
214 cr->rectangle (r->x, r->y, r->width, r->height);
215 cr->clip ();
216 cr->set_operator (Cairo::OPERATOR_OVER);
217
218 unsigned int xruns = _session->capture_xruns ();
219
220 if (xruns > 0) {
221 Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("alert:red"), .7);
222 } else {
223 Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("widget:bg"), .7);
224 }
225
226 Gtkmm2ext::rounded_rectangle (cr, 1 , 1, ww - 2, hh - 2, /*_height / 4.0 */ 4);
227 cr->fill ();
228
229 if (xruns < 99) {
230 if (_session->actively_recording ()) {
231 _layout_value->set_text (string_compose ("%1", xruns));
232 } else if (_session->capture_duration () > 0) {
233 _layout_value->set_text (string_compose ("<%1>", xruns));
234 } else {
235 _layout_value->set_text ("-");
236 }
237 } else {
238 if (_session->actively_recording ()) {
239 _layout_value->set_text ("99+");
240 } else if (_session->capture_duration () > 0) {
241 _layout_value->set_text ("<99+>");
242 } else {
243 assert (0);
244 return;
245 }
246 }
247
248 Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("neutral:foreground"));
249 int w, h;
250 _layout_value->get_pixel_size (w, h);
251 cr->move_to (.5 * (ww - w), .5 * (hh - h));
252 _layout_value->show_in_cairo_context (cr);
253 }
254
255 void
update()256 XrunInfoBox::update ()
257 {
258 RecInfoBox::update ();
259 }
260
261 /* ****************************************************************************/
262
263 void
set_session(Session * s)264 RemainInfoBox::set_session (Session* s)
265 {
266 RecInfoBox::set_session (s);
267
268 if (!_session) {
269 _diskspace_connection.disconnect ();
270 return;
271 }
272
273 _diskspace_connection = Timers::second_connect (sigc::mem_fun (*this, &RemainInfoBox::update));
274 _session->UpdateRouteRecordState.connect (_session_connections, invalidator (*this), boost::bind (&RemainInfoBox::update, this), gui_context());
275 }
276
277 void
dpi_reset()278 RemainInfoBox::dpi_reset ()
279 {
280 _layout_label->set_font_description (UIConfiguration::instance ().get_NormalFont ());
281 _layout_value->set_font_description (UIConfiguration::instance ().get_NormalMonospaceFont ());
282
283 int wl, hl, wv, hv;
284
285 _layout_label->set_text (_("Disk Space:"));
286 _layout_label->get_pixel_size (wl, hl);
287
288 _layout_value->set_text (_(">24h"));
289 _layout_value->get_pixel_size (wv, hv);
290
291 _width = 8 + std::max (wl, wv);
292 _height = 2 + hv + 2 + hl + 2;
293
294 queue_resize ();
295 }
296
297 void
count_recenabled_streams(Route & route)298 RemainInfoBox::count_recenabled_streams (Route& route)
299 {
300 Track* track = dynamic_cast<Track*>(&route);
301 if (track && track->rec_enable_control()->get_value()) {
302 _rec_enabled_streams += track->n_inputs().n_total();
303 }
304 }
305
306 void
render(Cairo::RefPtr<Cairo::Context> const & cr,cairo_rectangle_t * r)307 RemainInfoBox::render (Cairo::RefPtr<Cairo::Context> const& cr, cairo_rectangle_t* r)
308 {
309 int ww = get_width ();
310 int hh = get_height ();
311
312 cr->rectangle (r->x, r->y, r->width, r->height);
313 cr->clip ();
314 cr->set_operator (Cairo::OPERATOR_OVER);
315
316 if (!_session) {
317 return;
318 }
319
320 samplecnt_t sample_rate = _session->nominal_sample_rate ();
321 boost::optional<samplecnt_t> opt_samples = _session->available_capture_duration ();
322
323 Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("widget:bg"), .7);
324
325 if (!opt_samples) {
326 /* Available space is unknown */
327 _layout_value->set_text (_("Unknown"));
328 } else if (opt_samples.value_or (0) == max_samplecnt) {
329 _layout_value->set_text (_(">24h"));
330 } else {
331 _rec_enabled_streams = 0;
332 _session->foreach_route (this, &RemainInfoBox::count_recenabled_streams, false);
333
334 samplecnt_t samples = opt_samples.value_or (0);
335
336 if (_rec_enabled_streams > 0) {
337 samples /= _rec_enabled_streams;
338 }
339
340 float remain_sec = samples / (float)sample_rate;
341 char buf[32];
342
343 if (remain_sec > 86400) {
344 _layout_value->set_text (_(">24h"));
345 } else if (remain_sec > 32400 /* 9 hours */) {
346 snprintf (buf, sizeof (buf), "%.0f", remain_sec / 3600.f);
347 _layout_value->set_text (std::string (buf) + S_("hours|h"));
348 } else if (remain_sec > 5940 /* 99 mins */) {
349 snprintf (buf, sizeof (buf), "%.1f", remain_sec / 3600.f);
350 _layout_value->set_text (std::string (buf) + S_("hours|h"));
351 } else if (remain_sec > 60*3 /* 3 mins */) {
352 snprintf (buf, sizeof (buf), "%.0f", remain_sec / 60.f);
353 _layout_value->set_text (std::string (buf) + S_("minutes|m"));
354 } else {
355 Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("alert:red"), .7);
356 snprintf (buf, sizeof (buf), "%.0f", remain_sec / 60.f);
357 _layout_value->set_text (std::string (buf) + S_("minutes|m"));
358 }
359 }
360
361 /* draw box */
362 Gtkmm2ext::rounded_rectangle (cr, 1 , 1, ww - 2, hh - 2, /*_height / 4.0 */ 4);
363 cr->fill ();
364
365 /*draw text */
366 Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("neutral:foreground"));
367 cr->set_line_width (1.0);
368
369 int w, h;
370 _layout_label->get_pixel_size (w, h);
371 cr->move_to (.5 * (ww - w), 4);
372 _layout_label->show_in_cairo_context (cr);
373
374 _layout_value->get_pixel_size (w, h);
375 cr->move_to (.5 * (ww - w), hh - 4 - h);
376 _layout_value->show_in_cairo_context (cr);
377 }
378
379 void
update()380 RemainInfoBox::update ()
381 {
382 RecInfoBox::update ();
383 }
384