allow resizing of startup wizard window
[ardour.git] / gtk2_ardour / audio_clock.cc
index 57abbe325c30bfbc16dfe7569ff34a65772ce9f7..fd8aca8df18466016f9494a761bc093b22b59ef1 100644 (file)
 #include "pbd/enumwriter.h"
 
 #include <gtkmm/style.h>
+#include <sigc++/bind.h>
 
 #include "gtkmm2ext/cairocell.h"
 #include "gtkmm2ext/utils.h"
 #include "gtkmm2ext/rgb_macros.h"
 
-#include "ardour/ardour.h"
+#include "ardour/types.h"
 #include "ardour/session.h"
 #include "ardour/tempo.h"
 #include "ardour/profile.h"
-#include "ardour/slave.h"
-#include <sigc++/bind.h>
 
 #include "ardour_ui.h"
 #include "audio_clock.h"
@@ -62,7 +61,8 @@ const double AudioClock::x_leading_padding = 6.0;
 
 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)
+       : ops_menu (0)
+       , _name (clock_name)
        , is_transient (transient)
        , is_duration (duration)
        , editable (allow_edit)
@@ -70,7 +70,8 @@ AudioClock::AudioClock (const string& clock_name, bool transient, const string&
        , _off (false)
        , _fixed_width (true)
        , layout_x_offset (0)
-       , ops_menu (0)
+       , em_width (0)
+       , _edit_by_click_field (false)
        , editing_attr (0)
        , foreground_attr (0)
        , first_height (0)
@@ -171,6 +172,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
@@ -332,24 +344,39 @@ AudioClock::render (cairo_t* 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, cursor_r, cursor_g, cursor_b, cursor_a);
-                       if (!_fixed_width) {
-                               cairo_rectangle (cr, 
-                                                layout_x_offset + cursor.get_x()/PANGO_SCALE + cursor_width,
-                                                0,
-                                                2.0, cursor.get_height()/PANGO_SCALE);
+
+
+                       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 {
-                               cairo_rectangle (cr, 
-                                                layout_x_offset + cursor.get_x()/PANGO_SCALE + cursor_width,
-                                                (upper_height - layout_height)/2.0, 
-                                                2.0, cursor.get_height()/PANGO_SCALE);
+                               /* we've entered all possible digits, no cursor */
                        }
-                       cairo_fill (cr);        
+
                } else {
                        if (input_string.empty()) {
                                cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
@@ -388,7 +415,6 @@ AudioClock::on_size_allocate (Gtk::Allocation& alloc)
                /* left justify */
                layout_x_offset = 0;
        }
-
 }
 
 void
@@ -495,7 +521,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()) {
@@ -507,12 +533,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)
 {
@@ -530,6 +606,7 @@ AudioClock::end_edit (bool modify)
                        break;
                        
                case MinSec:
+                       ok = minsec_validate_edit (edit_string);
                        break;
                        
                case Frames:
@@ -718,7 +795,7 @@ AudioClock::parse_as_timecode_distance (const std::string& str)
 }
 
 framecnt_t 
-AudioClock::parse_as_bbt_distance (const std::string& str)
+AudioClock::parse_as_bbt_distance (const std::string&)
 {
        return 0;
 }
@@ -726,24 +803,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;
@@ -752,6 +823,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;
@@ -900,7 +1000,7 @@ AudioClock::set_frames (framepos_t when, bool /*force*/)
 }
 
 void
-AudioClock::set_minsec (framepos_t when, bool force)
+AudioClock::set_minsec (framepos_t when, bool /*force*/)
 {
        char buf[32];
        framecnt_t left;
@@ -945,7 +1045,7 @@ AudioClock::set_minsec (framepos_t when, bool force)
 }
 
 void
-AudioClock::set_timecode (framepos_t when, bool force)
+AudioClock::set_timecode (framepos_t when, bool /*force*/)
 {
        char buf[32];
        Timecode::Time TC;
@@ -1011,7 +1111,7 @@ AudioClock::set_timecode (framepos_t when, bool force)
 }
 
 void
-AudioClock::set_bbt (framepos_t when, bool force)
+AudioClock::set_bbt (framepos_t when, bool /*force*/)
 {
        char buf[16];
        Timecode::BBT_Time BBT;
@@ -1070,7 +1170,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);
        }
 }
@@ -1110,50 +1210,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;
@@ -1161,6 +1217,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:
@@ -1230,7 +1288,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:
@@ -1238,75 +1300,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
@@ -1358,15 +1455,6 @@ AudioClock::on_button_press_event (GdkEventButton *ev)
        switch (ev->button) {
        case 1:
                if (editable && !_off) {
-                       dragging = true;
-                       /* 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);
-                       drag_accum = 0;
-                       drag_start_y = ev->y;
-                       drag_y = ev->y;
-                       
                        int index;
                        int trailing;
                        int y;
@@ -1379,10 +1467,16 @@ AudioClock::on_button_press_event (GdkEventButton *ev)
                        y = ev->y - ((upper_height - layout_height)/2);
                        x = ev->x - layout_x_offset;
                        
-                       if (_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {                 
+                       if (_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
                                drag_field = index_to_field (index);
-                       } else {
-                               drag_field = Field (0);
+                               dragging = true;
+                               /* 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);
+                               drag_accum = 0;
+                               drag_start_y = ev->y;
+                               drag_y = ev->y;
                        }
                }
                break;
@@ -1408,10 +1502,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 ();
+                                       }
                                }
                        }
-
                }
        }
 
@@ -1537,7 +1657,7 @@ AudioClock::on_motion_notify_event (GdkEventMotion *ev)
                int dir;
                dir = (drag_accum < 0 ? 1:-1);
                pos = current_time();
-               frames = get_frame_step (drag_field,pos,dir);
+               frames = get_frame_step (drag_field, pos, dir);
 
                if (frames  != 0 &&  frames * drag_accum < current_time()) {
                        set ((framepos_t) floor (pos - drag_accum * frames), false); // minus because up is negative in GTK
@@ -1616,7 +1736,7 @@ AudioClock::get_frame_step (Field field, framepos_t pos, int dir)
 }
 
 framepos_t
-AudioClock::current_time (framepos_t pos) const
+AudioClock::current_time (framepos_t) const
 {
        return last_when;
 }
@@ -1654,7 +1774,11 @@ AudioClock::bbt_validate_edit (const string& str)
        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_beat) {
+               return false;
+       }
+
        if (!is_duration && any.bbt.bars == 0) {
                return false;
        }
@@ -1667,7 +1791,7 @@ AudioClock::bbt_validate_edit (const string& str)
 }
 
 bool
-AudioClock::timecode_validate_edit (const string& str)
+AudioClock::timecode_validate_edit (const string&)
 {
        Timecode::Time TC;
 
@@ -1676,16 +1800,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;
                }
        }
@@ -1693,6 +1817,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
 {
@@ -1771,10 +1911,10 @@ AudioClock::frame_duration_from_bbt_string (framepos_t pos, const string& str) c
 
        Timecode::BBT_Time bbt;
 
-       if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 0) {
+       if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 3) {
                return 0;
        }
-
+       
        return _session->tempo_map().bbt_duration_at(pos,bbt,1);
 }
 
@@ -1957,7 +2097,7 @@ AudioClock::set_off (bool yn)
 void
 AudioClock::focus ()
 {
-       start_edit ();
+       start_edit (Field (0));
 }
 
 void