followup to the previous tempo-dragging commit: use argument to Drag::aborted() to...
[ardour.git] / gtk2_ardour / audio_clock.cc
index 3dbdfa859811d0c4776d686b19387bc4db6783ee..7d37f9cea8c9fc2234ac2b8db89bd9c40bdee1a1 100644 (file)
@@ -57,6 +57,9 @@ const double AudioClock::info_font_scale_factor = 0.6;
 const double AudioClock::separator_height = 2.0;
 const double AudioClock::x_leading_padding = 6.0;
 
+#define BBT_BAR_CHAR "|"
+#define BBT_SCANF_FORMAT "%" PRIu32 "%*c%" PRIu32 "%*c%" PRIu32
+
 AudioClock::AudioClock (const string& clock_name, bool transient, const string& widget_name,
                        bool allow_edit, bool follows_playhead, bool duration, bool with_info)
        : _name (clock_name)
@@ -65,10 +68,21 @@ AudioClock::AudioClock (const string& clock_name, bool transient, const string&
        , editable (allow_edit)
        , _follows_playhead (follows_playhead)
        , _off (false)
+       , _fixed_width (true)
+       , layout_x_offset (0)
+       , em_width (0)
+       , _edit_by_click_field (false)
        , ops_menu (0)
        , editing_attr (0)
        , foreground_attr (0)
+       , first_height (0)
+       , first_width (0)
+       , layout_height (0)
+       , layout_width (0)
+       , info_height (0)
+       , upper_height (0)
        , mode_based_info_ratio (1.0)
+       , corner_radius (9)
        , editing (false)
        , bbt_reference_time (-1)
        , last_when(0)
@@ -99,6 +113,7 @@ AudioClock::AudioClock (const string& clock_name, bool transient, const string&
        }
 
        ColorsChanged.connect (sigc::mem_fun (*this, &AudioClock::set_colors));
+       DPIReset.connect (sigc::mem_fun (*this, &AudioClock::dpi_reset));
 }
 
 AudioClock::~AudioClock ()
@@ -158,6 +173,17 @@ AudioClock::set_font ()
        info_attributes.change (*font_attr);
        
        delete font_attr;
+
+       /* get the figure width for the font. This doesn't have to super
+        * accurate since we only use it to measure the (roughly 1 character)
+        * offset from the position Pango tells us for the "cursor"
+        */
+
+       Glib::RefPtr<Pango::Layout> tmp = Pango::Layout::create (get_pango_context());
+       int ignore_height;
+
+       tmp->set_text ("8");
+       tmp->get_pixel_size (em_width, ignore_height);
 }
 
 void
@@ -175,38 +201,52 @@ AudioClock::set_colors ()
        uint32_t bg_color;
        uint32_t text_color;
        uint32_t editing_color;
+       uint32_t cursor_color;
 
        if (active_state()) {
                bg_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: background", get_name()));
                text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: text", get_name()));
                editing_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: edited text", get_name()));
+               cursor_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: cursor", get_name()));
        } else {
                bg_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: background", get_name()));
                text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: text", get_name()));
                editing_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: edited text", get_name()));
+               cursor_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: cursor", get_name()));
        }
 
-       /* store for bg in render() */
+       /* store for bg and cursor in render() */
 
        UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
-       r = lrint ((r/256.0) * 65535.0);
-       g = lrint ((g/256.0) * 65535.0);
-       b = lrint ((b/256.0) * 65535.0);
-       bg_r = r/256.0;
-       bg_g = g/256.0;
-       bg_b = b/256.0;
-       bg_a = a/256.0;
+
+       bg_r = r/255.0;
+       bg_g = g/255.0;
+       bg_b = b/255.0;
+       bg_a = a/255.0;
+
+       UINT_TO_RGBA (cursor_color, &r, &g, &b, &a);
+
+       cursor_r = r/255.0;
+       cursor_g = g/255.0;
+       cursor_b = b/255.0;
+       cursor_a = a/255.0;
+
+       /* rescale for Pango colors ... sigh */
+
+       r = lrint (r * 65535.0);
+       g = lrint (g * 65535.0);
+       b = lrint (b * 65535.0);
 
        UINT_TO_RGBA (text_color, &r, &g, &b, &a);
