From 46d4b4ac668d0ceb0576cc09aa15c465a86a72cc Mon Sep 17 00:00:00 2001 From: Stefan Kangas Date: Wed, 8 Dec 2010 23:33:14 +0100 Subject: [PATCH] First version of timecode widget adapted from Ardour. --- src/gui/Makefile.am | 4 + src/gui/panels/timeline-panel.cpp | 12 +- src/gui/panels/timeline-panel.hpp | 4 +- src/gui/util/convert.cpp | 44 + src/gui/util/convert.hpp | 38 + src/gui/widgets/timecode-widget.cpp | 1353 +++++++++++++++++++++++++++ src/gui/widgets/timecode-widget.hpp | 192 ++++ 7 files changed, 1636 insertions(+), 11 deletions(-) create mode 100644 src/gui/util/convert.cpp create mode 100644 src/gui/util/convert.hpp create mode 100644 src/gui/widgets/timecode-widget.cpp create mode 100644 src/gui/widgets/timecode-widget.hpp diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am index 7f578eae1..55218452e 100644 --- a/src/gui/Makefile.am +++ b/src/gui/Makefile.am @@ -95,6 +95,8 @@ gtk_gui_la_SOURCES = \ $(lumigui_srcdir)/panels/timeline-panel.hpp \ $(lumigui_srcdir)/panels/viewer-panel.cpp \ $(lumigui_srcdir)/panels/viewer-panel.hpp \ + $(lumigui_srcdir)/util/convert.cpp \ + $(lumigui_srcdir)/util/convert.hpp \ $(lumigui_srcdir)/util/rectangle.cpp \ $(lumigui_srcdir)/util/rectangle.hpp \ $(lumigui_srcdir)/widgets/button-bar.cpp \ @@ -104,6 +106,8 @@ gtk_gui_la_SOURCES = \ $(lumigui_srcdir)/widgets/mini-button.hpp \ $(lumigui_srcdir)/widgets/panel-bar.cpp \ $(lumigui_srcdir)/widgets/panel-bar.hpp \ + $(lumigui_srcdir)/widgets/timecode-widget.cpp \ + $(lumigui_srcdir)/widgets/timecode-widget.hpp \ $(lumigui_srcdir)/widgets/timeline-widget.cpp \ $(lumigui_srcdir)/widgets/timeline-widget.hpp \ $(lumigui_srcdir)/widgets/timeline/timeline-arrow-tool.cpp \ diff --git a/src/gui/panels/timeline-panel.cpp b/src/gui/panels/timeline-panel.cpp index a93e49251..f85b83bc1 100644 --- a/src/gui/panels/timeline-panel.cpp +++ b/src/gui/panels/timeline-panel.cpp @@ -49,8 +49,7 @@ const int TimelinePanel::ZoomToolSteps = 2; // 2 seems comfortable TimelinePanel::TimelinePanel(workspace::PanelManager &panel_manager, GdlDockItem *dock_item) : Panel(panel_manager, dock_item, get_title(), get_stock_id()), - timeIndicator(), - timeIndicatorButton(), + timeCode("sequence_clock", "timecode_widget", true), previousButton(Stock::MEDIA_PREVIOUS), rewindButton(Stock::MEDIA_REWIND), playPauseButton(Stock::MEDIA_PLAY), @@ -82,11 +81,7 @@ TimelinePanel::TimelinePanel(workspace::PanelManager &panel_manager, panelBar.pack_start(sequenceChooser, PACK_SHRINK); // Setup the toolbar - timeIndicatorButton.add(timeIndicator); - timeIndicatorButton.set_relief(Gtk::RELIEF_NONE); - timeIndicatorButton.set_focus_on_click(false); - - toolbar.append(timeIndicatorButton); + toolbar.append(timeCode); toolbar.append(previousButton); toolbar.append(rewindButton); @@ -114,7 +109,6 @@ TimelinePanel::TimelinePanel(workspace::PanelManager &panel_manager, // Setup tooltips sequenceChooser .set_tooltip_text(_("Change sequence")); - timeIndicatorButton .set_tooltip_text(_("Go to time code")); previousButton .set_tooltip_text(_("To beginning")); rewindButton .set_tooltip_text(_("Rewind")); @@ -374,7 +368,7 @@ TimelinePanel::set_tool(timeline::ToolType tool) void TimelinePanel::show_time(gavl_time_t time) { - timeIndicator.set_text(lumiera_tmpbuf_print_time(time)); + // timeIndicator.set_text(lumiera_tmpbuf_print_time(time)); } bool diff --git a/src/gui/panels/timeline-panel.hpp b/src/gui/panels/timeline-panel.hpp index eaec9e02b..01a618b26 100644 --- a/src/gui/panels/timeline-panel.hpp +++ b/src/gui/panels/timeline-panel.hpp @@ -27,6 +27,7 @@ #define TIMELINE_PANEL_HPP #include "panel.hpp" +#include "../widgets/timecode-widget.hpp" #include "../widgets/timeline-widget.hpp" using namespace gui::widgets; @@ -168,8 +169,7 @@ private: timelineStates; // Toolbar Widgets - Gtk::Label timeIndicator; - Gtk::Button timeIndicatorButton; + TimeCode timeCode; MiniButton previousButton; MiniButton rewindButton; diff --git a/src/gui/util/convert.cpp b/src/gui/util/convert.cpp new file mode 100644 index 000000000..e9fe0e293 --- /dev/null +++ b/src/gui/util/convert.cpp @@ -0,0 +1,44 @@ +/* + convert.cpp - Defines utility functions for conversions + + Copyright (C) Lumiera.org + 2006, Paul Davis + 2010, Stefan Kangas + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include + +#include "convert.hpp" + +namespace gui { +namespace util { + +double +atof (const string& s) +{ + return std::atof (s.c_str()); +} + +int +atoi (const string& s) +{ + return std::atoi (s.c_str()); +} + +} // namespace util +} // namespace gui diff --git a/src/gui/util/convert.hpp b/src/gui/util/convert.hpp new file mode 100644 index 000000000..887040251 --- /dev/null +++ b/src/gui/util/convert.hpp @@ -0,0 +1,38 @@ +/* + convert.hpp - Declares utility functions for conversions + + Copyright (C) Lumiera.org + 2006, Paul Davis + 2010, Stefan Kangas + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include + +using std::string; + +namespace gui { +namespace util { + +double atof(const string& s); + +int atoi(const string& s); + +} // namespace util +} // namespace gui + + diff --git a/src/gui/widgets/timecode-widget.cpp b/src/gui/widgets/timecode-widget.cpp new file mode 100644 index 000000000..964f22c16 --- /dev/null +++ b/src/gui/widgets/timecode-widget.cpp @@ -0,0 +1,1353 @@ +/* + timecode-widget.cpp - Implementation of the timecode widget + + Copyright (C) Lumiera.org + 1999, Paul Davis + 2010, Stefan Kangas +#include +#include // for sprintf +#include +#include + +#include +#include + +#include "timecode-widget.hpp" +#include "../util/convert.hpp" + +#include "../../lib/lumitime.hpp" + +extern "C" { +#include "../../lib/time.h" +} + +using namespace sigc; +using namespace Gtk; +using namespace std; + +using gui::util::atof; +using gui::util::atoi; + +namespace gui { +namespace widgets { + +// TODO: frame rate should not be a constant, but instead be per sequence +const float framerate = 25; + +sigc::signal TimeCode::ModeChanged; + +const unsigned int TimeCode::field_length[(int) TimeCode::VFrames+1] = { + 2, /* SMPTE_Hours */ + 2, /* SMPTE_Minutes */ + 2, /* SMPTE_Seconds */ + 2, /* SMPTE_Frames */ + 2, /* MS_Hours */ + 2, /* MS_Minutes */ + 5, /* MS_Seconds */ + 10 /* VFrames */ +}; + +TimeCode::TimeCode(std::string clock_name, std::string widget_name, bool allow_edit) /*, bool duration,*/ + : _name(clock_name), + // is_duration(duration), + editable(allow_edit), + colon1(":"), + colon2(":"), + colon3(":"), + colon4(":"), + colon5(":") +{ + last_when = 0; + last_pdelta = 0; + last_sdelta = 0; + key_entry_state = 0; + ops_menu = 0; + dragging = false; + + audio_frames_ebox.add(audio_frames_label); + + frames_packer.set_homogeneous(false); + frames_packer.set_border_width(2); + frames_packer.pack_start(audio_frames_ebox, false, false); + + frames_packer_hbox.pack_start(frames_packer, true, false); + + hours_ebox.add(hours_label); + minutes_ebox.add(minutes_label); + seconds_ebox.add(seconds_label); + frames_ebox.add(frames_label); + ms_hours_ebox.add(ms_hours_label); + ms_minutes_ebox.add(ms_minutes_label); + ms_seconds_ebox.add(ms_seconds_label); + + smpte_packer.set_homogeneous(false); + smpte_packer.set_border_width(2); + smpte_packer.pack_start(hours_ebox, false, false); + smpte_packer.pack_start(colon1, false, false); + smpte_packer.pack_start(minutes_ebox, false, false); + smpte_packer.pack_start(colon2, false, false); + smpte_packer.pack_start(seconds_ebox, false, false); + smpte_packer.pack_start(colon3, false, false); + smpte_packer.pack_start(frames_ebox, false, false); + + smpte_packer_hbox.pack_start(smpte_packer, true, false); + + minsec_packer.set_homogeneous(false); + minsec_packer.set_border_width(2); + minsec_packer.pack_start(ms_hours_ebox, false, false); + minsec_packer.pack_start(colon4, false, false); + minsec_packer.pack_start(ms_minutes_ebox, false, false); + minsec_packer.pack_start(colon5, false, false); + minsec_packer.pack_start(ms_seconds_ebox, false, false); + + minsec_packer_hbox.pack_start(minsec_packer, true, false); + + clock_frame.set_shadow_type(Gtk::SHADOW_IN); + clock_frame.set_name("BaseFrame"); + + clock_frame.add(clock_base); + + set_widget_name(widget_name); + + // Set mode to force update + _mode = Off; + set_mode(SMPTE); + + pack_start(clock_frame, true, true); + + /* the clock base handles button releases for menu popup regardless of + editable status. if the clock is editable, the clock base is where + we pass focus to after leaving the last editable "field", which + will then shutdown editing till the user starts it up again. + + it does this because the focus out event on the field disables + keyboard event handling, and we don't connect anything up to + notice focus in on the clock base. hence, keyboard event handling + stays disabled. + */ + + clock_base.add_events(Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::SCROLL_MASK); + clock_base.signal_button_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_release_event), SMPTE_Hours)); + + // Session::SMPTEOffsetChanged.connect(mem_fun(*this, &TimeCode::smpte_offset_changed)); + + if (editable) { + setup_events(); + } + + set(last_when, true); +} + +void +TimeCode::set_widget_name(string name) +{ + Widget::set_name(name); + + clock_base.set_name(name); + + audio_frames_label.set_name(name); + hours_label.set_name(name); + minutes_label.set_name(name); + seconds_label.set_name(name); + frames_label.set_name(name); + ms_hours_label.set_name(name); + ms_minutes_label.set_name(name); + ms_seconds_label.set_name(name); + hours_ebox.set_name(name); + minutes_ebox.set_name(name); + seconds_ebox.set_name(name); + frames_ebox.set_name(name); + audio_frames_ebox.set_name(name); + ms_hours_ebox.set_name(name); + ms_minutes_ebox.set_name(name); + ms_seconds_ebox.set_name(name); + + colon1.set_name(name); + colon2.set_name(name); + colon3.set_name(name); + colon4.set_name(name); + colon5.set_name(name); + + queue_draw(); +} + +void +TimeCode::setup_events() +{ + clock_base.set_can_focus(true); + + const Gdk::EventMask eventMask = + Gdk::BUTTON_PRESS_MASK| + Gdk::BUTTON_RELEASE_MASK| + Gdk::KEY_PRESS_MASK| + Gdk::KEY_RELEASE_MASK| + Gdk::FOCUS_CHANGE_MASK| + Gdk::POINTER_MOTION_MASK| + Gdk::SCROLL_MASK; + + hours_ebox.add_events(eventMask); + minutes_ebox.add_events(eventMask); + seconds_ebox.add_events(eventMask); + frames_ebox.add_events(eventMask); + ms_hours_ebox.add_events(eventMask); + ms_minutes_ebox.add_events(eventMask); + ms_seconds_ebox.add_events(eventMask); + audio_frames_ebox.add_events(eventMask); + + hours_ebox.set_can_focus(true); + minutes_ebox.set_can_focus(true); + seconds_ebox.set_can_focus(true); + frames_ebox.set_can_focus(true); + audio_frames_ebox.set_can_focus(true); + ms_hours_ebox.set_can_focus(true); + ms_minutes_ebox.set_can_focus(true); + ms_seconds_ebox.set_can_focus(true); + + hours_ebox.signal_motion_notify_event().connect(bind(mem_fun( + *this, &TimeCode::field_motion_notify_event), SMPTE_Hours)); + minutes_ebox.signal_motion_notify_event().connect(bind(mem_fun( + *this, &TimeCode::field_motion_notify_event), SMPTE_Minutes)); + seconds_ebox.signal_motion_notify_event().connect(bind(mem_fun( + *this, &TimeCode::field_motion_notify_event), SMPTE_Seconds)); + frames_ebox.signal_motion_notify_event().connect(bind(mem_fun( + *this, &TimeCode::field_motion_notify_event), SMPTE_Frames)); + audio_frames_ebox.signal_motion_notify_event().connect(bind(mem_fun( + *this, &TimeCode::field_motion_notify_event), VFrames)); + ms_hours_ebox.signal_motion_notify_event().connect(bind(mem_fun( + *this, &TimeCode::field_motion_notify_event), MS_Hours)); + ms_minutes_ebox.signal_motion_notify_event().connect(bind(mem_fun( + *this, &TimeCode::field_motion_notify_event), MS_Minutes)); + ms_seconds_ebox.signal_motion_notify_event().connect(bind(mem_fun( + *this, &TimeCode::field_motion_notify_event), MS_Seconds)); + + hours_ebox.signal_button_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_press_event), SMPTE_Hours)); + minutes_ebox.signal_button_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_press_event), SMPTE_Minutes)); + seconds_ebox.signal_button_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_press_event), SMPTE_Seconds)); + frames_ebox.signal_button_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_press_event), SMPTE_Frames)); + audio_frames_ebox.signal_button_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_press_event), VFrames)); + ms_hours_ebox.signal_button_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_press_event), MS_Hours)); + ms_minutes_ebox.signal_button_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_press_event), MS_Minutes)); + ms_seconds_ebox.signal_button_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_press_event), MS_Seconds)); + + hours_ebox.signal_button_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_release_event), SMPTE_Hours)); + minutes_ebox.signal_button_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_release_event), SMPTE_Minutes)); + seconds_ebox.signal_button_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_release_event), SMPTE_Seconds)); + frames_ebox.signal_button_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_release_event), SMPTE_Frames)); + audio_frames_ebox.signal_button_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_release_event), VFrames)); + ms_hours_ebox.signal_button_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_release_event), MS_Hours)); + ms_minutes_ebox.signal_button_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_release_event), MS_Minutes)); + ms_seconds_ebox.signal_button_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_release_event), MS_Seconds)); + + hours_ebox.signal_scroll_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_scroll_event), SMPTE_Hours)); + minutes_ebox.signal_scroll_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_scroll_event), SMPTE_Minutes)); + seconds_ebox.signal_scroll_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_scroll_event), SMPTE_Seconds)); + frames_ebox.signal_scroll_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_scroll_event), SMPTE_Frames)); + audio_frames_ebox.signal_scroll_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_scroll_event), VFrames)); + ms_hours_ebox.signal_scroll_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_scroll_event), MS_Hours)); + ms_minutes_ebox.signal_scroll_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_scroll_event), MS_Minutes)); + ms_seconds_ebox.signal_scroll_event().connect(bind(mem_fun( + *this, &TimeCode::field_button_scroll_event), MS_Seconds)); + + hours_ebox.signal_key_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_press_event), SMPTE_Hours)); + minutes_ebox.signal_key_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_press_event), SMPTE_Minutes)); + seconds_ebox.signal_key_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_press_event), SMPTE_Seconds)); + frames_ebox.signal_key_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_press_event), SMPTE_Frames)); + audio_frames_ebox.signal_key_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_press_event), VFrames)); + ms_hours_ebox.signal_key_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_press_event), MS_Hours)); + ms_minutes_ebox.signal_key_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_press_event), MS_Minutes)); + ms_seconds_ebox.signal_key_press_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_press_event), MS_Seconds)); + + hours_ebox.signal_key_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_release_event), SMPTE_Hours)); + minutes_ebox.signal_key_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_release_event), SMPTE_Minutes)); + seconds_ebox.signal_key_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_release_event), SMPTE_Seconds)); + frames_ebox.signal_key_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_release_event), SMPTE_Frames)); + audio_frames_ebox.signal_key_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_release_event), VFrames)); + ms_hours_ebox.signal_key_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_release_event), MS_Hours)); + ms_minutes_ebox.signal_key_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_release_event), MS_Minutes)); + ms_seconds_ebox.signal_key_release_event().connect(bind(mem_fun( + *this, &TimeCode::field_key_release_event), MS_Seconds)); + + hours_ebox.signal_focus_in_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_in_event), SMPTE_Hours)); + minutes_ebox.signal_focus_in_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_in_event), SMPTE_Minutes)); + seconds_ebox.signal_focus_in_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_in_event), SMPTE_Seconds)); + frames_ebox.signal_focus_in_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_in_event), SMPTE_Frames)); + audio_frames_ebox.signal_focus_in_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_in_event), VFrames)); + ms_hours_ebox.signal_focus_in_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_in_event), MS_Hours)); + ms_minutes_ebox.signal_focus_in_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_in_event), MS_Minutes)); + ms_seconds_ebox.signal_focus_in_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_in_event), MS_Seconds)); + + hours_ebox.signal_focus_out_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_out_event), SMPTE_Hours)); + minutes_ebox.signal_focus_out_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_out_event), SMPTE_Minutes)); + seconds_ebox.signal_focus_out_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_out_event), SMPTE_Seconds)); + frames_ebox.signal_focus_out_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_out_event), SMPTE_Frames)); + audio_frames_ebox.signal_focus_out_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_out_event), VFrames)); + ms_hours_ebox.signal_focus_out_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_out_event), MS_Hours)); + ms_minutes_ebox.signal_focus_out_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_out_event), MS_Minutes)); + ms_seconds_ebox.signal_focus_out_event().connect(bind(mem_fun( + *this, &TimeCode::field_focus_out_event), MS_Seconds)); + + clock_base.signal_focus_in_event().connect(mem_fun( + *this, &TimeCode::drop_focus_handler)); +} + +bool +TimeCode::drop_focus_handler(GdkEventFocus* ignored) +{ + // Keyboard::magic_widget_drop_focus(); + return false; +} + +void +TimeCode::on_realize() +{ + HBox::on_realize(); + + /* styles are not available until the widgets are bound to a window */ + + set_size_requests(); +} + +void +TimeCode::set(gavl_time_t when, bool force) +{ + if (!force && when == last_when) + return; + + switch (_mode) { + case SMPTE: + set_smpte(when, force); + break; + + case MinSec: + set_minsec(when, force); + break; + + case Frames: + set_frames(when, force); + break; + + case Off: + break; + } + + last_when = when; +} + +// void +// TimeCode::smpte_offset_changed() +// { +// gavl_time_t current; + +// switch (_mode) { +// case SMPTE: +// // if(is_duration) { +// // current = current_duration(); +// // } else { +// current = current_time(); +// // } +// set(current, true); +// break; +// default: +// break; +// } +// } + +void +TimeCode::set_frames(gavl_time_t when, bool force) +{ + char buf[32]; + snprintf(buf, sizeof(buf), "%u", (unsigned int)when); + audio_frames_label.set_text(buf); +} + +void +TimeCode::set_minsec(gavl_time_t when, bool force) +{ + char buf[32]; + + lumiera::Time t(when); + + int hrs = t.getHours(); + int mins = t.getMins(); + float secs = t.getSecs(); + + if (force || hrs != ms_last_hrs) + { + sprintf(buf, "%02d", hrs); + ms_hours_label.set_text(buf); + ms_last_hrs = hrs; + } + + if (force || mins != ms_last_mins) + { + sprintf(buf, "%02d", mins); + ms_minutes_label.set_text(buf); + ms_last_mins = mins; + } + + if (force || secs != ms_last_secs) + { + sprintf(buf, "%06.3f", secs); + ms_seconds_label.set_text(buf); + ms_last_secs = secs; + } +} + +void +TimeCode::set_smpte(gavl_time_t when, bool force) +{ + char buf[32]; + + lumiera::Time t(when); + + int smpte_negative = when < 0; + int smpte_hours = t.getHours(); + int smpte_minutes = t.getMins(); + int smpte_seconds = t.getSecs(); + int smpte_frames = 0; //t.getFrames(framerate); + + // if (is_duration) { + // session->smpte_duration(when, smpte); + // } else { + // session->smpte_time(when, smpte); + // } + + if (force || smpte_hours != last_hrs || smpte_negative != last_negative) + { + if (smpte_negative) + { + sprintf(buf, "-%02d", smpte_hours); + } + else + { + sprintf(buf, " %02d", smpte_hours); + } + hours_label.set_text(buf); + last_hrs = smpte_hours; + last_negative = smpte_negative; + } + + if (force || smpte_minutes != last_mins) + { + sprintf(buf, "%02d", smpte_minutes); + minutes_label.set_text(buf); + last_mins = smpte_minutes; + } + + if (force || smpte_seconds != last_secs) + { + sprintf(buf, "%02d", smpte_seconds); + seconds_label.set_text(buf); + last_secs = smpte_seconds; + } + + if (force || smpte_frames != last_frames) + { + sprintf(buf, "%02d", smpte_frames); + frames_label.set_text(buf); + last_frames = smpte_frames; + } +} + +void +TimeCode::focus() +{ + switch (_mode) { + case SMPTE: + hours_ebox.grab_focus(); + break; + + case MinSec: + ms_hours_ebox.grab_focus(); + break; + + case Frames: + frames_ebox.grab_focus(); + break; + + case Off: + break; + } +} + +bool +TimeCode::field_key_press_event(GdkEventKey *ev, Field field) +{ + /* all key activity is handled on key release */ + return true; +} + +bool +TimeCode::field_key_release_event(GdkEventKey *ev, Field field) +{ + Label *label = 0; + string new_text; + char new_char = 0; + bool move_on = false; + + switch (field) { + case SMPTE_Hours: + label = &hours_label; + break; + case SMPTE_Minutes: + label = &minutes_label; + break; + case SMPTE_Seconds: + label = &seconds_label; + break; + case SMPTE_Frames: + label = &frames_label; + break; + + case VFrames: + label = &audio_frames_label; + break; + + case MS_Hours: + label = &ms_hours_label; + break; + case MS_Minutes: + label = &ms_minutes_label; + break; + case MS_Seconds: + label = &ms_seconds_label; + break; + + default: + return false; + } + + switch (ev->keyval) { + case GDK_0: + case GDK_KP_0: + new_char = '0'; + break; + case GDK_1: + case GDK_KP_1: + new_char = '1'; + break; + case GDK_2: + case GDK_KP_2: + new_char = '2'; + break; + case GDK_3: + case GDK_KP_3: + new_char = '3'; + break; + case GDK_4: + case GDK_KP_4: + new_char = '4'; + break; + case GDK_5: + case GDK_KP_5: + new_char = '5'; + break; + case GDK_6: + case GDK_KP_6: + new_char = '6'; + break; + case GDK_7: + case GDK_KP_7: + new_char = '7'; + break; + case GDK_8: + case GDK_KP_8: + new_char = '8'; + break; + case GDK_9: + case GDK_KP_9: + new_char = '9'; + break; + + case GDK_period: + case GDK_KP_Decimal: + if (_mode == MinSec && field == MS_Seconds) { + new_char = '.'; + } else { + return false; + } + break; + + case GDK_Tab: + case GDK_Return: + case GDK_KP_Enter: + move_on = true; + break; + + case GDK_Escape: + key_entry_state = 0; + clock_base.grab_focus(); + ChangeAborted(); /* EMIT SIGNAL */ + return true; + + default: + return false; + } + + if (!move_on) { + + if (key_entry_state == 0) { + + /* initialize with a fresh new string */ + + if (field != VFrames) { + for (unsigned int xn = 0; xn < field_length[field] - 1; ++xn) { + new_text += '0'; + } + } else { + new_text = ""; + } + + } else { + + string existing = label->get_text(); + if (existing.length() >= field_length[field]) { + new_text = existing.substr(1, field_length[field] - 1); + } else { + new_text = existing.substr(0, field_length[field] - 1); + } + } + + new_text += new_char; + label->set_text(new_text); + key_entry_state++; + } + + if (key_entry_state == field_length[field]) { + move_on = true; + } + + if (move_on) { + + if (key_entry_state) { + + switch (field) { + case SMPTE_Hours: + case SMPTE_Minutes: + case SMPTE_Seconds: + case SMPTE_Frames: + // Check SMPTE fields for sanity (may also adjust fields) + smpte_sanitize_display(); + break; + default: + break; + } + + ValueChanged(); /* EMIT_SIGNAL */ + } + + /* move on to the next field. + */ + + switch (field) { + + /* SMPTE */ + + case SMPTE_Hours: + minutes_ebox.grab_focus(); + break; + case SMPTE_Minutes: + seconds_ebox.grab_focus(); + break; + case SMPTE_Seconds: + frames_ebox.grab_focus(); + break; + case SMPTE_Frames: + clock_base.grab_focus(); + break; + + /* frames */ + + case VFrames: + clock_base.grab_focus(); + break; + + /* Min:Sec */ + + case MS_Hours: + ms_minutes_ebox.grab_focus(); + break; + case MS_Minutes: + ms_seconds_ebox.grab_focus(); + break; + case MS_Seconds: + clock_base.grab_focus(); + break; + + default: + break; + } + + } + + //if user hit Enter, lose focus + switch (ev->keyval) { + case GDK_Return: + case GDK_KP_Enter: + clock_base.grab_focus(); + } + + return true; +} + +bool +TimeCode::field_focus_in_event(GdkEventFocus *ev, Field field) +{ + key_entry_state = 0; + + // Keyboard::magic_widget_grab_focus(); + + switch (field) { + case SMPTE_Hours: + hours_ebox.set_flags(Gtk::HAS_FOCUS); + hours_ebox.set_state(Gtk::STATE_ACTIVE); + break; + case SMPTE_Minutes: + minutes_ebox.set_flags(Gtk::HAS_FOCUS); + minutes_ebox.set_state(Gtk::STATE_ACTIVE); + break; + case SMPTE_Seconds: + seconds_ebox.set_flags(Gtk::HAS_FOCUS); + seconds_ebox.set_state(Gtk::STATE_ACTIVE); + break; + case SMPTE_Frames: + frames_ebox.set_flags(Gtk::HAS_FOCUS); + frames_ebox.set_state(Gtk::STATE_ACTIVE); + break; + + case VFrames: + audio_frames_ebox.set_flags(Gtk::HAS_FOCUS); + audio_frames_ebox.set_state(Gtk::STATE_ACTIVE); + break; + + case MS_Hours: + ms_hours_ebox.set_flags(Gtk::HAS_FOCUS); + ms_hours_ebox.set_state(Gtk::STATE_ACTIVE); + break; + case MS_Minutes: + ms_minutes_ebox.set_flags(Gtk::HAS_FOCUS); + ms_minutes_ebox.set_state(Gtk::STATE_ACTIVE); + break; + case MS_Seconds: + ms_seconds_ebox.set_flags(Gtk::HAS_FOCUS); + ms_seconds_ebox.set_state(Gtk::STATE_ACTIVE); + break; + } + + return false; +} + +bool +TimeCode::field_focus_out_event(GdkEventFocus *ev, Field field) +{ + switch (field) { + + case SMPTE_Hours: + hours_ebox.unset_flags(Gtk::HAS_FOCUS); + hours_ebox.set_state(Gtk::STATE_NORMAL); + break; + case SMPTE_Minutes: + minutes_ebox.unset_flags(Gtk::HAS_FOCUS); + minutes_ebox.set_state(Gtk::STATE_NORMAL); + break; + case SMPTE_Seconds: + seconds_ebox.unset_flags(Gtk::HAS_FOCUS); + seconds_ebox.set_state(Gtk::STATE_NORMAL); + break; + case SMPTE_Frames: + frames_ebox.unset_flags(Gtk::HAS_FOCUS); + frames_ebox.set_state(Gtk::STATE_NORMAL); + break; + + case VFrames: + audio_frames_ebox.unset_flags(Gtk::HAS_FOCUS); + audio_frames_ebox.set_state(Gtk::STATE_NORMAL); + break; + + case MS_Hours: + ms_hours_ebox.unset_flags(Gtk::HAS_FOCUS); + ms_hours_ebox.set_state(Gtk::STATE_NORMAL); + break; + case MS_Minutes: + ms_minutes_ebox.unset_flags(Gtk::HAS_FOCUS); + ms_minutes_ebox.set_state(Gtk::STATE_NORMAL); + break; + case MS_Seconds: + ms_seconds_ebox.unset_flags(Gtk::HAS_FOCUS); + ms_seconds_ebox.set_state(Gtk::STATE_NORMAL); + break; + } + + // Keyboard::magic_widget_drop_focus(); + + return false; +} + +bool +TimeCode::field_button_release_event (GdkEventButton *ev, Field field) +{ + if (dragging) + { + gdk_pointer_ungrab(GDK_CURRENT_TIME); + dragging = false; + if (ev->y > drag_start_y+1 || ev->y < drag_start_y-1 + || (ev->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) + { + // we actually dragged so return without setting editing focus, or we shift clicked + return true; + } + } + + if (!editable) + { + if (ops_menu == 0) { + build_ops_menu(); + } + ops_menu->popup(1, ev->time); + return true; + } + + switch (ev->button) + { + case 1: + switch (field) + { + case SMPTE_Hours: + hours_ebox.grab_focus(); + break; + case SMPTE_Minutes: + minutes_ebox.grab_focus(); + break; + case SMPTE_Seconds: + seconds_ebox.grab_focus(); + break; + case SMPTE_Frames: + frames_ebox.grab_focus(); + break; + + case VFrames: + audio_frames_ebox.grab_focus(); + break; + + case MS_Hours: + ms_hours_ebox.grab_focus(); + break; + case MS_Minutes: + ms_minutes_ebox.grab_focus(); + break; + case MS_Seconds: + ms_seconds_ebox.grab_focus(); + break; + } + break; + + case 3: + if (ops_menu == 0) { + build_ops_menu(); + } + ops_menu->popup(1, ev->time); + return true; + + default: + break; + } + + return true; +} + +bool +TimeCode::field_button_press_event(GdkEventButton *ev, Field field) +{ + return false; + // if (session == 0) return false; + + // gavl_time_t frames = 0; + + switch (ev->button) { + case 1: + // if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { + // set (frames, true); + // ValueChanged (); /* EMIT_SIGNAL */ + // } + + /* make absolutely sure that the pointer is grabbed */ + gdk_pointer_grab(ev->window,false , + GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK), + NULL,NULL,ev->time); + dragging = true; + drag_accum = 0; + drag_start_y = ev->y; + drag_y = ev->y; + break; + + case 2: + // if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { + // set (frames, true); + // ValueChanged (); /* EMIT_SIGNAL */ + // } + break; + + case 3: + /* used for context sensitive menu */ + return false; + break; + + default: + return false; + break; + } + + return true; +} + +bool +TimeCode::field_button_scroll_event (GdkEventScroll *ev, Field field) +{ + return false; + + // if (session == 0) { + // return false; + // } + + // gavl_time_t frames = 0; + + switch (ev->direction) { + + case GDK_SCROLL_UP: + // frames = get_frames (field); + // if (frames != 0) { + // // if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + // // frames *= 10; + // // } + // set (current_time() + frames, true); + // ValueChanged (); /* EMIT_SIGNAL */ + // } + break; + + case GDK_SCROLL_DOWN: + // frames = get_frames (field); + // if (frames != 0) { + // // if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + // // frames *= 10; + // // } + + // if ((double)current_time() - (double)frames < 0.0) { + // set (0, true); + // } else { + // set (current_time() - frames, true); + // } + + // ValueChanged (); /* EMIT_SIGNAL */ + // } + break; + + default: + return false; + break; + } + + return true; +} + +bool +TimeCode::field_motion_notify_event (GdkEventMotion *ev, Field field) +{ + if (!dragging) + { + return false; + } + + float pixel_frame_scale_factor = 0.2f; + +/* + if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + pixel_frame_scale_factor = 0.1f; + } + + if (Keyboard::modifier_state_contains (ev->state, + Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) { + + pixel_frame_scale_factor = 0.025f; + } +*/ + double y_delta = ev->y - drag_y; + + drag_accum += y_delta*pixel_frame_scale_factor; + + drag_y = ev->y; + + if (trunc(drag_accum) != 0) + { + gavl_time_t frames; + gavl_time_t pos ; + int dir; + dir = (drag_accum < 0 ? 1:-1); + pos = current_time(); + frames = get_frames(field,pos,dir); + + if (frames != 0 && frames * drag_accum < current_time()) + { + // minus because up is negative in computer-land + set ((gavl_time_t) floor (pos - drag_accum * frames), false); + } + else + { + set (0 , false); + } + + drag_accum = 0; + ValueChanged(); /* EMIT_SIGNAL */ + } + + return true; +} + +gavl_time_t +TimeCode::get_frames(Field field, gavl_time_t pos, int dir) +{ + gavl_time_t frames = 0; + // switch (field) + // { + // case SMPTE_Hours: + // frames = (gavl_time_t) floor (3600.0 * session->frame_rate()); + // break; + // case SMPTE_Minutes: + // frames = (gavl_time_t) floor (60.0 * session->frame_rate()); + // break; + // case SMPTE_Seconds: + // frames = session->frame_rate(); + // break; + // case SMPTE_Frames: + // frames = (gavl_time_t) floor (session->frame_rate() / session->smpte_frames_per_second()); + // break; + + // case VFrames: + // frames = 1; + // break; + + // case MS_Hours: + // frames = (gavl_time_t) floor (3600.0 * session->frame_rate()); + // break; + // case MS_Minutes: + // frames = (gavl_time_t) floor (60.0 * session->frame_rate()); + // break; + // case MS_Seconds: + // frames = session->frame_rate(); + // break; + // } + + return frames; +} + +gavl_time_t +TimeCode::current_time(gavl_time_t pos) const +{ + gavl_time_t ret = 0; + + switch (_mode) + { + case SMPTE: + ret = smpte_time_from_display(); + break; + + case MinSec: + ret = minsec_time_from_display(); + break; + + case Frames: + ret = audio_time_from_display(); + break; + + case Off: + break; + } + + return ret; +} + +gavl_time_t +TimeCode::current_duration(gavl_time_t pos) const +{ + gavl_time_t ret = 0; + + switch (_mode) + { + case SMPTE: + ret = smpte_time_from_display(); + break; + + case MinSec: + ret = minsec_time_from_display(); + break; + + case Frames: + ret = audio_time_from_display(); + break; + + case Off: + break; + } + + return ret; +} + +void +TimeCode::smpte_sanitize_display() +{ + // Check SMPTE fields for sanity, possibly adjusting values + if (atoi(minutes_label.get_text()) > 59) + { + minutes_label.set_text("59"); + } + + if (atoi(seconds_label.get_text()) > 59) + { + seconds_label.set_text("59"); + } + + if (atoi(frames_label.get_text()) > framerate - 1) + { + char buf[32]; + sprintf(buf, "%02d", int(framerate - 1)); + frames_label.set_text(buf); + } + + // TODO: Drop frames + + // if (session->smpte_drop_frames()) { + // if ((atoi(minutes_label.get_text()) % 10) && (atoi(seconds_label.get_text()) == 0) && (atoi(frames_label.get_text()) < 2)) { + // frames_label.set_text("02"); + // } + // } +} + +gavl_time_t +TimeCode::smpte_time_from_display() const +{ + // TODO + + // SMPTE::Time smpte; + // gavl_time_t sample; + + // smpte.hours = atoi (hours_label.get_text()); + // smpte.minutes = atoi (minutes_label.get_text()); + // smpte.seconds = atoi (seconds_label.get_text()); + // smpte.frames = atoi (frames_label.get_text()); + // smpte.rate = session->smpte_frames_per_second(); + // smpte.drop= session->smpte_drop_frames(); + + // session->smpte_to_sample(smpte, sample, false /* use_offset */, false /* use_subframes */ ); + + return 0; +} + +gavl_time_t +TimeCode::minsec_time_from_display () const +{ + // TODO + + // int hrs = atoi (ms_hours_label.get_text()); + // int mins = atoi (ms_minutes_label.get_text()); + // float secs = atof (ms_seconds_label.get_text()); + + // gavl_time_t sr = session->frame_rate(); + + // return (gavl_time_t) floor ((hrs * 60.0f * 60.0f * sr) + (mins * 60.0f * sr) + (secs * sr)); + + return 0; +} + +gavl_time_t +TimeCode::audio_time_from_display () const +{ + return (gavl_time_t) atoi (audio_frames_label.get_text()); +} + +void +TimeCode::build_ops_menu () +{ + using namespace Menu_Helpers; + ops_menu = new Menu; + MenuList& ops_items = ops_menu->items(); + ops_menu->set_name ("LumieraContextMenu"); + + ops_items.push_back (MenuElem ("SMPTE", bind (mem_fun(*this, &TimeCode::set_mode), SMPTE))); + ops_items.push_back (MenuElem ("Minutes:Seconds", bind (mem_fun(*this, &TimeCode::set_mode), MinSec))); + ops_items.push_back (MenuElem ("Frames", bind (mem_fun(*this, &TimeCode::set_mode), Frames))); + ops_items.push_back (MenuElem ("Off", bind (mem_fun(*this, &TimeCode::set_mode), Off))); +} + +void +TimeCode::set_mode(Mode m) +{ + /* slightly tricky: this is called from within the ARDOUR_UI + constructor by some of its clock members. at that time + the instance pointer is unset, so we have to be careful. + the main idea is to drop keyboard focus in case we had + started editing the clock and then we switch clock mode. + */ + + clock_base.grab_focus(); + + if (_mode == m) + return; + + clock_base.remove(); + + _mode = m; + + switch (_mode) + { + case SMPTE: + clock_base.add(smpte_packer_hbox); + break; + + case MinSec: + clock_base.add(minsec_packer_hbox); + break; + + case Frames: + clock_base.add(frames_packer_hbox); + break; + + case Off: + clock_base.add(off_hbox); + break; + } + + set_size_requests(); + + set(last_when, true); + clock_base.show_all(); + key_entry_state = 0; + + ModeChanged(); /* EMIT SIGNAL */ +} + +void +TimeCode::set_size_requests() +{ + /* note that in some fonts, "88" is narrower than "00", hence the 2 pixel padding */ + + switch (_mode) { + case SMPTE: + set_size_request_to_display_given_text(hours_label, "-00", 5, 5); + set_size_request_to_display_given_text(minutes_label, "00", 5, 5); + set_size_request_to_display_given_text(seconds_label, "00", 5, 5); + set_size_request_to_display_given_text(frames_label, "00", 5, 5); + break; + + case MinSec: + set_size_request_to_display_given_text(ms_hours_label, "00", 5, 5); + set_size_request_to_display_given_text(ms_minutes_label, "00", 5, 5); + set_size_request_to_display_given_text(ms_seconds_label, "00.000", 5, 5); + break; + + case Frames: + set_size_request_to_display_given_text(audio_frames_label, "0000000000", 5, 5); + break; + + case Off: + set_size_request_to_display_given_text(off_hbox, "00000", 5, 5); + break; + + } +} + +void +TimeCode::set_size_request_to_display_given_text(Gtk::Widget &w, const gchar *text, + gint hpadding, gint vpadding) +{ + int width, height; + w.ensure_style(); + + get_ink_pixel_size(w.create_pango_layout(text), width, height); + w.set_size_request(width + hpadding, height + vpadding); +} + +void +TimeCode::get_ink_pixel_size (Glib::RefPtr layout, + int& width, + int& height) +{ + Pango::Rectangle ink_rect = layout->get_ink_extents (); + + width = (ink_rect.get_width() + PANGO_SCALE / 2) / PANGO_SCALE; + height = (ink_rect.get_height() + PANGO_SCALE / 2) / PANGO_SCALE; +} + +} // namespace widgets +} // namespace gui + diff --git a/src/gui/widgets/timecode-widget.hpp b/src/gui/widgets/timecode-widget.hpp new file mode 100644 index 000000000..cb63060ed --- /dev/null +++ b/src/gui/widgets/timecode-widget.hpp @@ -0,0 +1,192 @@ +/* + timecode-widget.hpp - Declaration of the timecode widget + + Copyright (C) 1999 Paul Davis + + Copyright (C) Lumiera.org + 2010, Stefan Kangas + +#include +#include +#include +#include +#include + +namespace gui { +namespace widgets { + +class TimeCode : public Gtk::HBox +{ +public: + enum Mode { + SMPTE, + MinSec, + Frames, + Off + }; + + TimeCode(std::string clock_name, std::string widget_name, bool editable /*, bool is_duration = false*/); + + Mode mode() const { return _mode; } + + void focus(); + + void set(gavl_time_t, bool force = false); + void set_mode(Mode); + + void set_widget_name(std::string); + + std::string name() const { return _name; } + + gavl_time_t current_time(gavl_time_t position = 0) const; + gavl_time_t current_duration(gavl_time_t position = 0) const; + + sigc::signal ValueChanged; + sigc::signal ChangeAborted; + + static sigc::signal ModeChanged; + // static std::vector clocks; + + static bool has_focus() { return _has_focus; } + +private: + Mode _mode; + unsigned int key_entry_state; + std::string _name; + // bool is_duration; + bool editable; + + Gtk::Menu *ops_menu; + + Gtk::HBox smpte_packer_hbox; + Gtk::HBox smpte_packer; + + Gtk::HBox minsec_packer_hbox; + Gtk::HBox minsec_packer; + + Gtk::HBox frames_packer_hbox; + Gtk::HBox frames_packer; + + enum Field { + SMPTE_Hours, + SMPTE_Minutes, + SMPTE_Seconds, + SMPTE_Frames, + MS_Hours, + MS_Minutes, + MS_Seconds, + VFrames + }; + + Gtk::EventBox audio_frames_ebox; + Gtk::Label audio_frames_label; + + Gtk::HBox off_hbox; + + Gtk::EventBox hours_ebox; + Gtk::EventBox minutes_ebox; + Gtk::EventBox seconds_ebox; + Gtk::EventBox frames_ebox; + + Gtk::EventBox ms_hours_ebox; + Gtk::EventBox ms_minutes_ebox; + Gtk::EventBox ms_seconds_ebox; + + Gtk::Label hours_label; + Gtk::Label minutes_label; + Gtk::Label seconds_label; + Gtk::Label frames_label; + Gtk::Label colon1, colon2, colon3; + + Gtk::Label ms_hours_label; + Gtk::Label ms_minutes_label; + Gtk::Label ms_seconds_label; + Gtk::Label colon4, colon5; + + Gtk::EventBox clock_base; + Gtk::Frame clock_frame; + + gavl_time_t last_when; + bool last_pdelta; + bool last_sdelta; + + int last_hrs; + int last_mins; + int last_secs; + int last_frames; + bool last_negative; + + int ms_last_hrs; + int ms_last_mins; + float ms_last_secs; + + bool dragging; + double drag_start_y; + double drag_y; + double drag_accum; + + void on_realize(); + + bool field_motion_notify_event(GdkEventMotion *ev, Field); + bool field_button_press_event(GdkEventButton *ev, Field); + bool field_button_release_event(GdkEventButton *ev, Field); + bool field_button_scroll_event(GdkEventScroll *ev, Field); + bool field_key_press_event(GdkEventKey *, Field); + bool field_key_release_event(GdkEventKey *, Field); + bool field_focus_in_event(GdkEventFocus *, Field); + bool field_focus_out_event(GdkEventFocus *, Field); + bool drop_focus_handler(GdkEventFocus*); + + void set_smpte(gavl_time_t, bool); + void set_minsec(gavl_time_t, bool); + void set_frames(gavl_time_t, bool); + + gavl_time_t get_frames(Field, gavl_time_t pos = 0, int dir=1); + + void smpte_sanitize_display(); + gavl_time_t smpte_time_from_display() const; + gavl_time_t minsec_time_from_display() const; + gavl_time_t audio_time_from_display() const; + + void build_ops_menu(); + void setup_events(); + + void smpte_offset_changed(); + void set_size_requests(); + + static const unsigned int field_length[(int)VFrames+1]; + static bool _has_focus; + + void set_size_request_to_display_given_text(Gtk::Widget &w, const gchar *text, + gint hpadding, gint vpadding); + + void get_ink_pixel_size(Glib::RefPtr layout, + int& width, int& height); + +}; + +} // namespace widgets +} // namespace gui + +#endif // TIMECODE_WIDGET_HPP