-       r = lrint ((r/256.0) * 65535.0);
-       g = lrint ((g/256.0) * 65535.0);
-       b = lrint ((b/256.0) * 65535.0);
+       r = lrint ((r/255.0) * 65535.0);
+       g = lrint ((g/255.0) * 65535.0);
+       b = lrint ((b/255.0) * 65535.0);
        foreground_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
 
        UINT_TO_RGBA (editing_color, &r, &g, &b, &a);
-       r = lrint ((r/256.0) * 65535.0);
-       g = lrint ((g/256.0) * 65535.0);
-       b = lrint ((b/256.0) * 65535.0);
+       r = lrint ((r/255.0) * 65535.0);
+       g = lrint ((g/255.0) * 65535.0);
+       b = lrint ((b/255.0) * 65535.0);
        editing_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
        
        normal_attributes.change (*foreground_attr);
@@ -235,11 +275,20 @@ AudioClock::render (cairo_t* cr)
        
        if (_need_bg) {
                cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
-               Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), upper_height, 9);
+               if (corner_radius) {
+                       Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), upper_height, corner_radius);
+               } else {
+                       cairo_rectangle (cr, 0, 0, get_width(), upper_height);
+               }
                cairo_fill (cr);
        }
 
-       cairo_move_to (cr, x_leading_padding, (upper_height - layout_height) / 2.0);
+       if (!_fixed_width) {
+               cairo_move_to (cr, layout_x_offset, 0);
+       } else {
+               cairo_move_to (cr, layout_x_offset, (upper_height - layout_height) / 2.0);
+       }
+
        pango_cairo_show_layout (cr, _layout->gobj());
 
        if (_left_layout) {
@@ -255,7 +304,11 @@ AudioClock::render (cairo_t* cr)
                        double left_rect_width = round (((get_width() - separator_height) * mode_based_info_ratio) + 0.5);
 
                        if (_need_bg) {
-                               Gtkmm2ext::rounded_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h, 9);
+                               if (corner_radius) {
+                                       Gtkmm2ext::rounded_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h, corner_radius);
+                               } else {
+                                       cairo_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h);
+                               }
                                cairo_fill (cr);
                        }
 
@@ -263,8 +316,14 @@ AudioClock::render (cairo_t* cr)
                        pango_cairo_show_layout (cr, _left_layout->gobj());
                        
                        if (_need_bg) {
-                               Gtkmm2ext::rounded_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height, 
-                                                             get_width() - separator_height - left_rect_width, h, 9);
+                               if (corner_radius) {
+                                       Gtkmm2ext::rounded_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height, 
+                                                                     get_width() - separator_height - left_rect_width, h, 
+                                                                     corner_radius);
+                               } else {
+                                       cairo_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height, 
+                                                        get_width() - separator_height - left_rect_width, h);
+                               }
                                cairo_fill (cr);        
                        }
 
@@ -275,31 +334,64 @@ AudioClock::render (cairo_t* cr)
                        /* no info to display, or just one */
 
                        if (_need_bg) {
-                               Gtkmm2ext::rounded_rectangle (cr, 0, upper_height + separator_height, get_width(), h, 9);
+                               if (corner_radius) {
+                                       Gtkmm2ext::rounded_rectangle (cr, 0, upper_height + separator_height, get_width(), h, corner_radius);
+                               } else {
+                                       cairo_rectangle (cr, 0, upper_height + separator_height, get_width(), h);
+                               }
                                cairo_fill (cr);
                        }
                }
        }
 
        if (editing) {
-               const double cursor_width = 12; /* need em width here, not 16 */
-
                if (!insert_map.empty()) {
-                       Pango::Rectangle cursor = _layout->get_cursor_strong_pos (insert_map[input_string.length()]);
-                       
-                       cairo_set_source_rgba (cr, 0.9, 0.1, 0.1, 0.8);
-                       cairo_rectangle (cr, 
-                                        x_leading_padding + cursor.get_x()/PANGO_SCALE + cursor_width,
-                                        (upper_height - layout_height)/2.0, 
-                                        2.0, cursor.get_height()/PANGO_SCALE);
-                       cairo_fill (cr);        
+
+
+                       if (input_string.length() < insert_map.size()) {
+                               Pango::Rectangle cursor;
+                               
+                               if (input_string.empty()) {
+                                       /* nothing entered yet, put cursor at the end
+                                          of string
+                                       */
+                                       cursor = _layout->get_cursor_strong_pos (edit_string.length() - 1);
+                               } else {
+                                       cursor = _layout->get_cursor_strong_pos (insert_map[input_string.length()]);
+                               }
+                               
+                               cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
+                               if (!_fixed_width) {
+                                       cairo_rectangle (cr, 
+                                                        min (get_width() - 2.0, 
+                                                             (double) cursor.get_x()/PANGO_SCALE + layout_x_offset + em_width), 0,
+                                                        2.0, cursor.get_height()/PANGO_SCALE);
+                               } else {
+                                       cairo_rectangle (cr, 
+                                                        min (get_width() - 2.0, 
+                                                             (double) layout_x_offset + cursor.get_x()/PANGO_SCALE + em_width),
+                                                        (upper_height - layout_height)/2.0, 
+                                                        2.0, cursor.get_height()/PANGO_SCALE);
+                               }
+                               cairo_fill (cr);        
+                       } else {
+                               /* we've entered all possible digits, no cursor */
+                       }
+
                } else {
                        if (input_string.empty()) {
-                               cairo_set_source_rgba (cr, 0.9, 0.1, 0.1, 0.8);
-                               cairo_rectangle (cr, 
-                                                (get_width()/2.0),
-                                                (upper_height - layout_height)/2.0, 
-                                                2.0, upper_height);
+                               cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
+                               if (!_fixed_width) {
+                                       cairo_rectangle (cr, 
+                                                        (get_width()/2.0),
+                                                        0,
+                                                        2.0, upper_height);
+                               } else {
+                                       cairo_rectangle (cr, 
+                                                        (get_width()/2.0),
+                                                        (upper_height - layout_height)/2.0, 
+                                                        2.0, upper_height);
+                               }
                                cairo_fill (cr);
                        }
                }
@@ -316,11 +408,30 @@ AudioClock::on_size_allocate (Gtk::Allocation& alloc)
        } else {
                upper_height = get_height();
        }
+
+       if (_fixed_width) {
+               /* center display in available space */
+               layout_x_offset = (get_width() - layout_width)/2.0;
+       } else {
+               /* left justify */
+               layout_x_offset = 0;
+       }
 }
 
 void
 AudioClock::on_size_request (Gtk::Requisition* req)
 {
+       /* even for non fixed width clocks, the size we *ask* for never changes, 
+          even though the size we receive might. so once we've computed it,
+          just return it.
+       */
+       
+       if (first_width) {
+               req->width = first_width;
+               req->height = first_height;
+               return;
+       }
+
        Glib::RefPtr<Pango::Layout> tmp;
        Glib::RefPtr<Gtk::Style> style = get_style ();
        Pango::FontDescription font; 
@@ -335,13 +446,30 @@ AudioClock::on_size_request (Gtk::Requisition* req)
 
        tmp->set_font_description (font);
 
-       /* this string is the longest thing we will ever display,
-          and also includes the BBT "|" that may descends below
-          the baseline a bit, and a comma for the minsecs mode
-          where we printf a fractional value (XXX or should)
-       */
-
-       tmp->set_text (" 88888888888,|"); 
+       if (_fixed_width) {
+               /* this string is the longest thing we will ever display,
+                  and also includes the BBT bar char that may descends below
+                  the baseline a bit, and a comma for the minsecs mode
+                  where we printf a fractional value (XXX or should)
+               */
+               
+               tmp->set_text (" 8888888888:,|"); 
+       } else {
+               switch (_mode) {
+               case Timecode:
+                       tmp->set_text (" 88:88:88:88");
+                       break;
+               case BBT:
+                       tmp->set_text (" 888|88|8888");
+                       break;
+               case MinSec:
+                       tmp->set_text (" 88:88:88,888");
+                       break;
+               case Frames:
+                       tmp->set_text (" 8888888888");
+                       break;
+               }
+       }
 
        tmp->get_pixel_size (req->width, req->height);
 
@@ -376,6 +504,9 @@ AudioClock::on_size_request (Gtk::Requisition* req)
                req->height += info_height;
                req->height += separator_height;
        }
+
+       first_height = req->height;
+       first_width = req->width;
 }
 
 void
@@ -391,7 +522,7 @@ AudioClock::show_edit_status (int length)
 }
 
 void
-AudioClock::start_edit ()
+AudioClock::start_edit (Field f)
 {
        pre_edit_string = _layout->get_text ();
        if (!insert_map.empty()) {
@@ -403,12 +534,62 @@ AudioClock::start_edit ()
        input_string.clear ();
        editing = true;
 
+       if (f) {
+               input_string = get_field (f);
+               show_edit_status (merge_input_and_edit_string ());
+               _layout->set_text (edit_string);
+       }
+
        queue_draw ();
 
        Keyboard::magic_widget_grab_focus ();
        grab_focus ();
 }
 
+string
+AudioClock::get_field (Field f)
+{
+       switch (f) {
+       case Timecode_Hours:
+               return edit_string.substr (1, 2);
+               break;
+       case Timecode_Minutes:
+               return edit_string.substr (4, 2);
+               break;
+       case Timecode_Seconds:
+               return edit_string.substr (7, 2);
+               break;
+       case Timecode_Frames:
+               return edit_string.substr (10, 2);
+               break;
+       case MS_Hours:
+               return edit_string.substr (1, 2);
+               break;
+       case MS_Minutes:
+               return edit_string.substr (4, 2);
+               break;
+       case MS_Seconds:
+               return edit_string.substr (7, 2);
+               break;
+       case MS_Milliseconds:
+               return edit_string.substr (10, 3);
+               break;
+       case Bars:
+               return edit_string.substr (1, 3);
+               break;
+       case Beats:
+               return edit_string.substr (5, 2);
+               break;
+       case Ticks:
+               return edit_string.substr (8, 4);
+               break;
+       case AudioFrames:
+               return edit_string;
+               break;
+       }
+       return "";
+}
+
 void
 AudioClock::end_edit (bool modify)
 {
@@ -426,6 +607,7 @@ AudioClock::end_edit (bool modify)
                        break;
                        
                case MinSec:
+                       ok = minsec_validate_edit (edit_string);
                        break;
                        
                case Frames:
@@ -622,24 +804,18 @@ AudioClock::parse_as_bbt_distance (const std::string& str)
 framecnt_t 
 AudioClock::parse_as_distance (const std::string& instr)
 {
-       string str = instr;
-
-       /* the input string is in reverse order */
-       
-       std::reverse (str.begin(), str.end());
-
        switch (_mode) {
        case Timecode:
-               return parse_as_timecode_distance (str);
+               return parse_as_timecode_distance (instr);
                break;
        case Frames:
-               return parse_as_frames_distance (str);
+               return parse_as_frames_distance (instr);
                break;
        case BBT:
-               return parse_as_bbt_distance (str);
+               return parse_as_bbt_distance (instr);
                break;
        case MinSec:
-               return parse_as_minsec_distance (str);
+               return parse_as_minsec_distance (instr);
                break;
        }
        return 0;
@@ -648,6 +824,35 @@ AudioClock::parse_as_distance (const std::string& instr)
 void
 AudioClock::end_edit_relative (bool add)
 {
+       bool ok = true;
+       
+       switch (_mode) {
+       case Timecode:
+               ok = timecode_validate_edit (edit_string);
+               break;
+               
+       case BBT:
+               ok = bbt_validate_edit (edit_string);
+               break;
+               
+       case MinSec:
+               ok = minsec_validate_edit (edit_string);
+               break;
+               
+       case Frames:
+               break;
+       }
+               
+       if (!ok) {
+               edit_string = pre_edit_string;
+               input_string.clear ();
+               _layout->set_text (edit_string);
+               show_edit_status (0);
+               /* edit attributes remain in use */
+               queue_draw ();
+               return;
+       }
+
        framecnt_t frames = parse_as_distance (input_string);
 
        editing = false;
@@ -943,9 +1148,11 @@ AudioClock::set_bbt (framepos_t when, bool force)
        }
 
        if (negative) {
-               snprintf (buf, sizeof (buf), "-%03" PRIu32 "|%02" PRIu32 "|%04" PRIu32, BBT.bars, BBT.beats, BBT.ticks);
+               snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32, 
+                         BBT.bars, BBT.beats, BBT.ticks);
        } else {
-               snprintf (buf, sizeof (buf), " %03" PRIu32 "|%02" PRIu32 "|%04" PRIu32, BBT.bars, BBT.beats, BBT.ticks);
+               snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32, 
+                         BBT.bars, BBT.beats, BBT.ticks);
        }
 
        _layout->set_text (buf);
@@ -964,7 +1171,7 @@ AudioClock::set_bbt (framepos_t when, bool force)
                sprintf (buf, "%-5.2f", m.tempo().beats_per_minute());
                _left_layout->set_text (buf);
 
-               sprintf (buf, "%g|%g", m.meter().beats_per_bar(), m.meter().note_divisor());
+               sprintf (buf, "%g/%g", m.meter().divisions_per_bar(), m.meter().note_divisor());
                _right_layout->set_text (buf);
        }
 }
@@ -1004,50 +1211,6 @@ AudioClock::set_session (Session *s)
 
 bool
 AudioClock::on_key_press_event (GdkEventKey* ev)
-{
-       if (!editing) {
-               return false;
-       }
-       
-       /* return true for keys that we MIGHT use 
-          at release
-       */
-       switch (ev->keyval) {
-       case GDK_0:
-       case GDK_KP_0:
-       case GDK_1:
-       case GDK_KP_1:
-       case GDK_2:
-       case GDK_KP_2:
-       case GDK_3:
-       case GDK_KP_3:
-       case GDK_4:
-       case GDK_KP_4:
-       case GDK_5:
-       case GDK_KP_5:
-       case GDK_6:
-       case GDK_KP_6:
-       case GDK_7:
-       case GDK_KP_7:
-       case GDK_8:
-       case GDK_KP_8:
-       case GDK_9:
-       case GDK_KP_9:
-       case GDK_period:
-       case GDK_comma:
-       case GDK_KP_Decimal:
-       case GDK_Tab:
-       case GDK_Return:
-       case GDK_KP_Enter:
-       case GDK_Escape:
-               return true;
-       default:
-               return false;
-       }
-}
-
-bool
-AudioClock::on_key_release_event (GdkEventKey *ev)
 {
        if (!editing) {
                return false;
@@ -1055,6 +1218,8 @@ AudioClock::on_key_release_event (GdkEventKey *ev)
 
        string new_text;
        char new_char = 0;
+       int highlight_length;
+       framepos_t pos;
 
        switch (ev->keyval) {
        case GDK_0:
@@ -1124,7 +1289,11 @@ AudioClock::on_key_release_event (GdkEventKey *ev)
 
        case GDK_Delete:
        case GDK_BackSpace:
-               input_string = input_string.substr (1, input_string.length() - 1);
+               if (!input_string.empty()) {
+                       /* delete the last key entered
+                       */
+                       input_string = input_string.substr (0, input_string.length() - 1);
+               }
                goto use_input_string;
 
        default:
@@ -1132,75 +1301,110 @@ AudioClock::on_key_release_event (GdkEventKey *ev)
        }
 
        if (!insert_map.empty() && (input_string.length() >= insert_map.size())) {
-               /* eat the key event, but do no nothing with it */
+               /* too many digits: eat the key event, but do nothing with it */
                return true;
        }
 
-       input_string.insert (input_string.begin(), new_char);
+       input_string.push_back (new_char);
 
   use_input_string:
 
-       string::reverse_iterator ri;
-       vector<int> insert_at;
-       int highlight_length = 0;
-       char buf[32];
-       framepos_t pos;
-
-       /* merge with pre-edit-string into edit string */
-       
        switch (_mode) {
        case Frames:
                /* get this one in the right order, and to the right width */
-               if (ev->keyval != GDK_Delete && ev->keyval != GDK_BackSpace) {
-                       edit_string.push_back (new_char);
-               } else {
+               if (ev->keyval == GDK_Delete || ev->keyval == GDK_BackSpace) {
                        edit_string = edit_string.substr (0, edit_string.length() - 1);
+               } else {
+                       edit_string.push_back (new_char);
                }
                if (!edit_string.empty()) {
+                       char buf[32];
                        sscanf (edit_string.c_str(), "%" PRId64, &pos);
                        snprintf (buf, sizeof (buf), " %10" PRId64, pos);
                        edit_string = buf;
                }
+               /* highlight the whole thing */
                highlight_length = edit_string.length();
                break;
-               
+
        default:
-               edit_string = pre_edit_string;
-               
-               /* backup through the original string, till we have
-                * enough digits locations to put all the digits from
-                * the input string.
-                */
-               
-               for (ri = edit_string.rbegin(); ri != edit_string.rend(); ++ri) {
-                       if (isdigit (*ri)) {
-                               insert_at.push_back (edit_string.length() - (ri - edit_string.rbegin()) - 1);
-                               if (insert_at.size() == input_string.length()) {
-                                       break;
-                               }
-                       }
-               }
-               
-               if (insert_at.size() != input_string.length()) {
-                       error << "something went wrong " << endmsg;
-               } else {
-                       for (int i = input_string.length() - 1; i >= 0; --i) {
-                               edit_string[insert_at[i]] = input_string[i];
-                       }
-                       
-                       highlight_length = edit_string.length() - insert_at.back();
-               }
-               
-               break;
+               highlight_length = merge_input_and_edit_string ();
        }
+
+       show_edit_status (highlight_length);
+       _layout->set_text (edit_string);
+       queue_draw ();
+
+       return true;
+}
+
+int
+AudioClock::merge_input_and_edit_string ()
+{
+       /* merge with pre-edit-string into edit string */
        
-       if (edit_string != _layout->get_text()) {
-               show_edit_status (highlight_length);
-               _layout->set_text (edit_string);
-               queue_draw ();
+       edit_string = pre_edit_string;
+       
+       if (input_string.empty()) {
+               return 0;
        } 
 
-       return true;
+       string::size_type target;
+       for (string::size_type i = 0; i < input_string.length(); ++i) {
+               target = insert_map[input_string.length() - 1 - i];
+               edit_string[target] = input_string[i];
+       }
+       /* highlight from end to wherever the last character was added */
+       return edit_string.length() - insert_map[input_string.length()-1];
+}
+
+
+bool
+AudioClock::on_key_release_event (GdkEventKey *ev)
+{
+       if (!editing) {
+               return false;
+       }
+       
+       /* return true for keys that we used on press
+          so that they cannot possibly do double-duty
+       */
+       switch (ev->keyval) {
+       case GDK_0:
+       case GDK_KP_0:
+       case GDK_1:
+       case GDK_KP_1:
+       case GDK_2:
+       case GDK_KP_2:
+       case GDK_3:
+       case GDK_KP_3:
+       case GDK_4:
+       case GDK_KP_4:
+       case GDK_5:
+       case GDK_KP_5:
+       case GDK_6:
+       case GDK_KP_6:
+       case GDK_7:
+       case GDK_KP_7:
+       case GDK_8:
+       case GDK_KP_8:
+       case GDK_9:
+       case GDK_KP_9:
+       case GDK_period:
+       case GDK_comma:
+       case GDK_KP_Decimal:
+       case GDK_Tab:
+       case GDK_Return:
+       case GDK_KP_Enter:
+       case GDK_Escape:
+       case GDK_minus:
+       case GDK_plus:
+       case GDK_KP_Add:
+       case GDK_KP_Subtract:
+               return true;
+       default:
+               return false;
+       }
 }
 
 AudioClock::Field
@@ -1271,7 +1475,7 @@ AudioClock::on_button_press_event (GdkEventButton *ev)
                         */
 
                        y = ev->y - ((upper_height - layout_height)/2);
-                       x = ev->x - x_leading_padding;
+                       x = ev->x - layout_x_offset;
                        
                        if (_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {                 
                                drag_field = index_to_field (index);
@@ -1302,10 +1506,36 @@ AudioClock::on_button_release_event (GdkEventButton *ev)
                                return true;
                        } else {
                                if (ev->button == 1) {
-                                       start_edit ();
+                                       
+                                       if (_edit_by_click_field) {
+
+                                               int index = 0;
+                                               int trailing;
+                                               int y = ev->y - ((upper_height - layout_height)/2);
+                                               int x = ev->x - layout_x_offset;
+                                               Field f;
+
+                                               if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
+                                                       return true;
+                                               }
+                                               
+                                               f = index_to_field (index);
+                                               
+                                               switch (f) {
+                                               case Timecode_Frames:
+                                               case MS_Milliseconds:
+                                               case Ticks:
+                                                       f = Field (0);
+                                                       break;
+                                               default:
+                                                       break;
+                                               }
+                                               start_edit (f);
+                                       } else {
+                                               start_edit ();
+                                       }
                                }
                        }
-
                }
        }
 
@@ -1350,7 +1580,7 @@ AudioClock::on_scroll_event (GdkEventScroll *ev)
         */
 
        y = ev->y - ((upper_height - layout_height)/2);
-       x = ev->x - x_leading_padding;
+       x = ev->x - layout_x_offset;
 
        if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
                /* not in the main layout */
@@ -1545,8 +1775,14 @@ AudioClock::bbt_validate_edit (const string& str)
 {
        AnyTime any;
 
-       sscanf (str.c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks);
-       
+       if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
+               return false;
+       }
+
+       if (any.bbt.ticks > Timecode::BBT_Time::ticks_per_bar_division) {
+               return false;
+       }
+
        if (!is_duration && any.bbt.bars == 0) {
                return false;
        }
@@ -1568,16 +1804,16 @@ AudioClock::timecode_validate_edit (const string& str)
                return false;
        }
 
-       if (TC.minutes > 59 || TC.seconds > 59) {
+       if (TC.hours > 23U || TC.minutes > 59U || TC.seconds > 59U) {
                return false;
        }
 
-       if (TC.frames > (long)rint(_session->timecode_frames_per_second()) - 1) {
+       if (TC.frames > (uint32_t) rint (_session->timecode_frames_per_second()) - 1) {
                return false;
        }
 
        if (_session->timecode_drop_frames()) {
-               if (TC.minutes % 10 && TC.seconds == 0 && TC.frames < 2) {
+               if (TC.minutes % 10 && TC.seconds == 0U && TC.frames < 2U) {
                        return false;
                }
        }
@@ -1585,6 +1821,22 @@ AudioClock::timecode_validate_edit (const string& str)
        return true;
 }
 
+bool
+AudioClock::minsec_validate_edit (const string& str)
+{
+       int hrs, mins, secs, millisecs;
+
+       if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
+               return false;
+       }
+       
+       if (hrs > 23 || mins > 59 || secs > 59 || millisecs > 999) {
+               return false;
+       }
+
+       return true;
+}
+
 framepos_t
 AudioClock::frames_from_timecode_string (const string& str) const
 {
@@ -1639,7 +1891,9 @@ AudioClock::frames_from_bbt_string (framepos_t pos, const string& str) const
        AnyTime any;
        any.type = AnyTime::BBT;
 
-       sscanf (str.c_str(), "%" PRId32 "|%" PRId32 "|%" PRId32, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks);
+       if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
+               return 0;
+       }
        
        if (is_duration) {
                any.bbt.bars++;
@@ -1661,7 +1915,9 @@ AudioClock::frame_duration_from_bbt_string (framepos_t pos, const string& str) c
 
        Timecode::BBT_Time bbt;
 
-       sscanf (str.c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, &bbt.bars, &bbt.beats, &bbt.ticks);
+       if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 0) {
+               return 0;
+       }
 
        return _session->tempo_map().bbt_duration_at(pos,bbt,1);
 }
@@ -1785,6 +2041,13 @@ AudioClock::set_mode (Mode m)
                 ModeChanged (); /* EMIT SIGNAL (the static one)*/
         }
 
+       if (!_fixed_width) {
+               /* display is different, allow us to resize */
+               first_width = 0;
+               first_height = 0;
+               queue_resize ();
+       }
+
         mode_changed (); /* EMIT SIGNAL (the member one) */
 }
 
@@ -1838,7 +2101,28 @@ AudioClock::set_off (bool yn)
 void
 AudioClock::focus ()
 {
-       start_edit ();
+       start_edit (Field (0));
+}
+
+void
+AudioClock::set_corner_radius (double r)
+{
+       corner_radius = r;
+       queue_draw ();
 }
 
+void
+AudioClock::set_fixed_width (bool yn)
+{
+       _fixed_width = yn;
+}
 
+void
+AudioClock::dpi_reset ()
+{
+       /* force recomputation of size even if we are fixed width 
+        */
+       first_width = 0;
+       first_height = 0;
+       queue_resize ();
+}