# HG changeset patch # User Chris Cannam # Date 1484303390 0 # Node ID dc2af6616c8389ce559d36ef8b27671ced2f3db9 # Parent e8102ff5573b5d89c54da4a92b9c7fbd1a27962b# Parent c603aba6170202ae759e99c09390eb4b640f8003 Merge from branch 3.0-integration diff -r e8102ff5573b -r dc2af6616c83 configure --- a/configure Fri Mar 04 12:23:31 2016 +0000 +++ b/configure Fri Jan 13 10:29:50 2017 +0000 @@ -646,16 +646,12 @@ libpulse_CFLAGS JACK_LIBS JACK_CFLAGS -portaudio_2_0_LIBS -portaudio_2_0_CFLAGS +portaudio_LIBS +portaudio_CFLAGS liblo_LIBS liblo_CFLAGS rubberband_LIBS rubberband_CFLAGS -vamphostsdk_LIBS -vamphostsdk_CFLAGS -vamp_LIBS -vamp_CFLAGS samplerate_LIBS samplerate_CFLAGS sndfile_LIBS @@ -756,16 +752,12 @@ sndfile_LIBS samplerate_CFLAGS samplerate_LIBS -vamp_CFLAGS -vamp_LIBS -vamphostsdk_CFLAGS -vamphostsdk_LIBS rubberband_CFLAGS rubberband_LIBS liblo_CFLAGS liblo_LIBS -portaudio_2_0_CFLAGS -portaudio_2_0_LIBS +portaudio_CFLAGS +portaudio_LIBS JACK_CFLAGS JACK_LIBS libpulse_CFLAGS @@ -1423,12 +1415,6 @@ C compiler flags for samplerate, overriding pkg-config samplerate_LIBS linker flags for samplerate, overriding pkg-config - vamp_CFLAGS C compiler flags for vamp, overriding pkg-config - vamp_LIBS linker flags for vamp, overriding pkg-config - vamphostsdk_CFLAGS - C compiler flags for vamphostsdk, overriding pkg-config - vamphostsdk_LIBS - linker flags for vamphostsdk, overriding pkg-config rubberband_CFLAGS C compiler flags for rubberband, overriding pkg-config rubberband_LIBS @@ -1436,10 +1422,10 @@ liblo_CFLAGS C compiler flags for liblo, overriding pkg-config liblo_LIBS linker flags for liblo, overriding pkg-config - portaudio_2_0_CFLAGS - C compiler flags for portaudio_2_0, overriding pkg-config - portaudio_2_0_LIBS - linker flags for portaudio_2_0, overriding pkg-config + portaudio_CFLAGS + C compiler flags for portaudio, overriding pkg-config + portaudio_LIBS + linker flags for portaudio, overriding pkg-config JACK_CFLAGS C compiler flags for JACK, overriding pkg-config JACK_LIBS linker flags for JACK, overriding pkg-config libpulse_CFLAGS @@ -5226,18 +5212,18 @@ fi -SV_MODULE_MODULE=vamp -SV_MODULE_VERSION_TEST="vamp >= 2.1" -SV_MODULE_HEADER=vamp/vamp.h -SV_MODULE_LIB= -SV_MODULE_FUNC= -SV_MODULE_HAVE=HAVE_$(echo vamp | tr 'a-z' 'A-Z') +SV_MODULE_MODULE=rubberband +SV_MODULE_VERSION_TEST="rubberband" +SV_MODULE_HEADER=rubberband/RubberBandStretcher.h +SV_MODULE_LIB=rubberband +SV_MODULE_FUNC=rubberband_new +SV_MODULE_HAVE=HAVE_$(echo rubberband | tr 'a-z' 'A-Z') SV_MODULE_FAILED=1 -if test -n "$vamp_LIBS" ; then +if test -n "$rubberband_LIBS" ; then { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} - CXXFLAGS="$CXXFLAGS $vamp_CFLAGS" - LIBS="$LIBS $vamp_LIBS" + CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS" + LIBS="$LIBS $rubberband_LIBS" SV_MODULE_FAILED="" fi if test -z "$SV_MODULE_VERSION_TEST" ; then @@ -5246,11 +5232,11 @@ if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for vamp" >&5 -$as_echo_n "checking for vamp... " >&6; } - -if test -n "$vamp_CFLAGS"; then - pkg_cv_vamp_CFLAGS="$vamp_CFLAGS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for rubberband" >&5 +$as_echo_n "checking for rubberband... " >&6; } + +if test -n "$rubberband_CFLAGS"; then + pkg_cv_rubberband_CFLAGS="$rubberband_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5258,7 +5244,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_vamp_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_rubberband_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5266,8 +5252,8 @@ else pkg_failed=untried fi -if test -n "$vamp_LIBS"; then - pkg_cv_vamp_LIBS="$vamp_LIBS" +if test -n "$rubberband_LIBS"; then + pkg_cv_rubberband_LIBS="$rubberband_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5275,7 +5261,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_vamp_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_rubberband_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5296,12 +5282,12 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - vamp_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + rubberband_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` else - vamp_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + rubberband_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` fi # Put the nasty error message in config.log where it belongs - echo "$vamp_PKG_ERRORS" >&5 + echo "$rubberband_PKG_ERRORS" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 $as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} @@ -5311,11 +5297,11 @@ { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 $as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} else - vamp_CFLAGS=$pkg_cv_vamp_CFLAGS - vamp_LIBS=$pkg_cv_vamp_LIBS + rubberband_CFLAGS=$pkg_cv_rubberband_CFLAGS + rubberband_LIBS=$pkg_cv_rubberband_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $vamp_CFLAGS";LIBS="$LIBS $vamp_LIBS";SV_MODULE_FAILED="" + HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS";LIBS="$LIBS $rubberband_LIBS";SV_MODULE_FAILED="" fi fi if test -n "$SV_MODULE_FAILED"; then @@ -5377,18 +5363,19 @@ fi -SV_MODULE_MODULE=vamphostsdk -SV_MODULE_VERSION_TEST="vamp-hostsdk >= 2.5" -SV_MODULE_HEADER=vamp-hostsdk/PluginLoader.h -SV_MODULE_LIB=vamp-hostsdk -SV_MODULE_FUNC=libvamphostsdk_v_2_5_present -SV_MODULE_HAVE=HAVE_$(echo vamphostsdk | tr 'a-z' 'A-Z') + +SV_MODULE_MODULE=liblo +SV_MODULE_VERSION_TEST="" +SV_MODULE_HEADER=lo/lo.h +SV_MODULE_LIB=lo +SV_MODULE_FUNC=lo_address_new +SV_MODULE_HAVE=HAVE_$(echo liblo | tr 'a-z' 'A-Z') SV_MODULE_FAILED=1 -if test -n "$vamphostsdk_LIBS" ; then +if test -n "$liblo_LIBS" ; then { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} - CXXFLAGS="$CXXFLAGS $vamphostsdk_CFLAGS" - LIBS="$LIBS $vamphostsdk_LIBS" + CXXFLAGS="$CXXFLAGS $liblo_CFLAGS" + LIBS="$LIBS $liblo_LIBS" SV_MODULE_FAILED="" fi if test -z "$SV_MODULE_VERSION_TEST" ; then @@ -5397,11 +5384,11 @@ if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for vamphostsdk" >&5 -$as_echo_n "checking for vamphostsdk... " >&6; } - -if test -n "$vamphostsdk_CFLAGS"; then - pkg_cv_vamphostsdk_CFLAGS="$vamphostsdk_CFLAGS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for liblo" >&5 +$as_echo_n "checking for liblo... " >&6; } + +if test -n "$liblo_CFLAGS"; then + pkg_cv_liblo_CFLAGS="$liblo_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5409,7 +5396,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_vamphostsdk_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_liblo_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5417,8 +5404,8 @@ else pkg_failed=untried fi -if test -n "$vamphostsdk_LIBS"; then - pkg_cv_vamphostsdk_LIBS="$vamphostsdk_LIBS" +if test -n "$liblo_LIBS"; then + pkg_cv_liblo_LIBS="$liblo_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5426,7 +5413,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_vamphostsdk_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_liblo_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5447,40 +5434,42 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - vamphostsdk_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + liblo_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` else - vamphostsdk_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + liblo_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` fi # Put the nasty error message in config.log where it belongs - echo "$vamphostsdk_PKG_ERRORS" >&5 - - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} + echo "$liblo_PKG_ERRORS" >&5 + + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 +$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} -else - vamphostsdk_CFLAGS=$pkg_cv_vamphostsdk_CFLAGS - vamphostsdk_LIBS=$pkg_cv_vamphostsdk_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 +$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} +else + liblo_CFLAGS=$pkg_cv_liblo_CFLAGS + liblo_LIBS=$pkg_cv_liblo_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $vamphostsdk_CFLAGS";LIBS="$LIBS $vamphostsdk_LIBS";SV_MODULE_FAILED="" + HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $liblo_CFLAGS";LIBS="$LIBS $liblo_LIBS";SV_MODULE_FAILED="" fi fi if test -n "$SV_MODULE_FAILED"; then as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh` ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default" if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : - HAVES="$HAVES $SV_MODULE_HAVE" -else - as_fn_error $? "Failed to find header $SV_MODULE_HEADER for required module $SV_MODULE_MODULE" "$LINENO" 5 -fi - - - if test -n "$SV_MODULE_LIB"; then - as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh` + HAVES="$HAVES $SV_MODULE_HAVE";SV_MODULE_FAILED="" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&5 +$as_echo "$as_me: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&6;} +fi + + + if test -z "$SV_MODULE_FAILED"; then + if test -n "$SV_MODULE_LIB"; then + as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh` { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5 $as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; } if eval \${$as_ac_Lib+:} false; then : @@ -5521,25 +5510,27 @@ if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : LIBS="$LIBS -l$SV_MODULE_LIB" else - as_fn_error $? "Failed to find library $SV_MODULE_LIB for required module $SV_MODULE_MODULE" "$LINENO" 5 -fi - + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&5 +$as_echo "$as_me: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&6;} +fi + + fi fi fi -SV_MODULE_MODULE=rubberband -SV_MODULE_VERSION_TEST="rubberband" -SV_MODULE_HEADER=rubberband/RubberBandStretcher.h -SV_MODULE_LIB=rubberband -SV_MODULE_FUNC=rubberband_new -SV_MODULE_HAVE=HAVE_$(echo rubberband | tr 'a-z' 'A-Z') +SV_MODULE_MODULE=portaudio +SV_MODULE_VERSION_TEST="portaudio-2.0 >= 19" +SV_MODULE_HEADER=portaudio.h +SV_MODULE_LIB=portaudio +SV_MODULE_FUNC=Pa_IsFormatSupported +SV_MODULE_HAVE=HAVE_$(echo portaudio | tr 'a-z' 'A-Z') SV_MODULE_FAILED=1 -if test -n "$rubberband_LIBS" ; then +if test -n "$portaudio_LIBS" ; then { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 $as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} - CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS" - LIBS="$LIBS $rubberband_LIBS" + CXXFLAGS="$CXXFLAGS $portaudio_CFLAGS" + LIBS="$LIBS $portaudio_LIBS" SV_MODULE_FAILED="" fi if test -z "$SV_MODULE_VERSION_TEST" ; then @@ -5548,11 +5539,11 @@ if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for rubberband" >&5 -$as_echo_n "checking for rubberband... " >&6; } - -if test -n "$rubberband_CFLAGS"; then - pkg_cv_rubberband_CFLAGS="$rubberband_CFLAGS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for portaudio" >&5 +$as_echo_n "checking for portaudio... " >&6; } + +if test -n "$portaudio_CFLAGS"; then + pkg_cv_portaudio_CFLAGS="$portaudio_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5560,7 +5551,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_rubberband_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_portaudio_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5568,8 +5559,8 @@ else pkg_failed=untried fi -if test -n "$rubberband_LIBS"; then - pkg_cv_rubberband_LIBS="$rubberband_LIBS" +if test -n "$portaudio_LIBS"; then + pkg_cv_portaudio_LIBS="$portaudio_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 @@ -5577,7 +5568,7 @@ ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_rubberband_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` + pkg_cv_portaudio_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5598,164 +5589,12 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - rubberband_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + portaudio_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` else - rubberband_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + portaudio_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` fi # Put the nasty error message in config.log where it belongs - echo "$rubberband_PKG_ERRORS" >&5 - - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} -elif test $pkg_failed = untried; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find required module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} -else - rubberband_CFLAGS=$pkg_cv_rubberband_CFLAGS - rubberband_LIBS=$pkg_cv_rubberband_LIBS - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $rubberband_CFLAGS";LIBS="$LIBS $rubberband_LIBS";SV_MODULE_FAILED="" -fi -fi -if test -n "$SV_MODULE_FAILED"; then - as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh` -ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default" -if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : - HAVES="$HAVES $SV_MODULE_HAVE" -else - as_fn_error $? "Failed to find header $SV_MODULE_HEADER for required module $SV_MODULE_MODULE" "$LINENO" 5 -fi - - - if test -n "$SV_MODULE_LIB"; then - as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5 -$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$SV_MODULE_LIB $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char $SV_MODULE_FUNC (); -int -main () -{ -return $SV_MODULE_FUNC (); - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - LIBS="$LIBS -l$SV_MODULE_LIB" -else - as_fn_error $? "Failed to find library $SV_MODULE_LIB for required module $SV_MODULE_MODULE" "$LINENO" 5 -fi - - fi -fi - - - -SV_MODULE_MODULE=liblo -SV_MODULE_VERSION_TEST="" -SV_MODULE_HEADER=lo/lo.h -SV_MODULE_LIB=lo -SV_MODULE_FUNC=lo_address_new -SV_MODULE_HAVE=HAVE_$(echo liblo | tr 'a-z' 'A-Z') -SV_MODULE_FAILED=1 -if test -n "$liblo_LIBS" ; then - { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 -$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} - CXXFLAGS="$CXXFLAGS $liblo_CFLAGS" - LIBS="$LIBS $liblo_LIBS" - SV_MODULE_FAILED="" -fi -if test -z "$SV_MODULE_VERSION_TEST" ; then - SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE -fi -if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then - -pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for liblo" >&5 -$as_echo_n "checking for liblo... " >&6; } - -if test -n "$liblo_CFLAGS"; then - pkg_cv_liblo_CFLAGS="$liblo_CFLAGS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 - ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_liblo_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi -if test -n "$liblo_LIBS"; then - pkg_cv_liblo_LIBS="$liblo_LIBS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 - ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_liblo_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi - - - -if test $pkg_failed = yes; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - -if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then - _pkg_short_errors_supported=yes -else - _pkg_short_errors_supported=no -fi - if test $_pkg_short_errors_supported = yes; then - liblo_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` - else - liblo_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` - fi - # Put the nasty error message in config.log where it belongs - echo "$liblo_PKG_ERRORS" >&5 + echo "$portaudio_PKG_ERRORS" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 $as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} @@ -5765,166 +5604,11 @@ { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 $as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} else - liblo_CFLAGS=$pkg_cv_liblo_CFLAGS - liblo_LIBS=$pkg_cv_liblo_LIBS + portaudio_CFLAGS=$pkg_cv_portaudio_CFLAGS + portaudio_LIBS=$pkg_cv_portaudio_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $liblo_CFLAGS";LIBS="$LIBS $liblo_LIBS";SV_MODULE_FAILED="" -fi -fi -if test -n "$SV_MODULE_FAILED"; then - as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh` -ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default" -if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : - HAVES="$HAVES $SV_MODULE_HAVE";SV_MODULE_FAILED="" -else - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&5 -$as_echo "$as_me: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&6;} -fi - - - if test -z "$SV_MODULE_FAILED"; then - if test -n "$SV_MODULE_LIB"; then - as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5 -$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$SV_MODULE_LIB $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char $SV_MODULE_FUNC (); -int -main () -{ -return $SV_MODULE_FUNC (); - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - LIBS="$LIBS -l$SV_MODULE_LIB" -else - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&5 -$as_echo "$as_me: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&6;} -fi - - fi - fi -fi - - -SV_MODULE_MODULE=portaudio_2_0 -SV_MODULE_VERSION_TEST="portaudio-2.0 >= 19" -SV_MODULE_HEADER=portaudio.h -SV_MODULE_LIB=portaudio -SV_MODULE_FUNC=Pa_IsFormatSupported -SV_MODULE_HAVE=HAVE_$(echo portaudio_2_0 | tr 'a-z' 'A-Z') -SV_MODULE_FAILED=1 -if test -n "$portaudio_2_0_LIBS" ; then - { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 -$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} - CXXFLAGS="$CXXFLAGS $portaudio_2_0_CFLAGS" - LIBS="$LIBS $portaudio_2_0_LIBS" - SV_MODULE_FAILED="" -fi -if test -z "$SV_MODULE_VERSION_TEST" ; then - SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE -fi -if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then - -pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for portaudio_2_0" >&5 -$as_echo_n "checking for portaudio_2_0... " >&6; } - -if test -n "$portaudio_2_0_CFLAGS"; then - pkg_cv_portaudio_2_0_CFLAGS="$portaudio_2_0_CFLAGS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 - ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_portaudio_2_0_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi -if test -n "$portaudio_2_0_LIBS"; then - pkg_cv_portaudio_2_0_LIBS="$portaudio_2_0_LIBS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 - ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_portaudio_2_0_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi - - - -if test $pkg_failed = yes; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - -if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then - _pkg_short_errors_supported=yes -else - _pkg_short_errors_supported=no -fi - if test $_pkg_short_errors_supported = yes; then - portaudio_2_0_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` - else - portaudio_2_0_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` - fi - # Put the nasty error message in config.log where it belongs - echo "$portaudio_2_0_PKG_ERRORS" >&5 - - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} -elif test $pkg_failed = untried; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 -$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} -else - portaudio_2_0_CFLAGS=$pkg_cv_portaudio_2_0_CFLAGS - portaudio_2_0_LIBS=$pkg_cv_portaudio_2_0_LIBS - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $portaudio_2_0_CFLAGS";LIBS="$LIBS $portaudio_2_0_LIBS";SV_MODULE_FAILED="" + HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $portaudio_CFLAGS";LIBS="$LIBS $portaudio_LIBS";SV_MODULE_FAILED="" fi fi if test -n "$SV_MODULE_FAILED"; then diff -r e8102ff5573b -r dc2af6616c83 configure.ac --- a/configure.ac Fri Mar 04 12:23:31 2016 +0000 +++ b/configure.ac Fri Jan 13 10:29:50 2017 +0000 @@ -83,12 +83,10 @@ SV_MODULE_REQUIRED([fftw3f],[fftw3f >= 3.0.0],[fftw3.h],[fftw3f],[fftwf_execute]) SV_MODULE_REQUIRED([sndfile],[sndfile >= 1.0.16],[sndfile.h],[sndfile],[sf_open]) SV_MODULE_REQUIRED([samplerate],[samplerate >= 0.1.2],[samplerate.h],[samplerate],[src_new]) -SV_MODULE_REQUIRED([vamp],[vamp >= 2.1],[vamp/vamp.h],[],[]) -SV_MODULE_REQUIRED([vamphostsdk],[vamp-hostsdk >= 2.5],[vamp-hostsdk/PluginLoader.h],[vamp-hostsdk],[libvamphostsdk_v_2_5_present]) SV_MODULE_REQUIRED([rubberband],[rubberband],[rubberband/RubberBandStretcher.h],[rubberband],[rubberband_new]) SV_MODULE_OPTIONAL([liblo],[],[lo/lo.h],[lo],[lo_address_new]) -SV_MODULE_OPTIONAL([portaudio_2_0],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported]) +SV_MODULE_OPTIONAL([portaudio],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported]) SV_MODULE_OPTIONAL([JACK],[jack >= 0.100],[jack/jack.h],[jack],[jack_client_open]) SV_MODULE_OPTIONAL([libpulse],[libpulse >= 0.9],[pulse/pulseaudio.h],[pulse],[pa_stream_new]) SV_MODULE_OPTIONAL([lrdf],[lrdf >= 0.2],[lrdf.h],[lrdf],[lrdf_init]) diff -r e8102ff5573b -r dc2af6616c83 files.pri --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/files.pri Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,168 @@ + +SVGUI_HEADERS += \ + layer/Colour3DPlotLayer.h \ + layer/Colour3DPlotRenderer.h \ + layer/ColourDatabase.h \ + layer/ColourMapper.h \ + layer/ColourScale.h \ + layer/ColourScaleLayer.h \ + layer/FlexiNoteLayer.h \ + layer/ImageLayer.h \ + layer/ImageRegionFinder.h \ + layer/Layer.h \ + layer/LayerFactory.h \ + layer/LayerGeometryProvider.h \ + layer/LinearNumericalScale.h \ + layer/LogNumericalScale.h \ + layer/LinearColourScale.h \ + layer/LogColourScale.h \ + layer/NoteLayer.h \ + layer/PaintAssistant.h \ + layer/PianoScale.h \ + layer/RegionLayer.h \ + layer/RenderTimer.h \ + layer/ScrollableImageCache.h \ + layer/ScrollableMagRangeCache.h \ + layer/SingleColourLayer.h \ + layer/SliceableLayer.h \ + layer/SliceLayer.h \ + layer/SpectrogramLayer.h \ + layer/SpectrumLayer.h \ + layer/TextLayer.h \ + layer/TimeInstantLayer.h \ + layer/TimeRulerLayer.h \ + layer/TimeValueLayer.h \ + layer/VerticalScaleLayer.h \ + layer/WaveformLayer.h \ + view/AlignmentView.h \ + view/Overview.h \ + view/Pane.h \ + view/PaneStack.h \ + view/View.h \ + view/ViewManager.h \ + view/ViewProxy.h \ + widgets/ActivityLog.h \ + widgets/AudioDial.h \ + widgets/ClickableLabel.h \ + widgets/ColourComboBox.h \ + widgets/ColourMapComboBox.h \ + widgets/ColourNameDialog.h \ + widgets/CommandHistory.h \ + widgets/CSVFormatDialog.h \ + widgets/Fader.h \ + widgets/InteractiveFileFinder.h \ + widgets/IconLoader.h \ + widgets/ImageDialog.h \ + widgets/ItemEditDialog.h \ + widgets/KeyReference.h \ + widgets/LabelCounterInputDialog.h \ + widgets/LayerTree.h \ + widgets/LayerTreeDialog.h \ + widgets/LEDButton.h \ + widgets/LevelPanToolButton.h \ + widgets/LevelPanWidget.h \ + widgets/ListInputDialog.h \ + widgets/MIDIFileImportDialog.h \ + widgets/ModelDataTableDialog.h \ + widgets/NotifyingCheckBox.h \ + widgets/NotifyingComboBox.h \ + widgets/NotifyingPushButton.h \ + widgets/NotifyingTabBar.h \ + widgets/NotifyingToolButton.h \ + widgets/Panner.h \ + widgets/PluginParameterBox.h \ + widgets/PluginParameterDialog.h \ + widgets/ProgressDialog.h \ + widgets/PropertyBox.h \ + widgets/PropertyStack.h \ + widgets/RangeInputDialog.h \ + widgets/SelectableLabel.h \ + widgets/SubdividingMenu.h \ + widgets/TextAbbrev.h \ + widgets/Thumbwheel.h \ + widgets/TipDialog.h \ + widgets/TransformFinder.h \ + widgets/UnitConverter.h \ + widgets/WidgetScale.h \ + widgets/WindowShapePreview.h \ + widgets/WindowTypeSelector.h + +SVGUI_SOURCES += \ + layer/Colour3DPlotLayer.cpp \ + layer/Colour3DPlotRenderer.cpp \ + layer/ColourDatabase.cpp \ + layer/ColourMapper.cpp \ + layer/ColourScale.cpp \ + layer/FlexiNoteLayer.cpp \ + layer/ImageLayer.cpp \ + layer/ImageRegionFinder.cpp \ + layer/Layer.cpp \ + layer/LayerFactory.cpp \ + layer/LinearNumericalScale.cpp \ + layer/LogNumericalScale.cpp \ + layer/LinearColourScale.cpp \ + layer/LogColourScale.cpp \ + layer/NoteLayer.cpp \ + layer/PaintAssistant.cpp \ + layer/PianoScale.cpp \ + layer/RegionLayer.cpp \ + layer/ScrollableImageCache.cpp \ + layer/ScrollableMagRangeCache.cpp \ + layer/SingleColourLayer.cpp \ + layer/SliceLayer.cpp \ + layer/SpectrogramLayer.cpp \ + layer/SpectrumLayer.cpp \ + layer/TextLayer.cpp \ + layer/TimeInstantLayer.cpp \ + layer/TimeRulerLayer.cpp \ + layer/TimeValueLayer.cpp \ + layer/WaveformLayer.cpp \ + view/AlignmentView.cpp \ + view/Overview.cpp \ + view/Pane.cpp \ + view/PaneStack.cpp \ + view/View.cpp \ + view/ViewManager.cpp \ + widgets/ActivityLog.cpp \ + widgets/AudioDial.cpp \ + widgets/ColourComboBox.cpp \ + widgets/ColourMapComboBox.cpp \ + widgets/ColourNameDialog.cpp \ + widgets/CommandHistory.cpp \ + widgets/CSVFormatDialog.cpp \ + widgets/Fader.cpp \ + widgets/InteractiveFileFinder.cpp \ + widgets/IconLoader.cpp \ + widgets/ImageDialog.cpp \ + widgets/ItemEditDialog.cpp \ + widgets/KeyReference.cpp \ + widgets/LabelCounterInputDialog.cpp \ + widgets/LayerTree.cpp \ + widgets/LayerTreeDialog.cpp \ + widgets/LEDButton.cpp \ + widgets/LevelPanToolButton.cpp \ + widgets/LevelPanWidget.cpp \ + widgets/ListInputDialog.cpp \ + widgets/MIDIFileImportDialog.cpp \ + widgets/ModelDataTableDialog.cpp \ + widgets/NotifyingCheckBox.cpp \ + widgets/NotifyingComboBox.cpp \ + widgets/NotifyingPushButton.cpp \ + widgets/NotifyingTabBar.cpp \ + widgets/NotifyingToolButton.cpp \ + widgets/Panner.cpp \ + widgets/PluginParameterBox.cpp \ + widgets/PluginParameterDialog.cpp \ + widgets/ProgressDialog.cpp \ + widgets/PropertyBox.cpp \ + widgets/PropertyStack.cpp \ + widgets/RangeInputDialog.cpp \ + widgets/SelectableLabel.cpp \ + widgets/SubdividingMenu.cpp \ + widgets/TextAbbrev.cpp \ + widgets/Thumbwheel.cpp \ + widgets/TipDialog.cpp \ + widgets/TransformFinder.cpp \ + widgets/UnitConverter.cpp \ + widgets/WindowShapePreview.cpp \ + widgets/WindowTypeSelector.cpp diff -r e8102ff5573b -r dc2af6616c83 layer/Colour3DPlotLayer.cpp --- a/layer/Colour3DPlotLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/Colour3DPlotLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -15,25 +15,28 @@ #include "Colour3DPlotLayer.h" -#include "view/View.h" #include "base/Profiler.h" #include "base/LogRange.h" #include "base/RangeMapper.h" + #include "ColourMapper.h" +#include "LayerGeometryProvider.h" +#include "PaintAssistant.h" + +#include "data/model/Dense3DModelPeakCache.h" + +#include "view/ViewManager.h" #include #include #include #include +#include #include #include -#ifndef __GNUC__ -#include -#endif - using std::vector; //#define DEBUG_COLOUR_3D_PLOT_LAYER_PAINT 1 @@ -41,32 +44,92 @@ Colour3DPlotLayer::Colour3DPlotLayer() : m_model(0), - m_cache(0), - m_peaksCache(0), - m_cacheValidStart(0), - m_cacheValidEnd(0), - m_colourScale(LinearScale), + m_colourScale(ColourScaleType::Linear), m_colourScaleSet(false), m_colourMap(0), m_gain(1.0), - m_binScale(LinearBinScale), - m_normalizeColumns(false), + m_binScale(BinScale::Linear), + m_normalization(ColumnNormalization::None), m_normalizeVisibleArea(false), - m_normalizeHybrid(false), m_invertVertical(false), m_opaque(false), m_smooth(false), m_peakResolution(256), m_miny(0), - m_maxy(0) + m_maxy(0), + m_synchronous(false), + m_peakCache(0), + m_peakCacheDivisor(8) { - + QSettings settings; + settings.beginGroup("Preferences"); + setColourMap(settings.value("colour-3d-plot-colour", ColourMapper::Green).toInt()); + settings.endGroup(); } Colour3DPlotLayer::~Colour3DPlotLayer() { - delete m_cache; - delete m_peaksCache; + invalidateRenderers(); + delete m_peakCache; +} + +ColourScaleType +Colour3DPlotLayer::convertToColourScale(int value) +{ + switch (value) { + default: + case 0: return ColourScaleType::Linear; + case 1: return ColourScaleType::Log; + case 2: return ColourScaleType::PlusMinusOne; + case 3: return ColourScaleType::Absolute; + } +} + +int +Colour3DPlotLayer::convertFromColourScale(ColourScaleType scale) +{ + switch (scale) { + case ColourScaleType::Linear: return 0; + case ColourScaleType::Log: return 1; + case ColourScaleType::PlusMinusOne: return 2; + case ColourScaleType::Absolute: return 3; + + case ColourScaleType::Meter: + case ColourScaleType::Phase: + default: return 0; + } +} + +std::pair +Colour3DPlotLayer::convertToColumnNorm(int value) +{ + switch (value) { + default: + case 0: return { ColumnNormalization::None, false }; + case 1: return { ColumnNormalization::Max1, false }; + case 2: return { ColumnNormalization::None, true }; // visible area + case 3: return { ColumnNormalization::Hybrid, false }; + } +} + +int +Colour3DPlotLayer::convertFromColumnNorm(ColumnNormalization norm, bool visible) +{ + if (visible) return 2; + switch (norm) { + case ColumnNormalization::None: return 0; + case ColumnNormalization::Max1: return 1; + case ColumnNormalization::Hybrid: return 3; + + case ColumnNormalization::Sum1: + default: return 0; + } +} + +void +Colour3DPlotLayer::setSynchronousPainting(bool synchronous) +{ + m_synchronous = synchronous; } void @@ -91,7 +154,11 @@ } else if (model->getResolution() > 2) { m_peakResolution = 128; } - cacheInvalid(); + + delete m_peakCache; + m_peakCache = 0; + + invalidateRenderers(); emit modelReplaced(); emit sliceableModelReplaced(oldModel, model); @@ -100,34 +167,46 @@ void Colour3DPlotLayer::cacheInvalid() { - delete m_cache; - delete m_peaksCache; - m_cache = 0; - m_peaksCache = 0; - m_cacheValidStart = 0; - m_cacheValidEnd = 0; + invalidateRenderers(); } void -Colour3DPlotLayer::cacheInvalid(sv_frame_t startFrame, sv_frame_t endFrame) +Colour3DPlotLayer::cacheInvalid(sv_frame_t /* startFrame */, + sv_frame_t /* endFrame */) { - if (!m_cache || !m_model) return; + //!!! should do this only if the range is visible + delete m_peakCache; + m_peakCache = 0; + + invalidateRenderers(); +} - int modelResolution = m_model->getResolution(); - int start = int(startFrame / modelResolution); - int end = int(endFrame / modelResolution + 1); - if (m_cacheValidStart < end) m_cacheValidStart = end; - if (m_cacheValidEnd > start) m_cacheValidEnd = start; - if (m_cacheValidStart > m_cacheValidEnd) m_cacheValidEnd = m_cacheValidStart; +void +Colour3DPlotLayer::invalidateRenderers() +{ + for (ViewRendererMap::iterator i = m_renderers.begin(); + i != m_renderers.end(); ++i) { + delete i->second; + } + m_renderers.clear(); +} + +Dense3DModelPeakCache * +Colour3DPlotLayer::getPeakCache() const +{ + if (!m_peakCache) { + m_peakCache = new Dense3DModelPeakCache(m_model, m_peakCacheDivisor); + } + return m_peakCache; } void Colour3DPlotLayer::modelChanged() { - if (!m_colourScaleSet && m_colourScale == LinearScale) { + if (!m_colourScaleSet && m_colourScale == ColourScaleType::Linear) { if (m_model) { if (m_model->shouldUseLogValueScale()) { - setColourScale(LogScale); + setColourScale(ColourScaleType::Log); } else { m_colourScaleSet = true; } @@ -139,10 +218,10 @@ void Colour3DPlotLayer::modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame) { - if (!m_colourScaleSet && m_colourScale == LinearScale) { + if (!m_colourScaleSet && m_colourScale == ColourScaleType::Linear) { if (m_model && m_model->getWidth() > 50) { if (m_model->shouldUseLogValueScale()) { - setColourScale(LogScale); + setColourScale(ColourScaleType::Log); } else { m_colourScaleSet = true; } @@ -157,8 +236,7 @@ PropertyList list; list.push_back("Colour"); list.push_back("Colour Scale"); - list.push_back("Normalize Columns"); - list.push_back("Normalize Visible Area"); + list.push_back("Normalization"); list.push_back("Gain"); list.push_back("Bin Scale"); list.push_back("Invert Vertical Scale"); @@ -172,8 +250,7 @@ { if (name == "Colour") return tr("Colour"); if (name == "Colour Scale") return tr("Scale"); - if (name == "Normalize Columns") return tr("Normalize Columns"); - if (name == "Normalize Visible Area") return tr("Normalize Visible Area"); + if (name == "Normalization") return tr("Normalization"); if (name == "Invert Vertical Scale") return tr("Invert Vertical Scale"); if (name == "Gain") return tr("Gain"); if (name == "Opaque") return tr("Always Opaque"); @@ -185,8 +262,6 @@ QString Colour3DPlotLayer::getPropertyIconName(const PropertyName &name) const { - if (name == "Normalize Columns") return "normalise-columns"; - if (name == "Normalize Visible Area") return "normalise"; if (name == "Invert Vertical Scale") return "invert-vertical"; if (name == "Opaque") return "opaque"; if (name == "Smooth") return "smooth"; @@ -197,20 +272,18 @@ Colour3DPlotLayer::getPropertyType(const PropertyName &name) const { if (name == "Gain") return RangeProperty; - if (name == "Normalize Columns") return ToggleProperty; - if (name == "Normalize Visible Area") return ToggleProperty; if (name == "Invert Vertical Scale") return ToggleProperty; if (name == "Opaque") return ToggleProperty; if (name == "Smooth") return ToggleProperty; + if (name == "Colour") return ColourMapProperty; return ValueProperty; } QString Colour3DPlotLayer::getPropertyGroupName(const PropertyName &name) const { - if (name == "Normalize Columns" || - name == "Normalize Visible Area" || - name == "Colour Scale" || + if (name == "Normalization" || + name == "Colour Scale" || name == "Gain") return tr("Scale"); if (name == "Bin Scale" || name == "Invert Vertical Scale") return tr("Bins"); @@ -246,11 +319,12 @@ } else if (name == "Colour Scale") { + // linear, log, +/-1, abs *min = 0; *max = 3; - *deflt = (int)LinearScale; + *deflt = 0; - val = (int)m_colourScale; + val = convertFromColourScale(m_colourScale); } else if (name == "Colour") { @@ -260,15 +334,13 @@ val = m_colourMap; - } else if (name == "Normalize Columns") { + } else if (name == "Normalization") { + *min = 0; + *max = 3; *deflt = 0; - val = (m_normalizeColumns ? 1 : 0); - } else if (name == "Normalize Visible Area") { - - *deflt = 0; - val = (m_normalizeVisibleArea ? 1 : 0); + val = convertFromColumnNorm(m_normalization, m_normalizeVisibleArea); } else if (name == "Invert Vertical Scale") { @@ -279,7 +351,7 @@ *min = 0; *max = 1; - *deflt = int(LinearBinScale); + *deflt = int(BinScale::Linear); val = (int)m_binScale; } else if (name == "Opaque") { @@ -315,6 +387,16 @@ case 3: return tr("Absolute"); } } + if (name == "Normalization") { + switch(value) { + default: + case 0: return tr("None"); + case 1: return tr("Col"); + case 2: return tr("View"); + case 3: return tr("Hybrid"); + } +// return ""; // icon only + } if (name == "Bin Scale") { switch (value) { default: @@ -325,6 +407,22 @@ return tr(""); } +QString +Colour3DPlotLayer::getPropertyValueIconName(const PropertyName &name, + int value) const +{ + if (name == "Normalization") { + switch(value) { + default: + case 0: return "normalise-none"; + case 1: return "normalise-columns"; + case 2: return "normalise"; + case 3: return "normalise-hybrid"; + } + } + return ""; +} + RangeMapper * Colour3DPlotLayer::getNewPropertyRangeMapper(const PropertyName &name) const { @@ -340,19 +438,9 @@ if (name == "Gain") { setGain(float(pow(10, value/20.0))); } else if (name == "Colour Scale") { - switch (value) { - default: - case 0: setColourScale(LinearScale); break; - case 1: setColourScale(LogScale); break; - case 2: setColourScale(PlusMinusOneScale); break; - case 3: setColourScale(AbsoluteScale); break; - } + setColourScale(convertToColourScale(value)); } else if (name == "Colour") { setColourMap(value); - } else if (name == "Normalize Columns") { - setNormalizeColumns(value ? true : false); - } else if (name == "Normalize Visible Area") { - setNormalizeVisibleArea(value ? true : false); } else if (name == "Invert Vertical Scale") { setInvertVertical(value ? true : false); } else if (name == "Opaque") { @@ -362,19 +450,23 @@ } else if (name == "Bin Scale") { switch (value) { default: - case 0: setBinScale(LinearBinScale); break; - case 1: setBinScale(LogBinScale); break; + case 0: setBinScale(BinScale::Linear); break; + case 1: setBinScale(BinScale::Log); break; } + } else if (name == "Normalization") { + auto n = convertToColumnNorm(value); + setNormalization(n.first); + setNormalizeVisibleArea(n.second); } } void -Colour3DPlotLayer::setColourScale(ColourScale scale) +Colour3DPlotLayer::setColourScale(ColourScaleType scale) { if (m_colourScale == scale) return; m_colourScale = scale; m_colourScaleSet = true; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -383,7 +475,7 @@ { if (m_colourMap == map) return; m_colourMap = map; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -392,7 +484,7 @@ { if (m_gain == gain) return; m_gain = gain; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -407,52 +499,41 @@ { if (m_binScale == binScale) return; m_binScale = binScale; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } -Colour3DPlotLayer::BinScale +BinScale Colour3DPlotLayer::getBinScale() const { return m_binScale; } void -Colour3DPlotLayer::setNormalizeColumns(bool n) +Colour3DPlotLayer::setNormalization(ColumnNormalization n) { - if (m_normalizeColumns == n) return; - m_normalizeColumns = n; - cacheInvalid(); + if (m_normalization == n) return; + + m_normalization = n; + invalidateRenderers(); + emit layerParametersChanged(); } -bool -Colour3DPlotLayer::getNormalizeColumns() const +ColumnNormalization +Colour3DPlotLayer::getNormalization() const { - return m_normalizeColumns; -} - -void -Colour3DPlotLayer::setNormalizeHybrid(bool n) -{ - if (m_normalizeHybrid == n) return; - m_normalizeHybrid = n; - cacheInvalid(); - emit layerParametersChanged(); -} - -bool -Colour3DPlotLayer::getNormalizeHybrid() const -{ - return m_normalizeHybrid; + return m_normalization; } void Colour3DPlotLayer::setNormalizeVisibleArea(bool n) { if (m_normalizeVisibleArea == n) return; + m_normalizeVisibleArea = n; - cacheInvalid(); + invalidateRenderers(); + emit layerParametersChanged(); } @@ -467,7 +548,7 @@ { if (m_invertVertical == n) return; m_invertVertical = n; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -476,6 +557,7 @@ { if (m_opaque == n) return; m_opaque = n; + invalidateRenderers(); emit layerParametersChanged(); } @@ -484,6 +566,7 @@ { if (m_smooth == n) return; m_smooth = n; + invalidateRenderers(); emit layerParametersChanged(); } @@ -506,7 +589,7 @@ } void -Colour3DPlotLayer::setLayerDormant(const View *v, bool dormant) +Colour3DPlotLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant) { if (dormant) { @@ -530,16 +613,19 @@ } bool -Colour3DPlotLayer::isLayerScrollable(const View *v) const +Colour3DPlotLayer::isLayerScrollable(const LayerGeometryProvider * /* v */) const { if (m_normalizeVisibleArea) { return false; } - if (shouldPaintDenseIn(v)) { - return true; - } - QPoint discard; - return !v->shouldIlluminateLocalFeatures(this, discard); + //!!! ah hang on, if we're potentially rendering incrementally + //!!! they we can't be scrollable + return false; +// if (getRenderer(v)->willRenderOpaque(v)) { +// return true; +// } +// QPoint discard; +// return !v->shouldIlluminateLocalFeatures(this, discard); } bool @@ -584,12 +670,14 @@ m_miny = int(lrint(min)); m_maxy = int(lrint(max)); + invalidateRenderers(); + emit layerParametersChanged(); return true; } bool -Colour3DPlotLayer::getYScaleValue(const View *, int, +Colour3DPlotLayer::getYScaleValue(const LayerGeometryProvider *, int, double &, QString &) const { return false;//!!! @@ -630,6 +718,8 @@ m_maxy = m_miny + dist; if (m_maxy > m_model->getHeight()) m_maxy = m_model->getHeight(); + invalidateRenderers(); + // SVDEBUG << "Colour3DPlotLayer::setVerticalZoomStep(" < sh) symax = sh; - // double binHeight = double(v->height()) / (symax - symin); -// int sy = int((v->height() - y) / binHeight) + symin; + // double binHeight = double(v->getPaintHeight()) / (symax - symin); +// int sy = int((v->getPaintHeight() - y) / binHeight) + symin; int sy = getIBinForY(v, y); @@ -755,14 +833,15 @@ } int -Colour3DPlotLayer::getColourScaleWidth(QPainter &) const +Colour3DPlotLayer::getColourScaleWidth(QPainter &p) const { - int cw = 20; + // Font is rotated + int cw = p.fontMetrics().height(); return cw; } int -Colour3DPlotLayer::getVerticalScaleWidth(View *, bool, QPainter &paint) const +Colour3DPlotLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const { if (!m_model) return 0; @@ -784,7 +863,7 @@ } void -Colour3DPlotLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const +Colour3DPlotLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const { if (!m_model) return; @@ -793,49 +872,20 @@ int cw = getColourScaleWidth(paint); int ch = h - 20; - if (ch > 20 && m_cache) { + if (ch > 20) { - double min = m_model->getMinimumLevel(); - double max = m_model->getMaximumLevel(); + double min = m_viewMags[v->getId()].getMin(); + double max = m_viewMags[v->getId()].getMax(); - double mmin = min; - double mmax = max; + if (max <= min) max = min + 0.1; - if (m_colourScale == LogScale) { - LogRange::mapRange(mmin, mmax); - } else if (m_colourScale == PlusMinusOneScale) { - mmin = -1.f; - mmax = 1.f; - } else if (m_colourScale == AbsoluteScale) { - if (mmin < 0) { - if (fabs(mmin) > fabs(mmax)) mmax = fabs(mmin); - else mmax = fabs(mmax); - mmin = 0; - } else { - mmin = fabs(mmin); - mmax = fabs(mmax); - } - } - - if (max == min) max = min + 1.f; - if (mmax == mmin) mmax = mmin + 1.f; - paint.setPen(v->getForeground()); paint.drawRect(4, 10, cw - 8, ch+1); for (int y = 0; y < ch; ++y) { double value = ((max - min) * (double(ch-y) - 1.0)) / double(ch) + min; - if (m_colourScale == LogScale) { - value = LogRange::map(value); - } - int pixel = int(((value - mmin) * 256) / (mmax - mmin)); - if (pixel >= 0 && pixel < 256) { - QRgb c = m_cache->color(pixel); - paint.setPen(QColor(qRed(c), qGreen(c), qBlue(c))); - paint.drawLine(5, 11 + y, cw - 5, 11 + y); - } else { - cerr << "WARNING: Colour3DPlotLayer::paintVerticalScale: value " << value << ", mmin " << mmin << ", mmax " << mmax << " leads to invalid pixel " << pixel << endl; - } + paint.setPen(getRenderer(v)->getColour(value)); + paint.drawLine(5, 11 + y, cw - 5, 11 + y); } QString minstr = QString("%1").arg(min); @@ -844,8 +894,12 @@ paint.save(); QFont font = paint.font(); - font.setPixelSize(10); - paint.setFont(font); + if (font.pixelSize() > 0) { + int newSize = int(font.pixelSize() * 0.65); + if (newSize < 6) newSize = 6; + font.setPixelSize(newSize); + paint.setFont(font); + } int msw = paint.fontMetrics().width(maxstr); @@ -855,12 +909,12 @@ paint.setWorldMatrix(m); - v->drawVisibleText(paint, 2, 0, minstr, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, 2, 0, minstr, PaintAssistant::OutlinedText); m.translate(ch - msw - 2, 0); paint.setWorldMatrix(m); - v->drawVisibleText(paint, 0, 0, maxstr, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, 0, 0, maxstr, PaintAssistant::OutlinedText); paint.restore(); } @@ -882,6 +936,8 @@ int py = h; + int defaultFontHeight = paint.fontMetrics().height(); + for (int i = symin; i <= symax; ++i) { int y0; @@ -891,9 +947,9 @@ if (i > symin) { if (paint.fontMetrics().height() >= h) { - if (h >= 8) { + if (h >= defaultFontHeight * 0.8) { QFont tf = paint.font(); - tf.setPixelSize(h-2); + tf.setPixelSize(int(h * 0.8)); paint.setFont(tf); } else { continue; @@ -931,22 +987,27 @@ Profiler profiler("Colour3DPlotLayer::getColumn"); DenseThreeDimensionalModel::Column values = m_model->getColumn(col); - while (values.size() < m_model->getHeight()) values.push_back(0.f); - if (!m_normalizeColumns && !m_normalizeHybrid) return values; + values.resize(m_model->getHeight(), 0.f); + if (m_normalization != ColumnNormalization::Max1 && + m_normalization != ColumnNormalization::Hybrid) { + return values; + } double colMax = 0.f, colMin = 0.f; double min = 0.f, max = 0.f; + int nv = int(values.size()); + min = m_model->getMinimumLevel(); max = m_model->getMaximumLevel(); - for (int y = 0; y < values.size(); ++y) { + for (int y = 0; y < nv; ++y) { if (y == 0 || values.at(y) > colMax) colMax = values.at(y); if (y == 0 || values.at(y) < colMin) colMin = values.at(y); } if (colMin == colMax) colMax = colMin + 1; - for (int y = 0; y < values.size(); ++y) { + for (int y = 0; y < nv; ++y) { double value = values.at(y); double norm = (value - colMin) / (colMax - colMin); @@ -955,317 +1016,106 @@ if (value != newvalue) values[y] = float(newvalue); } - if (m_normalizeHybrid && (colMax > 0.0)) { + if (m_normalization == ColumnNormalization::Hybrid + && (colMax > 0.0)) { double logmax = log10(colMax); - for (int y = 0; y < values.size(); ++y) { + for (int y = 0; y < nv; ++y) { values[y] = float(values[y] * logmax); } } return values; } - -void -Colour3DPlotLayer::fillCache(int firstColumn, int lastColumn) const + +Colour3DPlotRenderer * +Colour3DPlotLayer::getRenderer(const LayerGeometryProvider *v) const { - // This call requests a (perhaps partial) fill of the cache - // between model columns firstColumn and lastColumn inclusive. - // The cache itself always has size sufficient to contain the - // whole model, but its validity may be less, depending on which - // regions have been requested via calls to this function. Note - // that firstColumn and lastColumn are *model* column numbers. If - // the model starts at a frame > 0, a firstColumn of zero still - // corresponds to the first column in the model, not the first - // column on the resulting rendered layer. + if (m_renderers.find(v->getId()) == m_renderers.end()) { - Profiler profiler("Colour3DPlotLayer::fillCache", true); + Colour3DPlotRenderer::Sources sources; + sources.verticalBinLayer = this; + sources.fft = 0; + sources.source = m_model; + sources.peakCaches.push_back(getPeakCache()); - int cacheWidth = m_model->getWidth(); - int cacheHeight = m_model->getHeight(); + ColourScale::Parameters cparams; + cparams.colourMap = m_colourMap; + cparams.scaleType = m_colourScale; + cparams.gain = m_gain; -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::fillCache: range " << firstColumn << " -> " << lastColumn << " (cache size will be " << cacheWidth << " x " << cacheHeight << ")" << endl; -#endif + if (m_normalization == ColumnNormalization::None) { + cparams.minValue = m_model->getMinimumLevel(); + cparams.maxValue = m_model->getMaximumLevel(); + } else if (m_normalization == ColumnNormalization::Hybrid) { + cparams.minValue = 0; + cparams.maxValue = log10(m_model->getMaximumLevel() + 1.0); + } - if (m_cache && m_cache->height() != cacheHeight) { - // height has changed: delete everything rather than resizing -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::fillCache: Cache height has changed, recreating" << endl; -#endif - delete m_cache; - delete m_peaksCache; - m_cache = 0; - m_peaksCache = 0; - } + if (cparams.maxValue <= cparams.minValue) { + cparams.maxValue = cparams.minValue + 0.1; + } + + Colour3DPlotRenderer::Parameters params; + params.colourScale = ColourScale(cparams); + params.normalization = m_normalization; + params.binScale = m_binScale; + params.alwaysOpaque = m_opaque; + params.invertVertical = m_invertVertical; + params.interpolate = m_smooth; - if (m_cache && m_cache->width() != cacheWidth) { - // width has changed and we have an existing cache: resize it -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::fillCache: Cache width has changed, resizing existing cache" << endl; -#endif - QImage *newCache = - new QImage(m_cache->copy(0, 0, cacheWidth, cacheHeight)); - delete m_cache; - m_cache = newCache; - if (m_peaksCache) { - QImage *newPeaksCache = - new QImage(m_peaksCache->copy - (0, 0, cacheWidth / m_peakResolution + 1, cacheHeight)); - delete m_peaksCache; - m_peaksCache = newPeaksCache; - } + m_renderers[v->getId()] = new Colour3DPlotRenderer(sources, params); } - if (!m_cache) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::fillCache: Have no cache, making one" << endl; -#endif - m_cache = new QImage(cacheWidth, cacheHeight, QImage::Format_Indexed8); - m_cache->setColorCount(256); - m_cache->fill(0); - if (!m_normalizeVisibleArea) { - m_peaksCache = new QImage - (cacheWidth / m_peakResolution + 1, cacheHeight, - QImage::Format_Indexed8); - m_peaksCache->setColorCount(256); - m_peaksCache->fill(0); - } else if (m_peaksCache) { - delete m_peaksCache; - m_peaksCache = 0; - } - m_cacheValidStart = 0; - m_cacheValidEnd = 0; - } + return m_renderers[v->getId()]; +} -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "cache size = " << m_cache->width() << "x" << m_cache->height() - << " peaks cache size = " << m_peaksCache->width() << "x" << m_peaksCache->height() << endl; -#endif +void +Colour3DPlotLayer::paintWithRenderer(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const +{ + Colour3DPlotRenderer *renderer = getRenderer(v); - if (m_cacheValidStart <= firstColumn && m_cacheValidEnd >= lastColumn) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Cache is valid in this region already" << endl; -#endif - return; + Colour3DPlotRenderer::RenderResult result; + MagnitudeRange magRange; + int viewId = v->getId(); + + if (!renderer->geometryChanged(v)) { + magRange = m_viewMags[viewId]; } - int fillStart = firstColumn; - int fillEnd = lastColumn; + if (m_synchronous) { - if (fillStart >= cacheWidth) fillStart = cacheWidth-1; - if (fillStart < 0) fillStart = 0; - if (fillEnd >= cacheWidth) fillEnd = cacheWidth-1; - if (fillEnd < 0) fillEnd = 0; - if (fillEnd < fillStart) fillEnd = fillStart; - - bool normalizeVisible = (m_normalizeVisibleArea && !m_normalizeColumns); - - if (!normalizeVisible && (m_cacheValidStart < m_cacheValidEnd)) { - - if (m_cacheValidEnd < fillStart) { - fillStart = m_cacheValidEnd + 1; - } - if (m_cacheValidStart > fillEnd) { - fillEnd = m_cacheValidStart - 1; - } - - m_cacheValidStart = std::min(fillStart, m_cacheValidStart); - m_cacheValidEnd = std::max(fillEnd, m_cacheValidEnd); + result = renderer->render(v, paint, rect); } else { - // when normalising the visible area, the only valid area, - // ever, is the currently visible one + result = renderer->renderTimeConstrained(v, paint, rect); - m_cacheValidStart = fillStart; - m_cacheValidEnd = fillEnd; - } - -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Cache size " << cacheWidth << "x" << cacheHeight << " will be valid from " << m_cacheValidStart << " to " << m_cacheValidEnd << " (fillStart = " << fillStart << ", fillEnd = " << fillEnd << ")" << endl; -#endif - - DenseThreeDimensionalModel::Column values; - - double min = m_model->getMinimumLevel(); - double max = m_model->getMaximumLevel(); - - if (m_colourScale == LogScale) { - LogRange::mapRange(min, max); - } else if (m_colourScale == PlusMinusOneScale) { - min = -1.f; - max = 1.f; - } else if (m_colourScale == AbsoluteScale) { - if (min < 0) { - if (fabs(min) > fabs(max)) max = fabs(min); - else max = fabs(max); - min = 0; - } else { - min = fabs(min); - max = fabs(max); + QRect uncached = renderer->getLargestUncachedRect(v); + if (uncached.width() > 0) { + v->updatePaintRect(uncached); } } - if (max == min) max = min + 1.f; - - ColourMapper mapper(m_colourMap, 0.f, 255.f); - - for (int index = 0; index < 256; ++index) { - QColor colour = mapper.map(index); - m_cache->setColor - (index, qRgb(colour.red(), colour.green(), colour.blue())); - if (m_peaksCache) { - m_peaksCache->setColor - (index, qRgb(colour.red(), colour.green(), colour.blue())); + magRange.sample(result.range); + + if (magRange.isSet()) { + if (!(m_viewMags[viewId] == magRange)) { + m_viewMags[viewId] = magRange; + //!!! now need to do the normalise-visible thing } } - double visibleMax = 0.f, visibleMin = 0.f; - - if (normalizeVisible) { + cerr << "mag range in this view: " + << m_viewMags[v->getId()].getMin() + << " -> " + << m_viewMags[v->getId()].getMax() + << endl; - for (int c = fillStart; c <= fillEnd; ++c) { - - values = getColumn(c); - - double colMax = 0.f, colMin = 0.f; - - for (int y = 0; y < cacheHeight; ++y) { - if (y >= values.size()) break; - if (y == 0 || values[y] > colMax) colMax = values[y]; - if (y == 0 || values[y] < colMin) colMin = values[y]; - } - - if (c == fillStart || colMax > visibleMax) visibleMax = colMax; - if (c == fillStart || colMin < visibleMin) visibleMin = colMin; - } - - if (m_colourScale == LogScale) { - visibleMin = LogRange::map(visibleMin); - visibleMax = LogRange::map(visibleMax); - if (visibleMin > visibleMax) std::swap(visibleMin, visibleMax); - } else if (m_colourScale == AbsoluteScale) { - if (visibleMin < 0) { - if (fabs(visibleMin) > fabs(visibleMax)) visibleMax = fabs(visibleMin); - else visibleMax = fabs(visibleMax); - visibleMin = 0; - } else { - visibleMin = fabs(visibleMin); - visibleMax = fabs(visibleMax); - } - } - } - - if (visibleMin == visibleMax) visibleMax = visibleMin + 1; - - int *peaks = 0; - if (m_peaksCache) { - peaks = new int[cacheHeight]; - for (int y = 0; y < cacheHeight; ++y) { - peaks[y] = 0; - } - } - - Profiler profiler2("Colour3DPlotLayer::fillCache: filling", true); - - for (int c = fillStart; c <= fillEnd; ++c) { - - values = getColumn(c); - - if (c >= m_cache->width()) { - cerr << "ERROR: column " << c << " >= cache width " - << m_cache->width() << endl; - continue; - } - - for (int y = 0; y < cacheHeight; ++y) { - - double value = min; - if (y < values.size()) { - value = values.at(y); - } - - value = value * m_gain; - - if (m_colourScale == LogScale) { - value = LogRange::map(value); - } else if (m_colourScale == AbsoluteScale) { - value = fabs(value); - } - - if (normalizeVisible) { - double norm = (value - visibleMin) / (visibleMax - visibleMin); - value = min + (max - min) * norm; - } - - int pixel = int(((value - min) * 256) / (max - min)); - if (pixel < 0) pixel = 0; - if (pixel > 255) pixel = 255; - if (peaks && (pixel > peaks[y])) peaks[y] = pixel; - - if (m_invertVertical) { - m_cache->setPixel(c, cacheHeight - y - 1, pixel); - } else { - if (y >= m_cache->height()) { - cerr << "ERROR: row " << y << " >= cache height " << m_cache->height() << endl; - } else { - m_cache->setPixel(c, y, pixel); - } - } - } - - if (peaks) { - int notch = (c % m_peakResolution); - if (notch == m_peakResolution-1 || c == fillEnd) { - int pc = c / m_peakResolution; - if (pc >= m_peaksCache->width()) { - cerr << "ERROR: peak column " << pc - << " (from col " << c << ") >= peaks cache width " - << m_peaksCache->width() << endl; - continue; - } - for (int y = 0; y < cacheHeight; ++y) { - if (m_invertVertical) { - m_peaksCache->setPixel(pc, cacheHeight - y - 1, peaks[y]); - } else { - if (y >= m_peaksCache->height()) { - cerr << "ERROR: row " << y - << " >= peaks cache height " - << m_peaksCache->height() << endl; - } else { - m_peaksCache->setPixel(pc, y, peaks[y]); - } - } - } - for (int y = 0; y < cacheHeight; ++y) { - peaks[y] = 0; - } - } - } - } - - delete[] peaks; -} - -bool -Colour3DPlotLayer::shouldPaintDenseIn(const View *v) const -{ - if (!m_model || !v || !(v->getViewManager())) { - return false; - } - double srRatio = - v->getViewManager()->getMainModelSampleRate() / m_model->getSampleRate(); - if (m_opaque || - m_smooth || - m_model->getHeight() >= v->height() || - ((m_model->getResolution() * srRatio) / v->getZoomLevel()) < 2) { - return true; - } - return false; } void -Colour3DPlotLayer::paint(View *v, QPainter &paint, QRect rect) const +Colour3DPlotLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { /* if (m_model) { @@ -1280,387 +1130,35 @@ int completion = 0; if (!m_model || !m_model->isOK() || !m_model->isReady(&completion)) { if (completion > 0) { - paint.fillRect(0, 10, v->width() * completion / 100, + paint.fillRect(0, 10, v->getPaintWidth() * completion / 100, 10, QColor(120, 120, 120)); } return; } - if (m_normalizeVisibleArea && !m_normalizeColumns) rect = v->rect(); - - sv_frame_t modelStart = m_model->getStartFrame(); - sv_frame_t modelEnd = m_model->getEndFrame(); - int modelResolution = m_model->getResolution(); - - // The cache is from the model's start frame to the model's end - // frame at the model's window increment frames per pixel. We - // want to draw from our start frame + x0 * zoomLevel to our start - // frame + x1 * zoomLevel at zoomLevel frames per pixel. - - // We have quite different paint mechanisms for rendering "large" - // bins (more than one bin per pixel in both directions) and - // "small". This is "large"; see paintDense below for "small". - - int x0 = rect.left(); - int x1 = rect.right() + 1; - - int h = v->height(); - - double srRatio = - v->getViewManager()->getMainModelSampleRate() / m_model->getSampleRate(); - - // the s-prefix values are source, i.e. model, column and bin numbers - int sx0 = int((double(v->getFrameForX(x0)) / srRatio - double(modelStart)) - / modelResolution); - int sx1 = int((double(v->getFrameForX(x1)) / srRatio - double(modelStart)) - / modelResolution); - int sh = m_model->getHeight(); - - int symin = m_miny; - int symax = m_maxy; - if (symax <= symin) { - symin = 0; - symax = sh; - } - if (symin < 0) symin = 0; - if (symax > sh) symax = sh; - - if (sx0 > 0) --sx0; - fillCache(sx0 < 0 ? 0 : sx0, - sx1 < 0 ? 0 : sx1); - + if (m_model->getWidth() == 0) { #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::paint: height = "<< m_model->getHeight() << ", modelStart = " << modelStart << ", resolution = " << modelResolution << ", model rate = " << m_model->getSampleRate() << " (zoom level = " << v->getZoomLevel() << ", srRatio = " << srRatio << ")" << endl; + cerr << "Colour3DPlotLayer::paint(): model width == 0, " + << "nothing to paint (yet)" << endl; #endif - - if (shouldPaintDenseIn(v)) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "calling paintDense" << endl; -#endif - paintDense(v, paint, rect); return; } -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::paint: w " << x1-x0 << ", h " << h << ", sx0 " << sx0 << ", sx1 " << sx1 << ", sw " << sx1-sx0 << ", sh " << sh << endl; - cerr << "Colour3DPlotLayer: sample rate is " << m_model->getSampleRate() << ", resolution " << m_model->getResolution() << endl; -#endif - - QPoint illuminatePos; - bool illuminate = v->shouldIlluminateLocalFeatures(this, illuminatePos); + //!!!??? - const int buflen = 40; - char labelbuf[buflen]; - - for (int sx = sx0; sx <= sx1; ++sx) { - - sv_frame_t fx = sx * modelResolution + modelStart; - - if (fx + modelResolution <= modelStart || fx > modelEnd) continue; - - int rx0 = v->getXForFrame(int(double(fx) * srRatio)); - int rx1 = v->getXForFrame(int(double(fx + modelResolution + 1) * srRatio)); - - int rw = rx1 - rx0; - if (rw < 1) rw = 1; - - bool showLabel = (rw > 10 && - paint.fontMetrics().width("0.000000") < rw - 3 && - paint.fontMetrics().height() < (h / sh)); - - for (int sy = symin; sy < symax; ++sy) { - - int ry0 = getIYForBin(v, sy); - int ry1 = getIYForBin(v, sy + 1); - QRect r(rx0, ry1, rw, ry0 - ry1); - - QRgb pixel = qRgb(255, 255, 255); - if (sx >= 0 && sx < m_cache->width() && - sy >= 0 && sy < m_cache->height()) { - pixel = m_cache->pixel(sx, sy); - } - - if (rw == 1) { - paint.setPen(pixel); - paint.setBrush(Qt::NoBrush); - paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1); - continue; - } - - QColor pen(255, 255, 255, 80); - QColor brush(pixel); - - if (rw > 3 && r.height() > 3) { - brush.setAlpha(160); - } - - paint.setPen(Qt::NoPen); - paint.setBrush(brush); - - if (illuminate) { - if (r.contains(illuminatePos)) { - paint.setPen(v->getForeground()); - } - } - -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT -// cerr << "rect " << r.x() << "," << r.y() << " " -// << r.width() << "x" << r.height() << endl; -#endif - - paint.drawRect(r); - - if (showLabel) { - if (sx >= 0 && sx < m_cache->width() && - sy >= 0 && sy < m_cache->height()) { - double value = m_model->getValueAt(sx, sy); - snprintf(labelbuf, buflen, "%06f", value); - QString text(labelbuf); - v->drawVisibleText - (paint, - rx0 + 2, - ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(), - text, - View::OutlinedText); - } - } - } - } -} - -void -Colour3DPlotLayer::paintDense(View *v, QPainter &paint, QRect rect) const -{ - Profiler profiler("Colour3DPlotLayer::paintDense", true); - if (!m_cache) return; - - double modelStart = double(m_model->getStartFrame()); - double modelResolution = double(m_model->getResolution()); - - sv_samplerate_t mmsr = v->getViewManager()->getMainModelSampleRate(); - sv_samplerate_t msr = m_model->getSampleRate(); - double srRatio = mmsr / msr; - - int x0 = rect.left(); - int x1 = rect.right() + 1; - - const int w = x1 - x0; // const so it can be used as array size below - int h = v->height(); // we always paint full height - int sh = m_model->getHeight(); - - int symin = m_miny; - int symax = m_maxy; - if (symax <= symin) { - symin = 0; - symax = sh; - } - if (symin < 0) symin = 0; - if (symax > sh) symax = sh; - - QImage img(w, h, QImage::Format_Indexed8); - img.setColorTable(m_cache->colorTable()); - - uchar *peaks = new uchar[w]; - memset(peaks, 0, w); - - int zoomLevel = v->getZoomLevel(); - - QImage *source = m_cache; - -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "modelResolution " << modelResolution << ", srRatio " - << srRatio << ", m_peakResolution " << m_peakResolution - << ", zoomLevel " << zoomLevel << ", result " - << ((modelResolution * srRatio * m_peakResolution) / zoomLevel) - << endl; -#endif - - if (m_peaksCache) { - if (((modelResolution * srRatio * m_peakResolution) / zoomLevel) < 1) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "using peaks cache" << endl; -#endif - source = m_peaksCache; - modelResolution *= m_peakResolution; - } else { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "not using peaks cache" << endl; -#endif - } - } else { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "have no peaks cache" << endl; -#endif + if (m_normalizeVisibleArea) { + rect = v->getPaintRect(); } - int sw = source->width(); - - sv_frame_t xf = -1; - sv_frame_t nxf = v->getFrameForX(x0); + //!!! why is the setLayerDormant(false) found here in + //!!! SpectrogramLayer not present in Colour3DPlotLayer? + //!!! unnecessary? vestigial? forgotten? - double epsilon = 0.000001; - - vector sxa(w*2); - - for (int x = 0; x < w; ++x) { - - xf = nxf; - nxf = xf + zoomLevel; - - double sx0 = (double(xf) / srRatio - modelStart) / modelResolution; - double sx1 = (double(nxf) / srRatio - modelStart) / modelResolution; - - sxa[x*2] = sx0; - sxa[x*2 + 1] = sx1; - } - - double logmin = symin+1, logmax = symax+1; - LogRange::mapRange(logmin, logmax); - -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "m_smooth = " << m_smooth << ", w = " << w << ", h = " << h << endl; -#endif - - if (m_smooth) { - - for (int y = 0; y < h; ++y) { - - double sy = getBinForY(v, y) - 0.5; - int syi = int(sy + epsilon); - if (syi < 0 || syi >= source->height()) continue; - - uchar *targetLine = img.scanLine(y); - uchar *sourceLine = source->scanLine(syi); - uchar *nextSource; - if (syi + 1 < source->height()) { - nextSource = source->scanLine(syi + 1); - } else { - nextSource = sourceLine; - } - - for (int x = 0; x < w; ++x) { - - targetLine[x] = 0; - - double sx0 = sxa[x*2]; - if (sx0 < 0) continue; - int sx0i = int(sx0 + epsilon); - if (sx0i >= sw) break; - - double a = sourceLine[sx0i]; - double b = a; - double value; - - double sx1 = sxa[x*2+1]; - if (sx1 > sx0 + 1.f) { - int sx1i = int(sx1); - bool have = false; - for (int sx = sx0i; sx <= sx1i; ++sx) { - if (sx < 0 || sx >= sw) continue; - if (!have) { - a = sourceLine[sx]; - b = nextSource[sx]; - have = true; - } else { - a = std::max(a, double(sourceLine[sx])); - b = std::max(b, double(nextSource[sx])); - } - } - double yprop = sy - syi; - value = (a * (1.f - yprop) + b * yprop); - } else { - a = sourceLine[sx0i]; - b = nextSource[sx0i]; - double yprop = sy - syi; - value = (a * (1.f - yprop) + b * yprop); - int oi = sx0i + 1; - double xprop = sx0 - sx0i; - xprop -= 0.5; - if (xprop < 0) { - oi = sx0i - 1; - xprop = -xprop; - } - if (oi < 0 || oi >= sw) oi = sx0i; - a = sourceLine[oi]; - b = nextSource[oi]; - value = (value * (1.f - xprop) + - (a * (1.f - yprop) + b * yprop) * xprop); - } - - int vi = int(lrint(value)); - if (vi > 255) vi = 255; - if (vi < 0) vi = 0; - targetLine[x] = uchar(vi); - } - } - } else { - - double sy0 = getBinForY(v, 0); - - int psy0i = -1, psy1i = -1; - - for (int y = 0; y < h; ++y) { - - double sy1 = sy0; - sy0 = getBinForY(v, double(y + 1)); - - int sy0i = int(sy0 + epsilon); - int sy1i = int(sy1); - - uchar *targetLine = img.scanLine(y); - - if (sy0i == psy0i && sy1i == psy1i) { - // same source scan line as just computed - goto copy; - } - - psy0i = sy0i; - psy1i = sy1i; - - for (int x = 0; x < w; ++x) { - peaks[x] = 0; - } - - for (int sy = sy0i; sy <= sy1i; ++sy) { - - if (sy < 0 || sy >= source->height()) continue; - - uchar *sourceLine = source->scanLine(sy); - - for (int x = 0; x < w; ++x) { - - double sx1 = sxa[x*2 + 1]; - if (sx1 < 0) continue; - int sx1i = int(sx1); - - double sx0 = sxa[x*2]; - if (sx0 < 0) continue; - int sx0i = int(sx0 + epsilon); - if (sx0i >= sw) break; - - uchar peak = 0; - for (int sx = sx0i; sx <= sx1i; ++sx) { - if (sx < 0 || sx >= sw) continue; - if (sourceLine[sx] > peak) peak = sourceLine[sx]; - } - peaks[x] = peak; - } - } - - copy: - for (int x = 0; x < w; ++x) { - targetLine[x] = peaks[x]; - } - } - } - - delete[] peaks; - - paint.drawImage(x0, 0, img); + paintWithRenderer(v, paint, rect); } bool -Colour3DPlotLayer::snapToFeatureFrame(View *v, sv_frame_t &frame, +Colour3DPlotLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { @@ -1691,25 +1189,40 @@ { QString s = QString("scale=\"%1\" " "colourScheme=\"%2\" " - "normalizeColumns=\"%3\" " - "normalizeVisibleArea=\"%4\" " - "minY=\"%5\" " - "maxY=\"%6\" " - "invertVertical=\"%7\" " - "opaque=\"%8\" %9") - .arg((int)m_colourScale) + "minY=\"%3\" " + "maxY=\"%4\" " + "invertVertical=\"%5\" " + "opaque=\"%6\" %7") + .arg(convertFromColourScale(m_colourScale)) .arg(m_colourMap) - .arg(m_normalizeColumns ? "true" : "false") - .arg(m_normalizeVisibleArea ? "true" : "false") .arg(m_miny) .arg(m_maxy) .arg(m_invertVertical ? "true" : "false") .arg(m_opaque ? "true" : "false") .arg(QString("binScale=\"%1\" smooth=\"%2\" gain=\"%3\" ") - .arg((int)m_binScale) + .arg(int(m_binScale)) .arg(m_smooth ? "true" : "false") .arg(m_gain)); + // New-style normalization attributes, allowing for more types of + // normalization in future: write out the column normalization + // type separately, and then whether we are normalizing visible + // area as well afterwards + + s += QString("columnNormalization=\"%1\" ") + .arg(m_normalization == ColumnNormalization::Max1 ? "peak" : + m_normalization == ColumnNormalization::Hybrid ? "hybrid" : "none"); + + // Old-style normalization attribute, for backward compatibility + + s += QString("normalizeColumns=\"%1\" ") + .arg(m_normalization == ColumnNormalization::Max1 ? "true" : "false"); + + // And this applies to both old- and new-style attributes + + s += QString("normalizeVisibleArea=\"%1\" ") + .arg(m_normalizeVisibleArea ? "true" : "false"); + Layer::toXml(stream, indent, extraAttributes + " " + s); } @@ -1718,22 +1231,16 @@ { bool ok = false, alsoOk = false; - ColourScale scale = (ColourScale)attributes.value("scale").toInt(&ok); - if (ok) setColourScale(scale); + ColourScaleType colourScale = convertToColourScale + (attributes.value("colourScale").toInt(&ok)); + if (ok) setColourScale(colourScale); int colourMap = attributes.value("colourScheme").toInt(&ok); if (ok) setColourMap(colourMap); - BinScale binscale = (BinScale)attributes.value("binScale").toInt(&ok); - if (ok) setBinScale(binscale); - - bool normalizeColumns = - (attributes.value("normalizeColumns").trimmed() == "true"); - setNormalizeColumns(normalizeColumns); - - bool normalizeVisibleArea = - (attributes.value("normalizeVisibleArea").trimmed() == "true"); - setNormalizeVisibleArea(normalizeVisibleArea); + BinScale binScale = (BinScale) + attributes.value("binScale").toInt(&ok); + if (ok) setBinScale(binScale); bool invertVertical = (attributes.value("invertVertical").trimmed() == "true"); @@ -1753,5 +1260,50 @@ float min = attributes.value("minY").toFloat(&ok); float max = attributes.value("maxY").toFloat(&alsoOk); if (ok && alsoOk) setDisplayExtents(min, max); + + bool haveNewStyleNormalization = false; + + QString columnNormalization = attributes.value("columnNormalization"); + + if (columnNormalization != "") { + + haveNewStyleNormalization = true; + + if (columnNormalization == "peak") { + setNormalization(ColumnNormalization::Max1); + } else if (columnNormalization == "hybrid") { + setNormalization(ColumnNormalization::Hybrid); + } else if (columnNormalization == "none") { + setNormalization(ColumnNormalization::None); + } else { + cerr << "NOTE: Unknown or unsupported columnNormalization attribute \"" + << columnNormalization << "\"" << endl; + } + } + + if (!haveNewStyleNormalization) { + + setNormalization(ColumnNormalization::None); + + bool normalizeColumns = + (attributes.value("normalizeColumns").trimmed() == "true"); + if (normalizeColumns) { + setNormalization(ColumnNormalization::Max1); + } + + bool normalizeHybrid = + (attributes.value("normalizeHybrid").trimmed() == "true"); + if (normalizeHybrid) { + setNormalization(ColumnNormalization::Hybrid); + } + } + + bool normalizeVisibleArea = + (attributes.value("normalizeVisibleArea").trimmed() == "true"); + setNormalizeVisibleArea(normalizeVisibleArea); + + //!!! todo: check save/reload scaling, compare with + //!!! SpectrogramLayer, compare with prior SV versions, compare + //!!! with Tony v1 and v2 and their save files } diff -r e8102ff5573b -r dc2af6616c83 layer/Colour3DPlotLayer.h --- a/layer/Colour3DPlotLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/Colour3DPlotLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -13,10 +13,14 @@ COPYING included with this distribution for more information. */ -#ifndef _COLOUR_3D_PLOT_H_ -#define _COLOUR_3D_PLOT_H_ +#ifndef COLOUR_3D_PLOT_LAYER_H +#define COLOUR_3D_PLOT_LAYER_H #include "SliceableLayer.h" +#include "VerticalBinLayer.h" + +#include "ColourScale.h" +#include "Colour3DPlotRenderer.h" #include "data/model/DenseThreeDimensionalModel.h" @@ -30,14 +34,11 @@ * colour range. Its source is a DenseThreeDimensionalModel. * * This was the original implementation for the spectrogram view, but - * it was replaced with a more efficient implementation that derived - * the spectrogram itself from a DenseTimeValueModel instead of using - * a three-dimensional model. This class is retained in case it - * becomes useful, but it will probably need some cleaning up if it's - * ever actually used. + * it was replaced for that purpose with a more efficient + * implementation that derived the spectrogram itself from a + * DenseTimeValueModel instead of using a three-dimensional model. */ - -class Colour3DPlotLayer : public SliceableLayer +class Colour3DPlotLayer : public VerticalBinLayer { Q_OBJECT @@ -49,20 +50,21 @@ return m_model ? m_model->getZoomConstraint() : 0; } virtual const Model *getModel() const { return m_model; } - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; + virtual void setSynchronousPainting(bool synchronous); - virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const; - virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const; + virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const; + virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; - virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame, + virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual void setLayerDormant(const View *v, bool dormant); + virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant); - virtual bool isLayerScrollable(const View *v) const; + virtual bool isLayerScrollable(const LayerGeometryProvider *v) const; virtual ColourSignificance getLayerColourSignificance() const { return ColourHasMeaningfulValue; @@ -70,7 +72,7 @@ void setModel(const DenseThreeDimensionalModel *model); - virtual int getCompletion(View *) const { return m_model->getCompletion(); } + virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); } virtual PropertyList getProperties() const; virtual PropertyType getPropertyType(const PropertyName &) const; @@ -81,19 +83,14 @@ int *min, int *max, int *deflt) const; virtual QString getPropertyValueLabel(const PropertyName &, int value) const; + virtual QString getPropertyValueIconName(const PropertyName &, + int value) const; virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const; virtual void setProperty(const PropertyName &, int value); virtual void setProperties(const QXmlAttributes &); - enum ColourScale { - LinearScale, - LogScale, - PlusMinusOneScale, - AbsoluteScale - }; - - void setColourScale(ColourScale); - ColourScale getColourScale() const { return m_colourScale; } + void setColourScale(ColourScaleType); + ColourScaleType getColourScale() const { return m_colourScale; } void setColourMap(int map); int getColourMap() const; @@ -104,11 +101,6 @@ */ void setGain(float gain); float getGain() const; - - enum BinScale { - LinearBinScale, - LogBinScale - }; /** * Specify the scale for the y axis. @@ -117,25 +109,17 @@ BinScale getBinScale() const; /** - * Normalize each column to its maximum value, independent of its - * neighbours. + * Specify the normalization mode for individual columns. */ - void setNormalizeColumns(bool n); - bool getNormalizeColumns() const; + void setNormalization(ColumnNormalization); + ColumnNormalization getNormalization() const; /** - * Normalize each value against the maximum in the visible region. + * Specify whether to normalize the visible area. */ - void setNormalizeVisibleArea(bool n); + void setNormalizeVisibleArea(bool); bool getNormalizeVisibleArea() const; - /** - * Normalize each column to its maximum value, and then scale by - * the log of the (absolute) maximum value. - */ - void setNormalizeHybrid(bool n); - bool getNormalizeHybrid() const; - void setInvertVertical(bool i); bool getInvertVertical() const; @@ -151,7 +135,7 @@ virtual bool getDisplayExtents(double &min, double &max) const; virtual bool setDisplayExtents(double min, double max); - virtual bool getYScaleValue(const View *, int /* y */, + virtual bool getYScaleValue(const LayerGeometryProvider *, int /* y */, double &/* value */, QString &/* unit */) const; virtual int getVerticalZoomSteps(int &defaultStep) const; @@ -173,41 +157,50 @@ protected: const DenseThreeDimensionalModel *m_model; // I do not own this - mutable QImage *m_cache; - mutable QImage *m_peaksCache; - mutable int m_cacheValidStart; - mutable int m_cacheValidEnd; - - ColourScale m_colourScale; - bool m_colourScaleSet; - int m_colourMap; - float m_gain; - BinScale m_binScale; - bool m_normalizeColumns; - bool m_normalizeVisibleArea; - bool m_normalizeHybrid; - bool m_invertVertical; - bool m_opaque; - bool m_smooth; - int m_peakResolution; + ColourScaleType m_colourScale; + bool m_colourScaleSet; + int m_colourMap; + float m_gain; + BinScale m_binScale; + ColumnNormalization m_normalization; // of individual columns + bool m_normalizeVisibleArea; + bool m_invertVertical; + bool m_opaque; + bool m_smooth; + int m_peakResolution; // Minimum and maximum bin numbers visible within the view. We // always snap to whole bins at view edges. - int m_miny; - int m_maxy; + int m_miny; + int m_maxy; + bool m_synchronous; + + static ColourScaleType convertToColourScale(int value); + static int convertFromColourScale(ColourScaleType); + static std::pair convertToColumnNorm(int value); + static int convertFromColumnNorm(ColumnNormalization norm, bool visible); + + mutable Dense3DModelPeakCache *m_peakCache; + const int m_peakCacheDivisor; + Dense3DModelPeakCache *getPeakCache() const; + + typedef std::map ViewMagMap; // key is view id + mutable ViewMagMap m_viewMags; + + typedef std::map ViewRendererMap; // key is view id + mutable ViewRendererMap m_renderers; + + Colour3DPlotRenderer *getRenderer(const LayerGeometryProvider *) const; + void invalidateRenderers(); + /** * Return the y coordinate at which the given bin "starts" * (i.e. at the bottom of the bin, if the given bin is an integer * and the vertical scale is the usual way up). Bin number may be * fractional, to obtain a position part-way through a bin. */ - double getYForBin(View *, double bin) const; - - /** - * As getYForBin, but rounding to integer values. - */ - int getIYForBin(View *, int bin) const; + double getYForBin(const LayerGeometryProvider *, double bin) const; /** * Return the bin number, possibly fractional, at the given y @@ -215,25 +208,13 @@ * at which the bins "start" (i.e. the bottom of the visible bin, * if the vertical scale is the usual way up). */ - double getBinForY(View *, double y) const; - - /** - * As getBinForY, but rounding to integer values. - */ - int getIBinForY(View *, int y) const; + double getBinForY(const LayerGeometryProvider *, double y) const; DenseThreeDimensionalModel::Column getColumn(int col) const; - /** - * True if we have the opaque or smooth flag set, or if the cells - * are so small you can't see their borders. False for big, - * translucent cells. - */ - bool shouldPaintDenseIn(const View *) const; + int getColourScaleWidth(QPainter &) const; - int getColourScaleWidth(QPainter &) const; - void fillCache(int firstBin, int lastBin) const; - void paintDense(View *v, QPainter &paint, QRect rect) const; + void paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; }; #endif diff -r e8102ff5573b -r dc2af6616c83 layer/Colour3DPlotRenderer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Colour3DPlotRenderer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,1164 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2016 Chris Cannam and QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "Colour3DPlotRenderer.h" +#include "RenderTimer.h" + +#include "base/Profiler.h" +#include "base/HitCount.h" + +#include "data/model/DenseThreeDimensionalModel.h" +#include "data/model/Dense3DModelPeakCache.h" +#include "data/model/FFTModel.h" + +#include "LayerGeometryProvider.h" +#include "VerticalBinLayer.h" +#include "PaintAssistant.h" +#include "ImageRegionFinder.h" + +#include "view/ViewManager.h" // for main model sample rate. Pity + +#include + +//#define DEBUG_COLOUR_PLOT_REPAINT 1 + +using namespace std; + +Colour3DPlotRenderer::RenderResult +Colour3DPlotRenderer::render(const LayerGeometryProvider *v, QPainter &paint, QRect rect) +{ + return render(v, paint, rect, false); +} + +Colour3DPlotRenderer::RenderResult +Colour3DPlotRenderer::renderTimeConstrained(const LayerGeometryProvider *v, + QPainter &paint, QRect rect) +{ + return render(v, paint, rect, true); +} + +QRect +Colour3DPlotRenderer::getLargestUncachedRect(const LayerGeometryProvider *v) +{ + RenderType renderType = decideRenderType(v); + + if (renderType == DirectTranslucent) { + return QRect(); // never cached + } + + int h = m_cache.getSize().height(); + + QRect areaLeft(0, 0, m_cache.getValidLeft(), h); + QRect areaRight(m_cache.getValidRight(), 0, + m_cache.getSize().width() - m_cache.getValidRight(), h); + + if (areaRight.width() > areaLeft.width()) { + return areaRight; + } else { + return areaLeft; + } +} + +bool +Colour3DPlotRenderer::geometryChanged(const LayerGeometryProvider *v) +{ + RenderType renderType = decideRenderType(v); + + if (renderType == DirectTranslucent) { + return true; // never cached + } + + if (m_cache.getSize() == v->getPaintSize() && + m_cache.getZoomLevel() == v->getZoomLevel() && + m_cache.getStartFrame() == v->getStartFrame()) { + return false; + } else { + return true; + } +} + +Colour3DPlotRenderer::RenderResult +Colour3DPlotRenderer::render(const LayerGeometryProvider *v, + QPainter &paint, QRect rect, bool timeConstrained) +{ + RenderType renderType = decideRenderType(v); + + if (renderType != DrawBufferPixelResolution) { + // Rendering should be fast in bin-resolution and direct draw + // cases because we are quite well zoomed-in, and the sums are + // easier this way. Calculating boundaries later will be + // fiddly for partial paints otherwise. + timeConstrained = false; + } + + int x0 = v->getXForViewX(rect.x()); + int x1 = v->getXForViewX(rect.x() + rect.width()); + if (x0 < 0) x0 = 0; + if (x1 > v->getPaintWidth()) x1 = v->getPaintWidth(); + + sv_frame_t startFrame = v->getStartFrame(); + + m_cache.resize(v->getPaintSize()); + m_cache.setZoomLevel(v->getZoomLevel()); + + m_magCache.resize(v->getPaintSize().width()); + m_magCache.setZoomLevel(v->getZoomLevel()); + + if (renderType == DirectTranslucent) { + MagnitudeRange range = renderDirectTranslucent(v, paint, rect); + return { rect, range }; + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "cache start " << m_cache.getStartFrame() + << " valid left " << m_cache.getValidLeft() + << " valid right " << m_cache.getValidRight() + << endl; + SVDEBUG << " view start " << startFrame + << " x0 " << x0 + << " x1 " << x1 + << endl; +#endif + + static HitCount count("Colour3DPlotRenderer: image cache"); + + if (m_cache.isValid()) { // some part of the cache is valid + + if (v->getXForFrame(m_cache.getStartFrame()) == + v->getXForFrame(startFrame) && + m_cache.getValidLeft() <= x0 && + m_cache.getValidRight() >= x1) { + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "cache hit" << endl; +#endif + count.hit(); + + // cache is valid for the complete requested area + paint.drawImage(rect, m_cache.getImage(), rect); + + MagnitudeRange range = m_magCache.getRange(x0, x1 - x0); + + return { rect, range }; + + } else { +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "cache partial hit" << endl; +#endif + count.partial(); + + // cache doesn't begin at the right frame or doesn't + // contain the complete view, but might be scrollable or + // partially usable + m_cache.scrollTo(v, startFrame); + m_magCache.scrollTo(v, startFrame); + + // if we are not time-constrained, then we want to paint + // the whole area in one go; we don't return a partial + // paint. To avoid providing the more complex logic to + // handle painting discontiguous areas, if the only valid + // part of cache is in the middle, just make the whole + // thing invalid and start again. + if (!timeConstrained) { + if (m_cache.getValidLeft() > x0 && + m_cache.getValidRight() < x1) { + m_cache.invalidate(); + } + } + } + } else { + // cache is completely invalid + count.miss(); + m_cache.setStartFrame(startFrame); + m_magCache.setStartFrame(startFrame); + } + + bool rightToLeft = false; + + int reqx0 = x0; + int reqx1 = x1; + + if (!m_cache.isValid() && timeConstrained) { + // When rendering the whole area, in a context where we might + // not be able to complete the work, start from somewhere near + // the middle so that the region of interest appears + // first. But only if we aren't using a peak cache, as + // rendering from peak cache is usually (not always) quick and + // looks odd if we make a habit of jumping back after reaching + // the end. + if (x0 == 0 && x1 == v->getPaintWidth()) { + int peakCacheIndex = -1, binsPerPeak = -1; + getPreferredPeakCache(v, peakCacheIndex, binsPerPeak); + if (peakCacheIndex == -1) { // no peak cache + x0 = int(x1 * 0.3); + } + } + } + + if (m_cache.isValid()) { + + // When rendering only a part of the cache, we need to make + // sure that the part we're rendering is adjacent to (or + // overlapping) a valid area of cache, if we have one. The + // alternative is to ditch the valid area of cache and render + // only the requested area, but that's risky because this can + // happen when just waving the pointer over a small part of + // the view -- if we lose the partly-built cache every time + // the user does that, we'll never finish building it. + int left = x0; + int width = x1 - x0; + bool isLeftOfValidArea = false; + m_cache.adjustToTouchValidArea(left, width, isLeftOfValidArea); + x0 = left; + x1 = x0 + width; + + // That call also told us whether we should be painting + // sub-regions of our target region in right-to-left order in + // order to ensure contiguity + rightToLeft = isLeftOfValidArea; + } + + // Note, we always paint the full height to cache. We want to + // ensure the cache is coherent without having to worry about + // vertical matching of required and valid areas as well as + // horizontal. + + if (renderType == DrawBufferBinResolution) { + + renderToCacheBinResolution(v, x0, x1 - x0); + + } else { // must be DrawBufferPixelResolution, handled DirectTranslucent earlier + + renderToCachePixelResolution(v, x0, x1 - x0, rightToLeft, timeConstrained); + } + + QRect pr = rect & m_cache.getValidArea(); + paint.drawImage(pr.x(), pr.y(), m_cache.getImage(), + pr.x(), pr.y(), pr.width(), pr.height()); + + if (!timeConstrained && (pr != rect)) { + cerr << "WARNING: failed to render entire requested rect " + << "even when not time-constrained" << endl; + } + + MagnitudeRange range = m_magCache.getRange(reqx0, reqx1 - reqx0); + + return { pr, range }; +} + +Colour3DPlotRenderer::RenderType +Colour3DPlotRenderer::decideRenderType(const LayerGeometryProvider *v) const +{ + const DenseThreeDimensionalModel *model = m_sources.source; + if (!model || !v || !(v->getViewManager())) { + return DrawBufferPixelResolution; // or anything + } + + int binResolution = model->getResolution(); + int zoomLevel = v->getZoomLevel(); + sv_samplerate_t modelRate = model->getSampleRate(); + + double rateRatio = v->getViewManager()->getMainModelSampleRate() / modelRate; + double relativeBinResolution = binResolution * rateRatio; + + if (m_params.binDisplay == BinDisplay::PeakFrequencies) { + // no alternative works here + return DrawBufferPixelResolution; + } + + if (!m_params.alwaysOpaque && !m_params.interpolate) { + + // consider translucent option -- only if not smoothing & not + // explicitly requested opaque & sufficiently zoomed-in + + if (model->getHeight() * 3 < v->getPaintHeight() && + relativeBinResolution >= 3 * zoomLevel) { + return DirectTranslucent; + } + } + + if (relativeBinResolution > zoomLevel) { + return DrawBufferBinResolution; + } else { + return DrawBufferPixelResolution; + } +} + +ColumnOp::Column +Colour3DPlotRenderer::getColumn(int sx, int minbin, int nbins, + int peakCacheIndex) const +{ + Profiler profiler("Colour3DPlotRenderer::getColumn"); + + // order: + // get column -> scale -> normalise -> record extents -> + // peak pick -> distribute/interpolate -> apply display gain + + // we do the first bit here: + // get column -> scale -> normalise + + ColumnOp::Column column; + + if (m_params.colourScale.getScale() == ColourScaleType::Phase && + m_sources.fft) { + + ColumnOp::Column fullColumn = m_sources.fft->getPhases(sx); + + column = vector(fullColumn.data() + minbin, + fullColumn.data() + minbin + nbins); + + } else { + + ColumnOp::Column fullColumn = + (peakCacheIndex >= 0 ? + m_sources.peakCaches[peakCacheIndex] : + m_sources.source) + ->getColumn(sx); + + column = vector(fullColumn.data() + minbin, + fullColumn.data() + minbin + nbins); + + column = ColumnOp::applyGain(column, m_params.scaleFactor); + + column = ColumnOp::normalize(column, m_params.normalization); + } + + return column; +} + +MagnitudeRange +Colour3DPlotRenderer::renderDirectTranslucent(const LayerGeometryProvider *v, + QPainter &paint, + QRect rect) +{ + Profiler profiler("Colour3DPlotRenderer::renderDirectTranslucent"); + + MagnitudeRange magRange; + + QPoint illuminatePos; + bool illuminate = v->shouldIlluminateLocalFeatures + (m_sources.verticalBinLayer, illuminatePos); + + const DenseThreeDimensionalModel *model = m_sources.source; + + int x0 = rect.left(); + int x1 = rect.right() + 1; + + int h = v->getPaintHeight(); + + sv_frame_t modelStart = model->getStartFrame(); + sv_frame_t modelEnd = model->getEndFrame(); + int modelResolution = model->getResolution(); + + double rateRatio = + v->getViewManager()->getMainModelSampleRate() / model->getSampleRate(); + + // the s-prefix values are source, i.e. model, column and bin numbers + int sx0 = int((double(v->getFrameForX(x0)) / rateRatio - double(modelStart)) + / modelResolution); + int sx1 = int((double(v->getFrameForX(x1)) / rateRatio - double(modelStart)) + / modelResolution); + + int sh = model->getHeight(); + + const int buflen = 40; + char labelbuf[buflen]; + + int minbin = m_sources.verticalBinLayer->getIBinForY(v, h); + if (minbin >= sh) minbin = sh - 1; + if (minbin < 0) minbin = 0; + + int nbins = m_sources.verticalBinLayer->getIBinForY(v, 0) - minbin + 1; + if (minbin + nbins > sh) nbins = sh - minbin; + + int psx = -1; + + vector preparedColumn; + + int modelWidth = model->getWidth(); + + for (int sx = sx0; sx <= sx1; ++sx) { + + if (sx < 0 || sx >= modelWidth) { + continue; + } + + if (sx != psx) { + + // order: + // get column -> scale -> normalise -> record extents -> + // peak pick -> distribute/interpolate -> apply display gain + + // this does the first three: + preparedColumn = getColumn(sx, minbin, nbins, false); + + magRange.sample(preparedColumn); + + if (m_params.binDisplay == BinDisplay::PeakBins) { + preparedColumn = ColumnOp::peakPick(preparedColumn); + } + + // Display gain belongs to the colour scale and is + // applied by the colour scale object when mapping it + + psx = sx; + } + + sv_frame_t fx = sx * modelResolution + modelStart; + + if (fx + modelResolution <= modelStart || fx > modelEnd) continue; + + int rx0 = v->getXForFrame(int(double(fx) * rateRatio)); + int rx1 = v->getXForFrame(int(double(fx + modelResolution + 1) * rateRatio)); + + int rw = rx1 - rx0; + if (rw < 1) rw = 1; + + bool showLabel = (rw > 10 && + paint.fontMetrics().width("0.000000") < rw - 3 && + paint.fontMetrics().height() < (h / sh)); + + for (int sy = minbin; sy < minbin + nbins; ++sy) { + + int ry0 = m_sources.verticalBinLayer->getIYForBin(v, sy); + int ry1 = m_sources.verticalBinLayer->getIYForBin(v, sy + 1); + + if (m_params.invertVertical) { + ry0 = h - ry0 - 1; + ry1 = h - ry1 - 1; + } + + QRect r(rx0, ry1, rw, ry0 - ry1); + + float value = preparedColumn[sy - minbin]; + QColor colour = m_params.colourScale.getColour(value, + m_params.colourRotation); + + if (rw == 1) { + paint.setPen(colour); + paint.setBrush(Qt::NoBrush); + paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1); + continue; + } + + QColor pen(255, 255, 255, 80); + QColor brush(colour); + + if (rw > 3 && r.height() > 3) { + brush.setAlpha(160); + } + + paint.setPen(Qt::NoPen); + paint.setBrush(brush); + + if (illuminate) { + if (r.contains(illuminatePos)) { + paint.setPen(v->getForeground()); + } + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT +// SVDEBUG << "rect " << r.x() << "," << r.y() << " " +// << r.width() << "x" << r.height() << endl; +#endif + + paint.drawRect(r); + + if (showLabel) { + double value = model->getValueAt(sx, sy); + snprintf(labelbuf, buflen, "%06f", value); + QString text(labelbuf); + PaintAssistant::drawVisibleText + (v, + paint, + rx0 + 2, + ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(), + text, + PaintAssistant::OutlinedText); + } + } + } + + return magRange; +} + +void +Colour3DPlotRenderer::getPreferredPeakCache(const LayerGeometryProvider *v, + int &peakCacheIndex, + int &binsPerPeak) const +{ + peakCacheIndex = -1; + binsPerPeak = -1; + + const DenseThreeDimensionalModel *model = m_sources.source; + if (!model) return; + + int zoomLevel = v->getZoomLevel(); + int binResolution = model->getResolution(); + + for (int ix = 0; in_range_for(m_sources.peakCaches, ix); ++ix) { + int bpp = m_sources.peakCaches[ix]->getColumnsPerPeak(); + int equivZoom = binResolution * bpp; + if (zoomLevel >= equivZoom) { + // this peak cache would work, though it might not be best + if (bpp > binsPerPeak) { + // ok, it's better than the best one we've found so far + peakCacheIndex = ix; + binsPerPeak = bpp; + } + } + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "getPreferredPeakCache: zoomLevel = " << zoomLevel + << ", binResolution " << binResolution + << ", binsPerPeak " << binsPerPeak + << ", peakCacheIndex " << peakCacheIndex + << ", peakCaches " << m_sources.peakCaches.size() + << endl; +#endif +} + +void +Colour3DPlotRenderer::renderToCachePixelResolution(const LayerGeometryProvider *v, + int x0, int repaintWidth, + bool rightToLeft, + bool timeConstrained) +{ + Profiler profiler("Colour3DPlotRenderer::renderToCachePixelResolution"); +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "renderToCachePixelResolution" << endl; +#endif + + // Draw to the draw buffer, and then copy from there. The draw + // buffer is at the same resolution as the target in the cache, so + // no extra scaling needed. + + const DenseThreeDimensionalModel *model = m_sources.source; + if (!model || !model->isOK() || !model->isReady()) { + throw std::logic_error("no source model provided, or model not ready"); + } + + int h = v->getPaintHeight(); + + clearDrawBuffer(repaintWidth, h); + + vector binforx(repaintWidth); + vector binfory(h); + + int binResolution = model->getResolution(); + + for (int x = 0; x < repaintWidth; ++x) { + sv_frame_t f0 = v->getFrameForX(x0 + x); + double s0 = double(f0 - model->getStartFrame()) / binResolution; + binforx[x] = int(s0 + 0.0001); + } + + int peakCacheIndex = -1; + int binsPerPeak = -1; + + if (m_params.colourScale.getScale() != ColourScaleType::Phase) { + getPreferredPeakCache(v, peakCacheIndex, binsPerPeak); + } + + for (int y = 0; y < h; ++y) { + binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1); + } + + int attainedWidth; + + if (m_params.binDisplay == BinDisplay::PeakFrequencies) { + attainedWidth = renderDrawBufferPeakFrequencies(v, + repaintWidth, + h, + binforx, + binfory, + rightToLeft, + timeConstrained); + + } else { + attainedWidth = renderDrawBuffer(repaintWidth, + h, + binforx, + binfory, + peakCacheIndex, + rightToLeft, + timeConstrained); + } + + if (attainedWidth == 0) return; + + // draw buffer is pixel resolution, no scaling factors or padding involved + + int paintedLeft = x0; + if (rightToLeft) { + paintedLeft += (repaintWidth - attainedWidth); + } + + m_cache.drawImage(paintedLeft, attainedWidth, + m_drawBuffer, + paintedLeft - x0, attainedWidth); + + for (int i = 0; in_range_for(m_magRanges, i); ++i) { + m_magCache.sampleColumn(i, m_magRanges.at(i)); + } +} + +QImage +Colour3DPlotRenderer::scaleDrawBufferImage(QImage image, + int targetWidth, + int targetHeight) const +{ + int sourceWidth = image.width(); + int sourceHeight = image.height(); + + // We can only do this if we're making the image larger -- + // otherwise peaks may be lost. So this should be called only when + // rendering in DrawBufferBinResolution mode. Whenever the bin + // size is smaller than the pixel size, in either x or y axis, we + // should be using DrawBufferPixelResolution mode instead + + if (targetWidth < sourceWidth || targetHeight < sourceHeight) { + throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Can only use this function when making the image larger; should be rendering DrawBufferPixelResolution instead"); + } + + if (sourceWidth <= 0 || sourceHeight <= 0) { + throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Source image is empty"); + } + + if (targetWidth <= 0 || targetHeight <= 0) { + throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Target image is empty"); + } + + // This function exists because of some unpredictable behaviour + // from Qt when scaling images with FastTransformation mode. We + // continue to use Qt's scaler for SmoothTransformation but let's + // bring the non-interpolated version "in-house" so we know what + // it's really doing. + + if (m_params.interpolate) { + return image.scaled(targetWidth, targetHeight, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + } + + // Same format as the target cache + QImage target(targetWidth, targetHeight, QImage::Format_ARGB32_Premultiplied); + + for (int y = 0; y < targetHeight; ++y) { + + QRgb *targetLine = reinterpret_cast(target.scanLine(y)); + + int sy = int((uint64_t(y) * sourceHeight) / targetHeight); + if (sy == sourceHeight) --sy; + + for (int x = 0; x < targetWidth; ++x) { + + int sx = int((uint64_t(x) * sourceWidth) / targetWidth); + if (sx == sourceWidth) --sx; + + targetLine[x] = image.pixel(sx, sy); + } + } + + return target; +} + +void +Colour3DPlotRenderer::renderToCacheBinResolution(const LayerGeometryProvider *v, + int x0, int repaintWidth) +{ + Profiler profiler("Colour3DPlotRenderer::renderToCacheBinResolution"); +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "renderToCacheBinResolution" << endl; +#endif + + // Draw to the draw buffer, and then scale-copy from there. Draw + // buffer is at bin resolution, i.e. buffer x == source column + // number. We use toolkit smooth scaling for interpolation. + + const DenseThreeDimensionalModel *model = m_sources.source; + if (!model || !model->isOK() || !model->isReady()) { + throw std::logic_error("no source model provided, or model not ready"); + } + + // The draw buffer will contain a fragment at bin resolution. We + // need to ensure that it starts and ends at points where a + // time-bin boundary occurs at an exact pixel boundary, and with a + // certain amount of overlap across existing pixels so that we can + // scale and draw from it without smoothing errors at the edges. + + // If (getFrameForX(x) / increment) * increment == + // getFrameForX(x), then x is a time-bin boundary. We want two + // such boundaries at either side of the draw buffer -- one which + // we draw up to, and one which we subsequently crop at. + + sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1; + sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1; + + int drawBufferWidth; + int binResolution = model->getResolution(); + + for (int x = x0; ; --x) { + sv_frame_t f = v->getFrameForX(x); + if ((f / binResolution) * binResolution == f) { + if (leftCropFrame == -1) leftCropFrame = f; + else if (x < x0 - 2) { + leftBoundaryFrame = f; + break; + } + } + } + for (int x = x0 + repaintWidth; ; ++x) { + sv_frame_t f = v->getFrameForX(x); + if ((f / binResolution) * binResolution == f) { + if (rightCropFrame == -1) rightCropFrame = f; + else if (x > x0 + repaintWidth + 2) { + rightBoundaryFrame = f; + break; + } + } + } + drawBufferWidth = int + ((rightBoundaryFrame - leftBoundaryFrame) / binResolution); + + int h = v->getPaintHeight(); + + // For our purposes here, the draw buffer needs to be exactly our + // target size (so we recreate always rather than just clear it) + + recreateDrawBuffer(drawBufferWidth, h); + + vector binforx(drawBufferWidth); + vector binfory(h); + + for (int x = 0; x < drawBufferWidth; ++x) { + binforx[x] = int(leftBoundaryFrame / binResolution) + x; + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "[BIN] binResolution " << binResolution << endl; +#endif + + for (int y = 0; y < h; ++y) { + binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1); + } + + int attainedWidth = renderDrawBuffer(drawBufferWidth, + h, + binforx, + binfory, + -1, + false, + false); + + if (attainedWidth == 0) return; + + int scaledLeft = v->getXForFrame(leftBoundaryFrame); + int scaledRight = v->getXForFrame(rightBoundaryFrame); + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "scaling draw buffer from width " << m_drawBuffer.width() + << " to " << (scaledRight - scaledLeft) << " (nb drawBufferWidth = " + << drawBufferWidth << ")" << endl; +#endif + + QImage scaled = scaleDrawBufferImage + (m_drawBuffer, scaledRight - scaledLeft, h); + + int scaledLeftCrop = v->getXForFrame(leftCropFrame); + int scaledRightCrop = v->getXForFrame(rightCropFrame); + + int targetLeft = scaledLeftCrop; + if (targetLeft < 0) { + targetLeft = 0; + } + + int targetWidth = scaledRightCrop - targetLeft; + if (targetLeft + targetWidth > m_cache.getSize().width()) { + targetWidth = m_cache.getSize().width() - targetLeft; + } + + int sourceLeft = targetLeft - scaledLeft; + if (sourceLeft < 0) { + sourceLeft = 0; + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "repaintWidth = " << repaintWidth + << ", targetWidth = " << targetWidth << endl; +#endif + + if (targetWidth > 0) { + // we are copying from an image that has already been scaled, + // hence using the same width in both geometries + m_cache.drawImage(targetLeft, targetWidth, + scaled, + sourceLeft, targetWidth); + } + + for (int i = 0; i < targetWidth; ++i) { + // but the mag range vector has not been scaled + int sourceIx = int((double(i + sourceLeft) / scaled.width()) + * int(m_magRanges.size())); + if (in_range_for(m_magRanges, sourceIx)) { + m_magCache.sampleColumn(i, m_magRanges.at(sourceIx)); + } + } +} + +int +Colour3DPlotRenderer::renderDrawBuffer(int w, int h, + const vector &binforx, + const vector &binfory, + int peakCacheIndex, + bool rightToLeft, + bool timeConstrained) +{ + // Callers must have checked that the appropriate subset of + // Sources data members are set for the supplied flags (e.g. that + // peakCache corresponding to peakCacheIndex exists) + + RenderTimer timer(timeConstrained ? + RenderTimer::FastRender : + RenderTimer::NoTimeout); + + Profiler profiler("Colour3DPlotRenderer::renderDrawBuffer"); + + int divisor = 1; + const DenseThreeDimensionalModel *sourceModel = m_sources.source; + if (peakCacheIndex >= 0) { + divisor = m_sources.peakCaches[peakCacheIndex]->getColumnsPerPeak(); + sourceModel = m_sources.peakCaches[peakCacheIndex]; + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "renderDrawBuffer: w = " << w << ", h = " << h + << ", peakCacheIndex = " << peakCacheIndex << " (divisor = " + << divisor << "), rightToLeft = " << rightToLeft + << ", timeConstrained = " << timeConstrained << endl; + SVDEBUG << "renderDrawBuffer: normalization = " << int(m_params.normalization) + << ", binDisplay = " << int(m_params.binDisplay) + << ", binScale = " << int(m_params.binScale) + << ", alwaysOpaque = " << m_params.alwaysOpaque + << ", interpolate = " << m_params.interpolate << endl; +#endif + + int sh = sourceModel->getHeight(); + + int minbin = int(binfory[0] + 0.0001); + if (minbin >= sh) minbin = sh - 1; + if (minbin < 0) minbin = 0; + + int nbins = int(binfory[h-1] + 0.0001) - minbin + 1; + if (minbin + nbins > sh) nbins = sh - minbin; + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "minbin = " << minbin << ", nbins = " << nbins << ", last binfory = " + << binfory[h-1] << " (rounds to " << int(binfory[h-1]) << ") (model height " << sh << ")" << endl; +#endif + + int psx = -1; + + int start = 0; + int finish = w; + int step = 1; + + if (rightToLeft) { + start = w-1; + finish = -1; + step = -1; + } + + int columnCount = 0; + + vector preparedColumn; + + int modelWidth = sourceModel->getWidth(); + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "modelWidth " << modelWidth << ", divisor " << divisor << endl; +#endif + + for (int x = start; x != finish; x += step) { + + // x is the on-canvas pixel coord; sx (later) will be the + // source column index + + ++columnCount; + + if (binforx[x] < 0) continue; + + int sx0 = binforx[x] / divisor; + int sx1 = sx0; + if (x+1 < w) sx1 = binforx[x+1] / divisor; + if (sx0 < 0) sx0 = sx1 - 1; + if (sx0 < 0) continue; + if (sx1 <= sx0) sx1 = sx0 + 1; + +#ifdef DEBUG_COLOUR_PLOT_REPAINT +// SVDEBUG << "x = " << x << ", binforx[x] = " << binforx[x] << ", sx range " << sx0 << " -> " << sx1 << endl; +#endif + + vector pixelPeakColumn; + MagnitudeRange magRange; + + for (int sx = sx0; sx < sx1; ++sx) { + + if (sx < 0 || sx >= modelWidth) { + continue; + } + + if (sx != psx) { + + // order: + // get column -> scale -> normalise -> record extents -> + // peak pick -> distribute/interpolate -> apply display gain + + // this does the first three: + ColumnOp::Column column = getColumn(sx, minbin, nbins, + peakCacheIndex); + + magRange.sample(column); + + if (m_params.binDisplay == BinDisplay::PeakBins) { + column = ColumnOp::peakPick(column); + } + + preparedColumn = + ColumnOp::distribute(column, + h, + binfory, + minbin, + m_params.interpolate); + + // Display gain belongs to the colour scale and is + // applied by the colour scale object when mapping it + + psx = sx; + } + + if (sx == sx0) { + pixelPeakColumn = preparedColumn; + } else { + for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) { + pixelPeakColumn[i] = std::max(pixelPeakColumn[i], + preparedColumn[i]); + } + } + } + + if (!pixelPeakColumn.empty()) { + + for (int y = 0; y < h; ++y) { + int py; + if (m_params.invertVertical) { + py = y; + } else { + py = h - y - 1; + } + m_drawBuffer.setPixel + (x, + py, + m_params.colourScale.getPixel(pixelPeakColumn[y])); + } + + m_magRanges.push_back(magRange); + } + + double fractionComplete = double(columnCount) / double(w); + if (timer.outOfTime(fractionComplete)) { +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "out of time" << endl; +#endif + return columnCount; + } + } + + return columnCount; +} + +int +Colour3DPlotRenderer::renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v, + int w, int h, + const vector &binforx, + const vector &binfory, + bool rightToLeft, + bool timeConstrained) +{ + // Callers must have checked that the appropriate subset of + // Sources data members are set for the supplied flags (e.g. that + // fft model exists) + + RenderTimer timer(timeConstrained ? + RenderTimer::FastRender : + RenderTimer::NoTimeout); + + const FFTModel *fft = m_sources.fft; + + int sh = fft->getHeight(); + + int minbin = int(binfory[0] + 0.0001); + if (minbin >= sh) minbin = sh - 1; + if (minbin < 0) minbin = 0; + + int nbins = int(binfory[h-1]) - minbin + 1; + if (minbin + nbins > sh) nbins = sh - minbin; + + FFTModel::PeakSet peakfreqs; + + int psx = -1; + + int start = 0; + int finish = w; + int step = 1; + + if (rightToLeft) { + start = w-1; + finish = -1; + step = -1; + } + + int columnCount = 0; + + vector preparedColumn; + + int modelWidth = fft->getWidth(); +#ifdef DEBUG_COLOUR_PLOT_REPAINT + SVDEBUG << "modelWidth " << modelWidth << endl; +#endif + + double minFreq = + (double(minbin) * fft->getSampleRate()) / fft->getFFTSize(); + double maxFreq = + (double(minbin + nbins - 1) * fft->getSampleRate()) / fft->getFFTSize(); + + bool logarithmic = (m_params.binScale == BinScale::Log); + + for (int x = start; x != finish; x += step) { + + // x is the on-canvas pixel coord; sx (later) will be the + // source column index + + ++columnCount; + + if (binforx[x] < 0) continue; + + int sx0 = binforx[x]; + int sx1 = sx0; + if (x+1 < w) sx1 = binforx[x+1]; + if (sx0 < 0) sx0 = sx1 - 1; + if (sx0 < 0) continue; + if (sx1 <= sx0) sx1 = sx0 + 1; + + vector pixelPeakColumn; + MagnitudeRange magRange; + + for (int sx = sx0; sx < sx1; ++sx) { + + if (sx < 0 || sx >= modelWidth) { + continue; + } + + if (sx != psx) { + preparedColumn = getColumn(sx, minbin, nbins, false); + magRange.sample(preparedColumn); + psx = sx; + } + + if (sx == sx0) { + pixelPeakColumn = preparedColumn; + peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx, + minbin, minbin + nbins - 1); + } else { + for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) { + pixelPeakColumn[i] = std::max(pixelPeakColumn[i], + preparedColumn[i]); + } + } + } + + if (!pixelPeakColumn.empty()) { + + for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin(); + pi != peakfreqs.end(); ++pi) { + + int bin = pi->first; + double freq = pi->second; + + if (bin < minbin) continue; + if (bin >= minbin + nbins) break; + + double value = pixelPeakColumn[bin - minbin]; + + double y = v->getYForFrequency + (freq, minFreq, maxFreq, logarithmic); + + int iy = int(y + 0.5); + if (iy < 0 || iy >= h) continue; + + m_drawBuffer.setPixel + (x, + iy, + m_params.colourScale.getPixel(value)); + } + + m_magRanges.push_back(magRange); + } + + double fractionComplete = double(columnCount) / double(w); + if (timer.outOfTime(fractionComplete)) { + return columnCount; + } + } + + return columnCount; +} + +void +Colour3DPlotRenderer::recreateDrawBuffer(int w, int h) +{ + m_drawBuffer = QImage(w, h, QImage::Format_Indexed8); + + for (int pixel = 0; pixel < 256; ++pixel) { + m_drawBuffer.setColor + ((unsigned char)pixel, + m_params.colourScale.getColourForPixel + (pixel, m_params.colourRotation).rgb()); + } + + m_drawBuffer.fill(0); + m_magRanges.clear(); +} + +void +Colour3DPlotRenderer::clearDrawBuffer(int w, int h) +{ + if (m_drawBuffer.width() < w || m_drawBuffer.height() != h) { + recreateDrawBuffer(w, h); + } else { + m_drawBuffer.fill(0); + m_magRanges.clear(); + } +} + +QRect +Colour3DPlotRenderer::findSimilarRegionExtents(QPoint p) const +{ + QImage image = m_cache.getImage(); + ImageRegionFinder finder; + QRect rect = finder.findRegionExtents(&image, p); + return rect; +} diff -r e8102ff5573b -r dc2af6616c83 layer/Colour3DPlotRenderer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Colour3DPlotRenderer.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,316 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2016 Chris Cannam and QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef COLOUR_3D_PLOT_RENDERER_H +#define COLOUR_3D_PLOT_RENDERER_H + +#include "ColourScale.h" +#include "ScrollableImageCache.h" +#include "ScrollableMagRangeCache.h" + +#include "base/ColumnOp.h" +#include "base/MagnitudeRange.h" + +#include +#include +#include + +class LayerGeometryProvider; +class VerticalBinLayer; +class DenseThreeDimensionalModel; +class Dense3DModelPeakCache; +class FFTModel; + +enum class BinDisplay { + AllBins, + PeakBins, + PeakFrequencies +}; + +enum class BinScale { + Linear, + Log +}; + +class Colour3DPlotRenderer +{ +public: + struct Sources { + Sources() : verticalBinLayer(0), source(0), fft(0) { } + + // These must all outlive this class + const VerticalBinLayer *verticalBinLayer; // always + const DenseThreeDimensionalModel *source; // always + const FFTModel *fft; // optionally + std::vector peakCaches; // zero or more + }; + + struct Parameters { + Parameters() : + colourScale(ColourScale::Parameters()), + normalization(ColumnNormalization::None), + binDisplay(BinDisplay::AllBins), + binScale(BinScale::Linear), + alwaysOpaque(false), + interpolate(false), + invertVertical(false), + scaleFactor(1.0), + colourRotation(0) { } + + /** A complete ColourScale object by value, used for colour + * map conversion. Note that the final display gain setting is + * also encapsulated here. */ + ColourScale colourScale; + + /** Type of column normalization. */ + ColumnNormalization normalization; + + /** Selection of bins to display. */ + BinDisplay binDisplay; + + /** Scale for vertical bin spacing (linear or logarithmic). */ + BinScale binScale; + + /** Whether cells should always be opaque. If false, then + * large cells (when zoomed in a long way) will be rendered + * translucent in order not to obscure anything in a layer + * beneath. */ + bool alwaysOpaque; + + /** Whether to apply smoothing when rendering cells at more + * than one pixel per cell. !!! todo: decide about separating + * out x-interpolate and y-interpolate as the spectrogram + * actually does (or used to) + */ + bool interpolate; + + /** Whether to render the whole caboodle upside-down. */ + bool invertVertical; + + /** Initial scale factor (e.g. for FFT scaling). This factor + * is applied to all values read from the underlying model + * *before* magnitude ranges are calculated, in contrast to + * the display gain found in the ColourScale parameter. */ + double scaleFactor; + + /** Colourmap rotation, in the range 0-255. */ + int colourRotation; + }; + + Colour3DPlotRenderer(Sources sources, Parameters parameters) : + m_sources(sources), + m_params(parameters) + { } + + struct RenderResult { + /** + * The rect that was actually rendered. May be equal to the + * rect that was requested to render, or may be smaller if + * time ran out and the complete flag was not set. + */ + QRect rendered; + + /** + * The magnitude range of the data in the rendered area. + */ + MagnitudeRange range; + }; + + /** + * Render the requested area using the given painter, obtaining + * geometry (e.g. start frame) from the given + * LayerGeometryProvider. + * + * The whole of the supplied rect will be rendered and the + * returned QRect will be equal to the supplied QRect. (See + * renderTimeConstrained for an alternative that may render only + * part of the rect in cases where obtaining source data is slow + * and retaining responsiveness is important.) + * + * Note that Colour3DPlotRenderer retains internal cache state + * related to the size and position of the supplied + * LayerGeometryProvider. Although it is valid to call render() + * successively on the same Colour3DPlotRenderer with different + * LayerGeometryProviders, it will be much faster to use a + * dedicated Colour3DPlotRenderer for each LayerGeometryProvider. + * + * If the model to render from is not ready, this will throw a + * std::logic_error exception. The model must be ready and the + * layer requesting the render must not be dormant in its view, so + * that the LayerGeometryProvider returns valid results; it is the + * caller's responsibility to ensure these. + */ + RenderResult render(const LayerGeometryProvider *v, + QPainter &paint, QRect rect); + + /** + * Render the requested area using the given painter, obtaining + * geometry (e.g. start frame) from the stored + * LayerGeometryProvider. + * + * As much of the rect will be rendered as can be managed given + * internal time constraints (using a RenderTimer object + * internally). The returned QRect (the rendered field in the + * RenderResult struct) will contain the area that was + * rendered. Note that we always render the full requested height, + * it's only width that is time-constrained. + * + * Note that Colour3DPlotRenderer retains internal cache state + * related to the size and position of the supplied + * LayerGeometryProvider. Although it is valid to call render() + * successively on the same Colour3DPlotRenderer with different + * LayerGeometryProviders, it will be much faster to use a + * dedicated Colour3DPlotRenderer for each LayerGeometryProvider. + * + * If the model to render from is not ready, this will throw a + * std::logic_error exception. The model must be ready and the + * layer requesting the render must not be dormant in its view, so + * that the LayerGeometryProvider returns valid results; it is the + * caller's responsibility to ensure these. + */ + RenderResult renderTimeConstrained(const LayerGeometryProvider *v, + QPainter &paint, QRect rect); + + /** + * Return the area of the largest rectangle within the entire area + * of the cache that is unavailable in the cache. This is only + * valid in relation to a preceding render() call which is + * presumed to have set the area, start frame, and zoom level for + * the cache. It could be used to establish a suitable region for + * a subsequent paint request (because if an area is not in the + * cache, it cannot have been rendered since the cache was + * cleared). + * + * Returns an empty QRect if the cache is entirely valid. + */ + QRect getLargestUncachedRect(const LayerGeometryProvider *v); + + /** + * Return true if the provider's geometry differs from the cache, + * or if we are not using a cache. i.e. if the cache will be + * regenerated for the next render, or the next render performed + * from scratch. + */ + bool geometryChanged(const LayerGeometryProvider *v); + + /** + * Return true if the rendering will be opaque. This may be used + * by the calling layer to determine whether it can scroll + * directly without regard to any other layers beneath. + */ + bool willRenderOpaque(const LayerGeometryProvider *v) { + return decideRenderType(v) != DirectTranslucent; + } + + /** + * Return the colour corresponding to the given value. + * \see ColourScale::getPixel + * \see ColourScale::getColour + */ + QColor getColour(double value) const { + return m_params.colourScale.getColour(value, m_params.colourRotation); + } + + /** + * Return the enclosing rectangle for the region of similar colour + * to the given point within the cache. Return an empty QRect if + * this is not possible. \see ImageRegionFinder + */ + QRect findSimilarRegionExtents(QPoint point) const; + +private: + Sources m_sources; + Parameters m_params; + + // Draw buffer is the target of each partial repaint. It is always + // at view height (not model height) and is cleared and repainted + // on each fragment render. The only reason it's stored as a data + // member is to avoid reallocation. + QImage m_drawBuffer; + + // A temporary store of magnitude ranges per-column, used when + // rendering to the draw buffer. This always has the same length + // as the width of the draw buffer, and the x coordinates of the + // two containers are equivalent. + std::vector m_magRanges; + + // The image cache is our persistent record of the visible + // area. It is always the same size as the view (i.e. the paint + // size reported by the LayerGeometryProvider) and is scrolled and + // partially repainted internally as appropriate. A render request + // is carried out by repainting to cache (via the draw buffer) any + // area that is being requested but is not valid in the cache, and + // then repainting from cache to the requested painter. + ScrollableImageCache m_cache; + + // The mag range cache is our record of the column magnitude + // ranges for each of the columns in the cache. It always has the + // same start frame and width as the image cache, and the column + // indices match up across both. Our cache update mechanism + // guarantees that every valid column in the image cache has a + // valid range in the magnitude cache, but not necessarily vice + // versa (as the image cache is limited to contiguous ranges). + ScrollableMagRangeCache m_magCache; + + RenderResult render(const LayerGeometryProvider *v, + QPainter &paint, QRect rect, bool timeConstrained); + + MagnitudeRange renderDirectTranslucent(const LayerGeometryProvider *v, + QPainter &paint, QRect rect); + + void renderToCachePixelResolution(const LayerGeometryProvider *v, int x0, + int repaintWidth, bool rightToLeft, + bool timeConstrained); + + void renderToCacheBinResolution(const LayerGeometryProvider *v, int x0, + int repaintWidth); + + int renderDrawBuffer(int w, int h, + const std::vector &binforx, + const std::vector &binfory, + int peakCacheIndex, // -1 => don't use a peak cache + bool rightToLeft, + bool timeConstrained); + + int renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v, + int w, int h, + const std::vector &binforx, + const std::vector &binfory, + bool rightToLeft, + bool timeConstrained); + + void recreateDrawBuffer(int w, int h); + void clearDrawBuffer(int w, int h); + + enum RenderType { + DrawBufferPixelResolution, + DrawBufferBinResolution, + DirectTranslucent + }; + + RenderType decideRenderType(const LayerGeometryProvider *) const; + + QImage scaleDrawBufferImage(QImage source, int targetWidth, int targetHeight) + const; + + ColumnOp::Column getColumn(int sx, int minbin, int nbins, + int peakCacheIndex) const; // -1 => don't use cache + + void getPreferredPeakCache(const LayerGeometryProvider *, + int &peakCacheIndex, int &binsPerPeak) const; +}; + +#endif + diff -r e8102ff5573b -r dc2af6616c83 layer/ColourMapper.cpp --- a/layer/ColourMapper.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/ColourMapper.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -4,7 +4,7 @@ Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2007 Chris Cannam and QMUL. + This file copyright 2006-2016 Chris Cannam and QMUL. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -21,8 +21,47 @@ #include "base/Debug.h" +#include + +#include + +using namespace std; + +static vector convertStrings(const vector &strs) +{ + vector converted; + for (const auto &s: strs) converted.push_back(QColor(s)); + reverse(converted.begin(), converted.end()); + return converted; +} + +static vector ice = convertStrings({ + // Based on ColorBrewer ylGnBu + "#ffffff", "#ffff00", "#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5", + "#7bccc4", "#4eb3d3", "#2b8cbe", "#0868ac", "#084081", "#042040" + }); + +static vector cherry = convertStrings({ + "#f7f7f7", "#fddbc7", "#f4a582", "#d6604d", "#b2182b", "#dd3497", + "#ae017e", "#7a0177", "#49006a" + }); + +static void +mapDiscrete(double norm, vector &colours, double &r, double &g, double &b) +{ + int n = int(colours.size()); + double m = norm * (n-1); + if (m >= n-1) { colours[n-1].getRgbF(&r, &g, &b, 0); return; } + if (m <= 0) { colours[0].getRgbF(&r, &g, &b, 0); return; } + int base(int(floor(m))); + double prop0 = (base + 1.0) - m, prop1 = m - base; + QColor c0(colours[base]), c1(colours[base+1]); + r = c0.redF() * prop0 + c1.redF() * prop1; + g = c0.greenF() * prop0 + c1.greenF() * prop1; + b = c0.blueF() * prop0 + c1.blueF() * prop1; +} + ColourMapper::ColourMapper(int map, double min, double max) : - QObject(), m_map(map), m_min(min), m_max(max) @@ -47,25 +86,25 @@ QString ColourMapper::getColourMapName(int n) { - if (n >= getColourMapCount()) return tr(""); + if (n >= getColourMapCount()) return QObject::tr(""); StandardMap map = (StandardMap)n; switch (map) { - case DefaultColours: return tr("Default"); - case WhiteOnBlack: return tr("White on Black"); - case BlackOnWhite: return tr("Black on White"); - case RedOnBlue: return tr("Red on Blue"); - case YellowOnBlack: return tr("Yellow on Black"); - case BlueOnBlack: return tr("Blue on Black"); - case Sunset: return tr("Sunset"); - case FruitSalad: return tr("Fruit Salad"); - case Banded: return tr("Banded"); - case Highlight: return tr("Highlight"); - case Printer: return tr("Printer"); - case HighGain: return tr("High Gain"); + case Green: return QObject::tr("Green"); + case WhiteOnBlack: return QObject::tr("White on Black"); + case BlackOnWhite: return QObject::tr("Black on White"); + case Cherry: return QObject::tr("Cherry"); + case Wasp: return QObject::tr("Wasp"); + case Ice: return QObject::tr("Ice"); + case Sunset: return QObject::tr("Sunset"); + case FruitSalad: return QObject::tr("Fruit Salad"); + case Banded: return QObject::tr("Banded"); + case Highlight: return QObject::tr("Highlight"); + case Printer: return QObject::tr("Printer"); + case HighGain: return QObject::tr("High Gain"); } - return tr(""); + return QObject::tr(""); } QColor @@ -85,7 +124,7 @@ switch (map) { - case DefaultColours: + case Green: h = blue - norm * 2.0 * pieslice; s = 0.5f + norm/2.0; v = norm; @@ -101,30 +140,17 @@ hsv = false; break; - case RedOnBlue: - h = blue - pieslice/4.0 + norm * (pieslice + pieslice/4.0); - s = 1.0; - v = norm; + case Cherry: + hsv = false; + mapDiscrete(norm, cherry, r, g, b); break; - case YellowOnBlack: + case Wasp: h = 0.15; s = 1.0; v = norm; break; - case BlueOnBlack: - h = blue; - s = 1.0; - v = norm * 2.0; - if (v > 1.0) { - v = 1.0; - s = 1.0 - (sqrt(norm) - 0.707) * 3.413; - if (s < 0.0) s = 0.0; - if (s > 1.0) s = 1.0; - } - break; - case Sunset: r = (norm - 0.24) * 2.38; if (r > 1.0) r = 1.0; @@ -207,6 +233,10 @@ hsv = false; */ break; + + case Ice: + hsv = false; + mapDiscrete(norm, ice, r, g, b); } if (hsv) { @@ -224,7 +254,7 @@ switch (map) { - case DefaultColours: + case Green: return QColor(255, 150, 50); case WhiteOnBlack: @@ -233,13 +263,13 @@ case BlackOnWhite: return Qt::darkGreen; - case RedOnBlue: + case Cherry: return Qt::green; - case YellowOnBlack: + case Wasp: return QColor::fromHsv(240, 255, 255); - case BlueOnBlack: + case Ice: return Qt::red; case Sunset: @@ -277,12 +307,12 @@ case HighGain: return true; - case DefaultColours: + case Green: case Sunset: case WhiteOnBlack: - case RedOnBlue: - case YellowOnBlack: - case BlueOnBlack: + case Cherry: + case Wasp: + case Ice: case FruitSalad: case Banded: case Highlight: @@ -292,4 +322,29 @@ } } +QPixmap +ColourMapper::getExamplePixmap(QSize size) const +{ + QPixmap pmap(size); + pmap.fill(Qt::white); + QPainter paint(&pmap); + int w = size.width(), h = size.height(); + + int margin = 2; + if (w < 4 || h < 4) margin = 0; + else if (w < 8 || h < 8) margin = 1; + + int n = w - margin*2; + + for (int x = 0; x < n; ++x) { + double value = m_min + ((m_max - m_min) * x) / (n-1); + QColor colour(map(value)); + paint.setPen(colour); + paint.drawLine(x + margin, margin, x + margin, h - margin); + } + + return pmap; +} + + diff -r e8102ff5573b -r dc2af6616c83 layer/ColourMapper.h --- a/layer/ColourMapper.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/ColourMapper.h Fri Jan 13 10:29:50 2017 +0000 @@ -13,33 +13,34 @@ COPYING included with this distribution for more information. */ -#ifndef _COLOUR_MAPPER_H_ -#define _COLOUR_MAPPER_H_ +#ifndef SV_COLOUR_MAPPER_H +#define SV_COLOUR_MAPPER_H #include #include #include +#include /** * A class for mapping intensity values onto various colour maps. */ - -class ColourMapper : public QObject +class ColourMapper { - Q_OBJECT - public: ColourMapper(int map, double minValue, double maxValue); - virtual ~ColourMapper(); + ~ColourMapper(); + + ColourMapper(const ColourMapper &) = default; + ColourMapper &operator=(const ColourMapper &) = default; enum StandardMap { - DefaultColours, + Green, Sunset, WhiteOnBlack, BlackOnWhite, - RedOnBlue, - YellowOnBlack, - BlueOnBlack, + Cherry, + Wasp, + Ice, FruitSalad, Banded, Highlight, @@ -59,6 +60,8 @@ QColor getContrastingColour() const; // for cursors etc bool hasLightBackground() const; + QPixmap getExamplePixmap(QSize size) const; + protected: int m_map; double m_min; diff -r e8102ff5573b -r dc2af6616c83 layer/ColourScale.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ColourScale.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,160 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2016 Chris Cannam and QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "ColourScale.h" + +#include "base/AudioLevel.h" +#include "base/LogRange.h" + +#include +#include + +using namespace std; + +int ColourScale::m_maxPixel = 255; + +ColourScale::ColourScale(Parameters parameters) : + m_params(parameters), + m_mapper(m_params.colourMap, 1.f, double(m_maxPixel)) +{ + if (m_params.minValue >= m_params.maxValue) { + cerr << "ERROR: ColourScale::ColourScale: minValue = " + << m_params.minValue << ", maxValue = " << m_params.maxValue << endl; + throw std::logic_error("maxValue must be greater than minValue"); + } + + m_mappedMin = m_params.minValue; + m_mappedMax = m_params.maxValue; + + if (m_mappedMin < m_params.threshold) { + m_mappedMin = m_params.threshold; + } + + if (m_params.scaleType == ColourScaleType::Log) { + + LogRange::mapRange(m_mappedMin, m_mappedMax); + + } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) { + + m_mappedMin = -1.0; + m_mappedMax = 1.0; + + } else if (m_params.scaleType == ColourScaleType::Absolute) { + + m_mappedMin = fabs(m_mappedMin); + m_mappedMax = fabs(m_mappedMax); + if (m_mappedMin >= m_mappedMax) { + std::swap(m_mappedMin, m_mappedMax); + } + } + + if (m_mappedMin >= m_mappedMax) { + cerr << "ERROR: ColourScale::ColourScale: minValue = " << m_params.minValue + << ", maxValue = " << m_params.maxValue + << ", threshold = " << m_params.threshold + << ", scale = " << int(m_params.scaleType) + << " resulting in mapped minValue = " << m_mappedMin + << ", mapped maxValue = " << m_mappedMax << endl; + throw std::logic_error("maxValue must be greater than minValue [after mapping]"); + } +} + +ColourScale::~ColourScale() +{ +} + +ColourScaleType +ColourScale::getScale() const +{ + return m_params.scaleType; +} + +int +ColourScale::getPixel(double value) const +{ + double maxPixF = m_maxPixel; + + if (m_params.scaleType == ColourScaleType::Phase) { + double half = (maxPixF - 1.f) / 2.f; + int pixel = 1 + int((value * half) / M_PI + half); +// cerr << "phase = " << value << " pixel = " << pixel << endl; + return pixel; + } + + value *= m_params.gain; + + if (value < m_params.threshold) return 0; + + double mapped = value; + + if (m_params.scaleType == ColourScaleType::Log) { + mapped = LogRange::map(value); + } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) { + if (mapped < -1.f) mapped = -1.f; + if (mapped > 1.f) mapped = 1.f; + } else if (m_params.scaleType == ColourScaleType::Absolute) { + if (mapped < 0.f) mapped = -mapped; + } + + mapped *= m_params.multiple; + + if (mapped < m_mappedMin) { + mapped = m_mappedMin; + } + if (mapped > m_mappedMax) { + mapped = m_mappedMax; + } + + double proportion = (mapped - m_mappedMin) / (m_mappedMax - m_mappedMin); + + int pixel = 0; + + if (m_params.scaleType == ColourScaleType::Meter) { + pixel = AudioLevel::multiplier_to_preview(proportion, m_maxPixel-1) + 1; + } else { + pixel = int(proportion * maxPixF) + 1; + } + + if (pixel < 0) { + pixel = 0; + } + if (pixel > m_maxPixel) { + pixel = m_maxPixel; + } + return pixel; +} + +QColor +ColourScale::getColourForPixel(int pixel, int rotation) const +{ + if (pixel < 0) { + pixel = 0; + } + if (pixel > m_maxPixel) { + pixel = m_maxPixel; + } + if (pixel == 0) { + if (m_mapper.hasLightBackground()) { + return Qt::white; + } else { + return Qt::black; + } + } else { + int target = int(pixel) + rotation; + while (target < 1) target += m_maxPixel; + while (target > m_maxPixel) target -= m_maxPixel; + return m_mapper.map(double(target)); + } +} diff -r e8102ff5573b -r dc2af6616c83 layer/ColourScale.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ColourScale.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,120 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2016 Chris Cannam and QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef COLOUR_SCALE_H +#define COLOUR_SCALE_H + +#include "ColourMapper.h" + +enum class ColourScaleType { + Linear, + Meter, + Log, + Phase, + PlusMinusOne, + Absolute +}; + +/** + * Map values within a range onto a set of colours, with a given + * distribution (linear, log etc) and optional colourmap rotation. + */ +class ColourScale +{ +public: + struct Parameters { + Parameters() : colourMap(0), scaleType(ColourScaleType::Linear), + minValue(0.0), maxValue(1.0), + threshold(0.0), gain(1.0), multiple(1.0) { } + + /** A colour map index as used by ColourMapper */ + int colourMap; + + /** Distribution for the scale */ + ColourScaleType scaleType; + + /** Minimum value in source range */ + double minValue; + + /** Maximum value in source range. Must be > minValue */ + double maxValue; + + /** Threshold below which every value is mapped to background + pixel 0 */ + double threshold; + + /** Gain to apply before thresholding, mapping, and clamping */ + double gain; + + /** Multiple to apply after thresholding and mapping. In most + * cases the gain parameter is the one you want instead of + * this, but this can be used for example with Log scale to + * produce the log of some power of the original value, + * e.g. multiple = 2 gives log(x^2). */ + double multiple; + }; + + /** + * Create a ColourScale with the given parameters. + * + * Note that some parameters may be ignored for some scale + * distribution settings. For example, min and max are ignored for + * PlusMinusOneScale and PhaseColourScale and threshold and gain + * are ignored for PhaseColourScale. + */ + ColourScale(Parameters parameters); + ~ColourScale(); + + ColourScale(const ColourScale &) = default; + ColourScale &operator=(const ColourScale &) = default; + + /** + * Return the general type of scale this is. + */ + ColourScaleType getScale() const; + + /** + * Return a pixel number (in the range 0-255 inclusive) + * corresponding to the given value. The pixel 0 is used only for + * values below the threshold supplied in the constructor. All + * other values are mapped onto the range 1-255. + */ + int getPixel(double value) const; + + /** + * Return the colour for the given pixel number (which must be in + * the range 0-255). The pixel 0 is always the background + * colour. Other pixels are mapped taking into account the given + * colourmap rotation (which is also a value in the range 0-255). + */ + QColor getColourForPixel(int pixel, int rotation) const; + + /** + * Return the colour corresponding to the given value. This is + * equivalent to getColourForPixel(getPixel(value), rotation). + */ + QColor getColour(double value, int rotation) const { + return getColourForPixel(getPixel(value), rotation); + } + +private: + Parameters m_params; + ColourMapper m_mapper; + double m_mappedMin; + double m_mappedMax; + static int m_maxPixel; +}; + +#endif diff -r e8102ff5573b -r dc2af6616c83 layer/ColourScaleLayer.h --- a/layer/ColourScaleLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/ColourScaleLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -19,11 +19,20 @@ #include #include +class LayerGeometryProvider; + +/** + * Interface for layers in which a colour scale represents (or can + * sometimes represent, depending on the display mode) the sample + * value. For example, TimeValueLayer uses colour scale when in + * segment mode and so provides this interface for use by the + * LogColourScale or LinearColourScale scale renderers. + */ class ColourScaleLayer { public: virtual QString getScaleUnits() const = 0; - virtual QColor getColourForValue(View *v, double value) const = 0; + virtual QColor getColourForValue(LayerGeometryProvider *v, double value) const = 0; }; #endif diff -r e8102ff5573b -r dc2af6616c83 layer/FlexiNoteLayer.cpp --- a/layer/FlexiNoteLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/FlexiNoteLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -22,15 +22,18 @@ #include "base/Pitch.h" #include "base/LogRange.h" #include "base/RangeMapper.h" + #include "ColourDatabase.h" -#include "view/View.h" - +#include "LayerGeometryProvider.h" #include "PianoScale.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" +#include "PaintAssistant.h" #include "data/model/FlexiNoteModel.h" +#include "view/View.h" + #include "widgets/ItemEditDialog.h" #include "widgets/TextAbbrev.h" @@ -204,7 +207,7 @@ } bool -FlexiNoteLayer::isLayerScrollable(const View *v) const +FlexiNoteLayer::isLayerScrollable(const LayerGeometryProvider *v) const { QPoint discard; return !v->shouldIlluminateLocalFeatures(this, discard); @@ -405,7 +408,7 @@ } FlexiNoteModel::PointList -FlexiNoteLayer::getLocalPoints(View *v, int x) const +FlexiNoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const { if (!m_model) return FlexiNoteModel::PointList(); @@ -448,7 +451,7 @@ } bool -FlexiNoteLayer::getPointToDrag(View *v, int x, int y, FlexiNoteModel::Point &p) const +FlexiNoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &p) const { if (!m_model) return false; @@ -476,7 +479,7 @@ } bool -FlexiNoteLayer::getNoteToEdit(View *v, int x, int y, FlexiNoteModel::Point &p) const +FlexiNoteLayer::getNoteToEdit(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &p) const { // GF: find the note that is closest to the cursor if (!m_model) return false; @@ -505,7 +508,7 @@ } QString -FlexiNoteLayer::getFeatureDescription(View *v, QPoint &pos) const +FlexiNoteLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const { int x = pos.x(); @@ -593,7 +596,7 @@ } bool -FlexiNoteLayer::snapToFeatureFrame(View *v, sv_frame_t &frame, +FlexiNoteLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { @@ -673,7 +676,7 @@ } void -FlexiNoteLayer::getScaleExtents(View *v, double &min, double &max, bool &log) const +FlexiNoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const { min = 0.0; max = 0.0; @@ -730,11 +733,11 @@ } int -FlexiNoteLayer::getYForValue(View *v, double val) const +FlexiNoteLayer::getYForValue(LayerGeometryProvider *v, double val) const { double min = 0.0, max = 0.0; bool logarithmic = false; - int h = v->height(); + int h = v->getPaintHeight(); getScaleExtents(v, min, max, logarithmic); @@ -765,11 +768,11 @@ } double -FlexiNoteLayer::getValueForY(View *v, int y) const +FlexiNoteLayer::getValueForY(LayerGeometryProvider *v, int y) const { double min = 0.0, max = 0.0; bool logarithmic = false; - int h = v->height(); + int h = v->getPaintHeight(); getScaleExtents(v, min, max, logarithmic); @@ -794,7 +797,7 @@ } void -FlexiNoteLayer::paint(View *v, QPainter &paint, QRect rect) const +FlexiNoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) return; @@ -860,41 +863,41 @@ !FlexiNoteModel::Point::Comparator()(illuminatePoint, p) && !FlexiNoteModel::Point::Comparator()(p, illuminatePoint)) { - paint.drawLine(x, -1, x, v->height() + 1); - paint.drawLine(x+w, -1, x+w, v->height() + 1); + paint.drawLine(x, -1, x, v->getPaintHeight() + 1); + paint.drawLine(x+w, -1, x+w, v->getPaintHeight() + 1); paint.setPen(v->getForeground()); // paint.setBrush(v->getForeground()); QString vlabel = QString("freq: %1%2").arg(p.value).arg(m_model->getScaleUnits()); - // v->drawVisibleText(paint, + // PaintAssistant::drawVisibleText(v, paint, // x - paint.fontMetrics().width(vlabel) - 2, // y + paint.fontMetrics().height()/2 // - paint.fontMetrics().descent(), - // vlabel, View::OutlinedText); - v->drawVisibleText(paint, + // vlabel, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - 2 - paint.fontMetrics().height() - paint.fontMetrics().descent(), - vlabel, View::OutlinedText); + vlabel, PaintAssistant::OutlinedText); QString hlabel = "dur: " + QString(RealTime::frame2RealTime (p.duration, m_model->getSampleRate()).toText(true).c_str()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - paint.fontMetrics().descent() - 2, - hlabel, View::OutlinedText); + hlabel, PaintAssistant::OutlinedText); QString llabel = QString("%1").arg(p.label); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y + h + 2 + paint.fontMetrics().descent(), - llabel, View::OutlinedText); + llabel, PaintAssistant::OutlinedText); QString nlabel = QString("%1").arg(noteNumber); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x + paint.fontMetrics().averageCharWidth() / 2, y + h/2 - paint.fontMetrics().descent(), - nlabel, View::OutlinedText); + nlabel, PaintAssistant::OutlinedText); } paint.drawRect(x, y - h/2, w, h); @@ -904,7 +907,7 @@ } int -FlexiNoteLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const +FlexiNoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const { if (!m_model || shouldAutoAlign()) { return 0; @@ -918,7 +921,7 @@ } void -FlexiNoteLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const +FlexiNoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const { if (!m_model || m_model->getPoints().empty()) return; @@ -927,7 +930,7 @@ bool logarithmic; int w = getVerticalScaleWidth(v, false, paint); - int h = v->height(); + int h = v->getPaintHeight(); getScaleExtents(v, min, max, logarithmic); @@ -956,7 +959,7 @@ } void -FlexiNoteLayer::drawStart(View *v, QMouseEvent *e) +FlexiNoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl; @@ -980,7 +983,7 @@ } void -FlexiNoteLayer::drawDrag(View *v, QMouseEvent *e) +FlexiNoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl; @@ -1009,7 +1012,7 @@ } void -FlexiNoteLayer::drawEnd(View *, QMouseEvent *) +FlexiNoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *) { // SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl; if (!m_model || !m_editing) return; @@ -1019,7 +1022,7 @@ } void -FlexiNoteLayer::eraseStart(View *v, QMouseEvent *e) +FlexiNoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return; @@ -1034,12 +1037,12 @@ } void -FlexiNoteLayer::eraseDrag(View *, QMouseEvent *) +FlexiNoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *) { } void -FlexiNoteLayer::eraseEnd(View *v, QMouseEvent *e) +FlexiNoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model || !m_editing) return; @@ -1059,7 +1062,7 @@ } void -FlexiNoteLayer::editStart(View *v, QMouseEvent *e) +FlexiNoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl; std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl; @@ -1110,7 +1113,7 @@ } void -FlexiNoteLayer::editDrag(View *v, QMouseEvent *e) +FlexiNoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl; std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl; @@ -1190,7 +1193,7 @@ } void -FlexiNoteLayer::editEnd(View *v, QMouseEvent *e) +FlexiNoteLayer::editEnd(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl; std::cerr << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl; @@ -1229,7 +1232,7 @@ } void -FlexiNoteLayer::splitStart(View *v, QMouseEvent *e) +FlexiNoteLayer::splitStart(LayerGeometryProvider *v, QMouseEvent *e) { // GF: note splitting starts (!! remove printing soon) std::cerr << "splitStart (n.b. editStart will be called later, if the user drags the mouse)" << std::endl; @@ -1252,7 +1255,7 @@ } void -FlexiNoteLayer::splitEnd(View *v, QMouseEvent *e) +FlexiNoteLayer::splitEnd(LayerGeometryProvider *v, QMouseEvent *e) { // GF: note splitting ends. (!! remove printing soon) std::cerr << "splitEnd" << std::endl; @@ -1271,13 +1274,13 @@ } void -FlexiNoteLayer::splitNotesAt(View *v, sv_frame_t frame) +FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame) { splitNotesAt(v, frame, 0); } void -FlexiNoteLayer::splitNotesAt(View *v, sv_frame_t frame, QMouseEvent *e) +FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e) { FlexiNoteModel::PointList onPoints = m_model->getPoints(frame); if (onPoints.empty()) return; @@ -1317,7 +1320,7 @@ } void -FlexiNoteLayer::addNote(View *v, QMouseEvent *e) +FlexiNoteLayer::addNote(LayerGeometryProvider *v, QMouseEvent *e) { std::cerr << "addNote" << std::endl; if (!m_model) return; @@ -1356,14 +1359,14 @@ } SparseTimeValueModel * -FlexiNoteLayer::getAssociatedPitchModel(View *v) const +FlexiNoteLayer::getAssociatedPitchModel(LayerGeometryProvider *v) const { // Better than we used to do, but still not very satisfactory // cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl; - for (int i = 0; i < v->getLayerCount(); ++i) { - Layer *layer = v->getLayer(i); + for (int i = 0; i < v->getView()->getLayerCount(); ++i) { + Layer *layer = v->getView()->getLayer(i); if (layer && layer->getLayerPresentationName() != "candidate") { // cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl; @@ -1381,7 +1384,7 @@ } void -FlexiNoteLayer::snapSelectedNotesToPitchTrack(View *v, Selection s) +FlexiNoteLayer::snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s) { if (!m_model) return; @@ -1419,7 +1422,7 @@ } void -FlexiNoteLayer::mergeNotes(View *v, Selection s, bool inclusive) +FlexiNoteLayer::mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive) { FlexiNoteModel::PointList points = m_model->getPoints(s.getStartFrame(), s.getEndFrame()); @@ -1462,8 +1465,7 @@ } bool -FlexiNoteLayer::updateNoteValueFromPitchCurve(View *v, - FlexiNoteModel::Point ¬e) const +FlexiNoteLayer::updateNoteValueFromPitchCurve(LayerGeometryProvider *v, FlexiNoteModel::Point ¬e) const { SparseTimeValueModel *model = getAssociatedPitchModel(v); if (!model) return false; @@ -1507,13 +1509,13 @@ } void -FlexiNoteLayer::mouseMoveEvent(View *v, QMouseEvent *e) +FlexiNoteLayer::mouseMoveEvent(LayerGeometryProvider *v, QMouseEvent *e) { // GF: context sensitive cursors - // v->setCursor(Qt::ArrowCursor); + // v->getView()->setCursor(Qt::ArrowCursor); FlexiNoteModel::Point note(0); if (!getNoteToEdit(v, e->x(), e->y(), note)) { - // v->setCursor(Qt::UpArrowCursor); + // v->getView()->setCursor(Qt::UpArrowCursor); return; } @@ -1524,28 +1526,28 @@ closeToTop, closeToBottom); if (closeToLeft) { - v->setCursor(Qt::SizeHorCursor); + v->getView()->setCursor(Qt::SizeHorCursor); m_editMode = LeftBoundary; cerr << "edit mode -> LeftBoundary" << endl; } else if (closeToRight) { - v->setCursor(Qt::SizeHorCursor); + v->getView()->setCursor(Qt::SizeHorCursor); m_editMode = RightBoundary; cerr << "edit mode -> RightBoundary" << endl; } else if (closeToTop) { - v->setCursor(Qt::CrossCursor); + v->getView()->setCursor(Qt::CrossCursor); m_editMode = DragNote; cerr << "edit mode -> DragNote" << endl; } else if (closeToBottom) { - v->setCursor(Qt::UpArrowCursor); + v->getView()->setCursor(Qt::UpArrowCursor); m_editMode = SplitNote; cerr << "edit mode -> SplitNote" << endl; } else { - v->setCursor(Qt::ArrowCursor); + v->getView()->setCursor(Qt::ArrowCursor); } } void -FlexiNoteLayer::getRelativeMousePosition(View *v, FlexiNoteModel::Point ¬e, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const +FlexiNoteLayer::getRelativeMousePosition(LayerGeometryProvider *v, FlexiNoteModel::Point ¬e, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const { // GF: TODO: consoloidate the tolerance values if (!m_model) return; @@ -1574,7 +1576,7 @@ bool -FlexiNoteLayer::editOpen(View *v, QMouseEvent *e) +FlexiNoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) { std::cerr << "Opening note editor dialog" << std::endl; if (!m_model) return false; @@ -1728,7 +1730,7 @@ } void -FlexiNoteLayer::copy(View *v, Selection s, Clipboard &to) +FlexiNoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) { if (!m_model) return; @@ -1746,7 +1748,7 @@ } bool -FlexiNoteLayer::paste(View *v, const Clipboard &from, sv_frame_t /*frameOffset */, bool /* interactive */) +FlexiNoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /*frameOffset */, bool /* interactive */) { if (!m_model) return false; @@ -1757,7 +1759,7 @@ if (clipboardHasDifferentAlignment(v, from)) { QMessageBox::StandardButton button = - QMessageBox::question(v, tr("Re-align pasted items?"), + QMessageBox::question(v->getView(), tr("Re-align pasted items?"), tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); @@ -1894,7 +1896,7 @@ } void -FlexiNoteLayer::setVerticalRangeToNoteRange(View *v) +FlexiNoteLayer::setVerticalRangeToNoteRange(LayerGeometryProvider *v) { double minf = std::numeric_limits::max(); double maxf = 0; @@ -1910,7 +1912,7 @@ std::cerr << "min frequency:" << minf << ", max frequency: " << maxf << std::endl; if (hasNotes) { - v->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5); + v->getView()->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5); // MM: this is a hack because we rely on // * this layer being automatically aligned to layer 1 // * layer one is a log frequency layer. diff -r e8102ff5573b -r dc2af6616c83 layer/FlexiNoteLayer.h --- a/layer/FlexiNoteLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/FlexiNoteLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -38,50 +38,50 @@ public: FlexiNoteLayer(); - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; - virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const; - virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const; + virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const; + virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; - virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame, + virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual void drawStart(View *v, QMouseEvent *); - virtual void drawDrag(View *v, QMouseEvent *); - virtual void drawEnd(View *v, QMouseEvent *); + virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void eraseStart(View *v, QMouseEvent *); - virtual void eraseDrag(View *v, QMouseEvent *); - virtual void eraseEnd(View *v, QMouseEvent *); + virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void editStart(View *v, QMouseEvent *); - virtual void editDrag(View *v, QMouseEvent *); - virtual void editEnd(View *v, QMouseEvent *); + virtual void editStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void splitStart(View *v, QMouseEvent *); - virtual void splitEnd(View *v, QMouseEvent *); + virtual void splitStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void splitEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void addNote(View *v, QMouseEvent *e); + virtual void addNote(LayerGeometryProvider *v, QMouseEvent *e); - virtual void mouseMoveEvent(View *v, QMouseEvent *); + virtual void mouseMoveEvent(LayerGeometryProvider *v, QMouseEvent *); - virtual bool editOpen(View *v, QMouseEvent *); + virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *); virtual void moveSelection(Selection s, sv_frame_t newStartFrame); virtual void resizeSelection(Selection s, Selection newSize); virtual void deleteSelection(Selection s); virtual void deleteSelectionInclusive(Selection s); - virtual void copy(View *v, Selection s, Clipboard &to); - virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset, + virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to); + virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool interactive); - void splitNotesAt(View *v, sv_frame_t frame); - void snapSelectedNotesToPitchTrack(View *v, Selection s); - void mergeNotes(View *v, Selection s, bool inclusive); + void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame); + void snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s); + void mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive); virtual const Model *getModel() const { return m_model; } void setModel(FlexiNoteModel *model); @@ -116,11 +116,11 @@ void setVerticalScale(VerticalScale scale); VerticalScale getVerticalScale() const { return m_verticalScale; } - virtual bool isLayerScrollable(const View *v) const; + virtual bool isLayerScrollable(const LayerGeometryProvider *v) const; virtual bool isLayerEditable() const { return true; } - virtual int getCompletion(View *) const { return m_model->getCompletion(); } + virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); } virtual bool getValueExtents(double &min, double &max, bool &log, QString &unit) const; @@ -156,11 +156,11 @@ void setProperties(const QXmlAttributes &attributes); - void setVerticalRangeToNoteRange(View *v); + void setVerticalRangeToNoteRange(LayerGeometryProvider *v); /// VerticalScaleLayer methods - virtual int getYForValue(View *v, double value) const; - virtual double getValueForY(View *v, int y) const; + virtual int getYForValue(LayerGeometryProvider *v, double value) const; + virtual double getValueForY(LayerGeometryProvider *v, int y) const; virtual QString getScaleUnits() const; signals: @@ -168,23 +168,19 @@ void materialiseReAnalysis(); protected: - void getScaleExtents(View *, double &min, double &max, bool &log) const; + void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const; bool shouldConvertMIDIToHz() const; virtual int getDefaultColourHint(bool dark, bool &impose); - FlexiNoteModel::PointList getLocalPoints(View *v, int) const; + FlexiNoteModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const; - bool getPointToDrag(View *v, int x, int y, FlexiNoteModel::Point &) const; - bool getNoteToEdit(View *v, int x, int y, FlexiNoteModel::Point &) const; - void getRelativeMousePosition(View *v, FlexiNoteModel::Point ¬e, - int x, int y, - bool &closeToLeft, bool &closeToRight, - bool &closeToTop, bool &closeToBottom) const; - SparseTimeValueModel *getAssociatedPitchModel(View *v) const; - bool updateNoteValueFromPitchCurve(View *v, - FlexiNoteModel::Point ¬e) const; - void splitNotesAt(View *v, sv_frame_t frame, QMouseEvent *e); + bool getPointToDrag(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &) const; + bool getNoteToEdit(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &) const; + void getRelativeMousePosition(LayerGeometryProvider *v, FlexiNoteModel::Point ¬e, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const; + SparseTimeValueModel *getAssociatedPitchModel(LayerGeometryProvider *v) const; + bool updateNoteValueFromPitchCurve(LayerGeometryProvider *v, FlexiNoteModel::Point ¬e) const; + void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e); FlexiNoteModel *m_model; bool m_editing; diff -r e8102ff5573b -r dc2af6616c83 layer/ImageLayer.cpp --- a/layer/ImageLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/ImageLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -116,14 +116,14 @@ } bool -ImageLayer::isLayerScrollable(const View *) const +ImageLayer::isLayerScrollable(const LayerGeometryProvider *) const { return true; } ImageModel::PointList -ImageLayer::getLocalPoints(View *v, int x, int ) const +ImageLayer::getLocalPoints(LayerGeometryProvider *v, int x, int ) const { if (!m_model) return ImageModel::PointList(); @@ -169,7 +169,7 @@ } QString -ImageLayer::getFeatureDescription(View *v, QPoint &pos) const +ImageLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const { int x = pos.x(); @@ -208,7 +208,7 @@ //!!! too much overlap with TimeValueLayer/TimeInstantLayer/TextLayer bool -ImageLayer::snapToFeatureFrame(View *v, sv_frame_t &frame, +ImageLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { @@ -280,7 +280,7 @@ } void -ImageLayer::paint(View *v, QPainter &paint, QRect rect) const +ImageLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) return; @@ -290,7 +290,7 @@ // Profiler profiler("ImageLayer::paint", true); // int x0 = rect.left(), x1 = rect.right(); - int x0 = 0, x1 = v->width(); + int x0 = 0, x1 = v->getPaintWidth(); sv_frame_t frame0 = v->getFrameForX(x0); sv_frame_t frame1 = v->getFrameForX(x1); @@ -299,7 +299,7 @@ if (points.empty()) return; paint.save(); - paint.setClipRect(rect.x(), 0, rect.width(), v->height()); + paint.setClipRect(rect.x(), 0, rect.width(), v->getPaintHeight()); QColor penColour; penColour = v->getForeground(); @@ -338,7 +338,7 @@ } void -ImageLayer::drawImage(View *v, QPainter &paint, const ImageModel::Point &p, +ImageLayer::drawImage(LayerGeometryProvider *v, QPainter &paint, const ImageModel::Point &p, int x, int nx) const { QString label = p.label; @@ -358,12 +358,12 @@ int bottomMargin = 10; int spacing = 5; - if (v->height() < 100) { + if (v->getPaintHeight() < 100) { topMargin = 5; bottomMargin = 5; } - int maxBoxHeight = v->height() - topMargin - bottomMargin; + int maxBoxHeight = v->getPaintHeight() - topMargin - bottomMargin; int availableWidth = nx - x - 3; if (availableWidth < 20) availableWidth = 20; @@ -372,7 +372,7 @@ if (label != "") { - int likelyHeight = v->height() / 4; + int likelyHeight = v->getPaintHeight() / 4; int likelyWidth = // available height times image aspect ((maxBoxHeight - likelyHeight) * imageSize.width()) @@ -435,10 +435,10 @@ division += paint.fontMetrics().height(); } - bottomMargin = v->height() - topMargin - boxHeight; - if (bottomMargin > topMargin + v->height()/7) { - topMargin += v->height()/8; - bottomMargin -= v->height()/8; + bottomMargin = v->getPaintHeight() - topMargin - boxHeight; + if (bottomMargin > topMargin + v->getPaintHeight()/7) { + topMargin += v->getPaintHeight()/8; + bottomMargin -= v->getPaintHeight()/8; } paint.drawRect(x - 1, @@ -480,7 +480,7 @@ } void -ImageLayer::setLayerDormant(const View *v, bool dormant) +ImageLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant) { if (dormant) { // Delete the images named in the view's scaled map from the @@ -517,7 +517,7 @@ } QImage -ImageLayer::getImage(View *v, QString name, QSize maxSize) const +ImageLayer::getImage(LayerGeometryProvider *v, QString name, QSize maxSize) const { // SVDEBUG << "ImageLayer::getImage(" << v << ", " << name << ", (" // << maxSize.width() << "x" << maxSize.height() << "))" << endl; @@ -554,7 +554,7 @@ } void -ImageLayer::drawStart(View *v, QMouseEvent *e) +ImageLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "ImageLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl; @@ -578,7 +578,7 @@ } void -ImageLayer::drawDrag(View *v, QMouseEvent *e) +ImageLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "ImageLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl; @@ -594,7 +594,7 @@ } void -ImageLayer::drawEnd(View *, QMouseEvent *) +ImageLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *) { // SVDEBUG << "ImageLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl; if (!m_model || !m_editing) return; @@ -638,7 +638,7 @@ } void -ImageLayer::editStart(View *v, QMouseEvent *e) +ImageLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << endl; @@ -660,7 +660,7 @@ } void -ImageLayer::editDrag(View *v, QMouseEvent *e) +ImageLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model || !m_editing) return; @@ -680,7 +680,7 @@ } void -ImageLayer::editEnd(View *, QMouseEvent *) +ImageLayer::editEnd(LayerGeometryProvider *, QMouseEvent *) { // SVDEBUG << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl; if (!m_model || !m_editing) return; @@ -694,7 +694,7 @@ } bool -ImageLayer::editOpen(View *v, QMouseEvent *e) +ImageLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return false; @@ -801,7 +801,7 @@ } void -ImageLayer::copy(View *v, Selection s, Clipboard &to) +ImageLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) { if (!m_model) return; @@ -819,7 +819,7 @@ } bool -ImageLayer::paste(View *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) +ImageLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) { if (!m_model) return false; @@ -830,7 +830,7 @@ if (clipboardHasDifferentAlignment(v, from)) { QMessageBox::StandardButton button = - QMessageBox::question(v, tr("Re-align pasted items?"), + QMessageBox::question(v->getView(), tr("Re-align pasted items?"), tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); diff -r e8102ff5573b -r dc2af6616c83 layer/ImageLayer.h --- a/layer/ImageLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/ImageLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -38,31 +38,31 @@ ImageLayer(); virtual ~ImageLayer(); - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; - virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame, + virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual void drawStart(View *v, QMouseEvent *); - virtual void drawDrag(View *v, QMouseEvent *); - virtual void drawEnd(View *v, QMouseEvent *); + virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void editStart(View *v, QMouseEvent *); - virtual void editDrag(View *v, QMouseEvent *); - virtual void editEnd(View *v, QMouseEvent *); + virtual void editStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *); virtual void moveSelection(Selection s, sv_frame_t newStartFrame); virtual void resizeSelection(Selection s, Selection newSize); virtual void deleteSelection(Selection s); - virtual void copy(View *v, Selection s, Clipboard &to); - virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset, + virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to); + virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool interactive); - virtual bool editOpen(View *, QMouseEvent *); // on double-click + virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *); // on double-click virtual const Model *getModel() const { return m_model; } void setModel(ImageModel *model); @@ -80,11 +80,11 @@ return ColourAbsent; } - virtual bool isLayerScrollable(const View *v) const; + virtual bool isLayerScrollable(const LayerGeometryProvider *v) const; virtual bool isLayerEditable() const { return true; } - virtual int getCompletion(View *) const { return m_model->getCompletion(); } + virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); } virtual bool getValueExtents(double &min, double &max, bool &logarithmic, QString &unit) const; @@ -92,9 +92,9 @@ virtual void toXml(QTextStream &stream, QString indent = "", QString extraAttributes = "") const; - virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; } + virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; } - virtual void setLayerDormant(const View *v, bool dormant); + virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant); void setProperties(const QXmlAttributes &attributes); @@ -105,18 +105,18 @@ void fileSourceReady(); protected: - ImageModel::PointList getLocalPoints(View *v, int x, int y) const; + ImageModel::PointList getLocalPoints(LayerGeometryProvider *v, int x, int y) const; bool getImageOriginalSize(QString name, QSize &size) const; - QImage getImage(View *v, QString name, QSize maxSize) const; + QImage getImage(LayerGeometryProvider *v, QString name, QSize maxSize) const; - void drawImage(View *v, QPainter &paint, const ImageModel::Point &p, + void drawImage(LayerGeometryProvider *v, QPainter &paint, const ImageModel::Point &p, int x, int nx) const; //!!! how to reap no-longer-used images? typedef std::map ImageMap; - typedef std::map ViewImageMap; + typedef std::map ViewImageMap; typedef std::map FileSourceMap; static ImageMap m_images; diff -r e8102ff5573b -r dc2af6616c83 layer/Layer.cpp --- a/layer/Layer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/Layer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -115,7 +115,7 @@ } void -Layer::setLayerDormant(const View *v, bool dormant) +Layer::setLayerDormant(const LayerGeometryProvider *v, bool dormant) { const void *vv = (const void *)v; QMutexLocker locker(&m_dormancyMutex); @@ -123,7 +123,7 @@ } bool -Layer::isLayerDormant(const View *v) const +Layer::isLayerDormant(const LayerGeometryProvider *v) const { const void *vv = (const void *)v; QMutexLocker locker(&m_dormancyMutex); @@ -132,14 +132,14 @@ } void -Layer::showLayer(View *view, bool show) +Layer::showLayer(LayerGeometryProvider *view, bool show) { setLayerDormant(view, !show); emit layerParametersChanged(); } bool -Layer::getXScaleValue(const View *v, int x, double &value, QString &unit) const +Layer::getXScaleValue(const LayerGeometryProvider *v, int x, double &value, QString &unit) const { if (!hasTimeXAxis()) return false; @@ -152,7 +152,7 @@ } bool -Layer::getYScaleDifference(const View *v, int y0, int y1, +Layer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1, double &diff, QString &unit) const { double v0, v1; @@ -166,31 +166,31 @@ } sv_frame_t -Layer::alignToReference(View *v, sv_frame_t frame) const +Layer::alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const { const Model *m = getModel(); SVDEBUG << "Layer::alignToReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : 0) << endl; if (m && m->getAlignmentReference()) { return m->alignToReference(frame); } else { - return v->alignToReference(frame); + return v->getView()->alignToReference(frame); } } sv_frame_t -Layer::alignFromReference(View *v, sv_frame_t frame) const +Layer::alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const { const Model *m = getModel(); SVDEBUG << "Layer::alignFromReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : 0) << endl; if (m && m->getAlignmentReference()) { return m->alignFromReference(frame); } else { - return v->alignFromReference(frame); + return v->getView()->alignFromReference(frame); } } bool -Layer::clipboardHasDifferentAlignment(View *v, const Clipboard &clip) const +Layer::clipboardHasDifferentAlignment(LayerGeometryProvider *v, const Clipboard &clip) const { // Notes on pasting to an aligned layer: // @@ -371,7 +371,7 @@ } void -Layer::measureStart(View *v, QMouseEvent *e) +Layer::measureStart(LayerGeometryProvider *v, QMouseEvent *e) { setMeasureRectFromPixrect(v, m_draggingRect, QRect(e->x(), e->y(), 0, 0)); @@ -379,7 +379,7 @@ } void -Layer::measureDrag(View *v, QMouseEvent *e) +Layer::measureDrag(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_haveDraggingRect) return; @@ -391,7 +391,7 @@ } void -Layer::measureEnd(View *v, QMouseEvent *e) +Layer::measureEnd(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_haveDraggingRect) return; measureDrag(v, e); @@ -405,7 +405,7 @@ } void -Layer::measureDoubleClick(View *, QMouseEvent *) +Layer::measureDoubleClick(LayerGeometryProvider *, QMouseEvent *) { // nothing, in the base class } @@ -425,7 +425,7 @@ } void -Layer::paintMeasurementRects(View *v, QPainter &paint, +Layer::paintMeasurementRects(LayerGeometryProvider *v, QPainter &paint, bool showFocus, QPoint focusPoint) const { updateMeasurePixrects(v); @@ -457,7 +457,7 @@ } bool -Layer::nearestMeasurementRectChanged(View *v, QPoint prev, QPoint now) const +Layer::nearestMeasurementRectChanged(LayerGeometryProvider *v, QPoint prev, QPoint now) const { updateMeasurePixrects(v); @@ -468,7 +468,7 @@ } void -Layer::updateMeasurePixrects(View *v) const +Layer::updateMeasurePixrects(LayerGeometryProvider *v) const { sv_frame_t sf = v->getStartFrame(); sv_frame_t ef = v->getEndFrame(); @@ -507,26 +507,26 @@ } void -Layer::updateMeasureRectYCoords(View *v, const MeasureRect &r) const +Layer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const { - int y0 = int(lrint(r.startY * v->height())); - int y1 = int(lrint(r.endY * v->height())); + int y0 = int(lrint(r.startY * v->getPaintHeight())); + int y1 = int(lrint(r.endY * v->getPaintHeight())); r.pixrect = QRect(r.pixrect.x(), y0, r.pixrect.width(), y1 - y0); } void -Layer::setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const +Layer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const { if (start) { - r.startY = double(y) / double(v->height()); + r.startY = double(y) / double(v->getPaintHeight()); r.endY = r.startY; } else { - r.endY = double(y) / double(v->height()); + r.endY = double(y) / double(v->getPaintHeight()); } } void -Layer::setMeasureRectFromPixrect(View *v, MeasureRect &r, QRect pixrect) const +Layer::setMeasureRectFromPixrect(LayerGeometryProvider *v, MeasureRect &r, QRect pixrect) const { r.pixrect = pixrect; r.haveFrames = hasTimeXAxis(); @@ -566,13 +566,13 @@ } void -Layer::paintMeasurementRect(View *v, QPainter &paint, +Layer::paintMeasurementRect(LayerGeometryProvider *v, QPainter &paint, const MeasureRect &r, bool focus) const { if (r.haveFrames) { int x0 = -1; - int x1 = v->width() + 1; + int x1 = v->getPaintWidth() + 1; if (r.startFrame >= v->getStartFrame()) { x0 = v->getXForFrame(r.startFrame); diff -r e8102ff5573b -r dc2af6616c83 layer/Layer.h --- a/layer/Layer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/Layer.h Fri Jan 13 10:29:50 2017 +0000 @@ -39,6 +39,7 @@ class Model; class QPainter; class View; +class LayerGeometryProvider; class QMouseEvent; class Clipboard; class RangeMapper; @@ -83,12 +84,13 @@ /** * Paint the given rectangle of this layer onto the given view * using the given painter, superimposing it on top of any - * existing material in that view. The view is provided here - * because it is possible for one layer to exist in more than one - * view, so the dimensions of the view may vary from one paint - * call to another (without any view having been resized). + * existing material in that view. The LayerGeometryProvider (an + * interface implemented by View) is provided here because it is + * possible for one layer to exist in more than one view, so the + * dimensions of the view may vary from one paint call to another + * (without any view having been resized). */ - virtual void paint(View *, QPainter &, QRect) const = 0; + virtual void paint(LayerGeometryProvider *, QPainter &, QRect) const = 0; /** * Enable or disable synchronous painting. If synchronous @@ -128,25 +130,25 @@ virtual QString getLayerPresentationName() const; virtual QPixmap getLayerPresentationPixmap(QSize) const { return QPixmap(); } - virtual int getVerticalScaleWidth(View *, bool detailed, + virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &) const = 0; - virtual void paintVerticalScale(View *, bool /* detailed */, + virtual void paintVerticalScale(LayerGeometryProvider *, bool /* detailed */, QPainter &, QRect) const { } - virtual bool getCrosshairExtents(View *, QPainter &, QPoint /* cursorPos */, + virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint /* cursorPos */, std::vector &) const { return false; } - virtual void paintCrosshairs(View *, QPainter &, QPoint) const { } + virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const { } - virtual void paintMeasurementRects(View *, QPainter &, + virtual void paintMeasurementRects(LayerGeometryProvider *, QPainter &, bool showFocus, QPoint focusPoint) const; - virtual bool nearestMeasurementRectChanged(View *, QPoint prev, + virtual bool nearestMeasurementRectChanged(LayerGeometryProvider *, QPoint prev, QPoint now) const; - virtual QString getFeatureDescription(View *, QPoint &) const { + virtual QString getFeatureDescription(LayerGeometryProvider *, QPoint &) const { return ""; } @@ -180,7 +182,7 @@ * (and leave frame unmodified). If returning true, also return * the resolution of the model in this layer in sample frames. */ - virtual bool snapToFeatureFrame(View * /* v */, + virtual bool snapToFeatureFrame(LayerGeometryProvider * /* v */, sv_frame_t & /* frame */, int &resolution, SnapType /* snap */) const { @@ -204,7 +206,7 @@ * (and leave frame unmodified). If returning true, also return * the resolution of the model in this layer in sample frames. */ - virtual bool snapToSimilarFeature(View * /* v */, + virtual bool snapToSimilarFeature(LayerGeometryProvider * /* v */, sv_frame_t & /* source frame */, int &resolution, SnapType /* snap */) const { @@ -217,30 +219,30 @@ // Layer needs to get actual mouse events, I guess. Draw mode is // probably the easier. - virtual void drawStart(View *, QMouseEvent *) { } - virtual void drawDrag(View *, QMouseEvent *) { } - virtual void drawEnd(View *, QMouseEvent *) { } + virtual void drawStart(LayerGeometryProvider *, QMouseEvent *) { } + virtual void drawDrag(LayerGeometryProvider *, QMouseEvent *) { } + virtual void drawEnd(LayerGeometryProvider *, QMouseEvent *) { } - virtual void eraseStart(View *, QMouseEvent *) { } - virtual void eraseDrag(View *, QMouseEvent *) { } - virtual void eraseEnd(View *, QMouseEvent *) { } + virtual void eraseStart(LayerGeometryProvider *, QMouseEvent *) { } + virtual void eraseDrag(LayerGeometryProvider *, QMouseEvent *) { } + virtual void eraseEnd(LayerGeometryProvider *, QMouseEvent *) { } - virtual void editStart(View *, QMouseEvent *) { } - virtual void editDrag(View *, QMouseEvent *) { } - virtual void editEnd(View *, QMouseEvent *) { } + virtual void editStart(LayerGeometryProvider *, QMouseEvent *) { } + virtual void editDrag(LayerGeometryProvider *, QMouseEvent *) { } + virtual void editEnd(LayerGeometryProvider *, QMouseEvent *) { } - virtual void splitStart(View *, QMouseEvent *) { } - virtual void splitEnd(View *, QMouseEvent *) { } - virtual void addNote(View *, QMouseEvent *) { }; + virtual void splitStart(LayerGeometryProvider *, QMouseEvent *) { } + virtual void splitEnd(LayerGeometryProvider *, QMouseEvent *) { } + virtual void addNote(LayerGeometryProvider *, QMouseEvent *) { }; // Measurement rectangle (or equivalent). Unlike draw and edit, // the base Layer class can provide working implementations of // these for most situations. // - virtual void measureStart(View *, QMouseEvent *); - virtual void measureDrag(View *, QMouseEvent *); - virtual void measureEnd(View *, QMouseEvent *); - virtual void measureDoubleClick(View *, QMouseEvent *); + virtual void measureStart(LayerGeometryProvider *, QMouseEvent *); + virtual void measureDrag(LayerGeometryProvider *, QMouseEvent *); + virtual void measureEnd(LayerGeometryProvider *, QMouseEvent *); + virtual void measureDoubleClick(LayerGeometryProvider *, QMouseEvent *); virtual bool haveCurrentMeasureRect() const { return m_haveCurrentMeasureRect; @@ -252,13 +254,13 @@ * double-click). If there is no item or editing is not * supported, return false. */ - virtual bool editOpen(View *, QMouseEvent *) { return false; } + virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *) { return false; } virtual void moveSelection(Selection, sv_frame_t /* newStartFrame */) { } virtual void resizeSelection(Selection, Selection /* newSize */) { } virtual void deleteSelection(Selection) { } - virtual void copy(View *, Selection, Clipboard & /* to */) { } + virtual void copy(LayerGeometryProvider *, Selection, Clipboard & /* to */) { } /** * Paste from the given clipboard onto the layer at the given @@ -267,7 +269,7 @@ * return false if the user cancelled the paste operation. This * function should return true if a paste actually occurred. */ - virtual bool paste(View *, + virtual bool paste(LayerGeometryProvider *, const Clipboard & /* from */, sv_frame_t /* frameOffset */, bool /* interactive */) { return false; } @@ -289,7 +291,7 @@ * scrolling better if it is known that individual views can be * scrolled safely in this way. */ - virtual bool isLayerScrollable(const View *) const { return true; } + virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return true; } /** * This should return true if the layer completely obscures any @@ -344,14 +346,14 @@ * isReady(int *) call. The view may choose to show a progress * meter if it finds that this returns < 100 at any given moment. */ - virtual int getCompletion(View *) const { return 100; } + virtual int getCompletion(LayerGeometryProvider *) const { return 100; } /** * Return an error string if any errors have occurred while * loading or processing data for the given view. Return the * empty string if no error has occurred. */ - virtual QString getError(View *) const { return ""; } + virtual QString getError(LayerGeometryProvider *) const { return ""; } virtual void setObjectName(const QString &name); @@ -400,13 +402,13 @@ * A layer class that overrides this function must also call this * class's implementation. */ - virtual void setLayerDormant(const View *v, bool dormant); + virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant); /** * Return whether the layer is dormant (i.e. hidden) in the given * view. */ - virtual bool isLayerDormant(const View *v) const; + virtual bool isLayerDormant(const LayerGeometryProvider *v) const; virtual PlayParameters *getPlayParameters(); @@ -457,14 +459,14 @@ * measurement tool. The default implementation works correctly * if the layer hasTimeXAxis(). */ - virtual bool getXScaleValue(const View *v, int x, + virtual bool getXScaleValue(const LayerGeometryProvider *v, int x, double &value, QString &unit) const; /** * Return the value and unit at the given y coordinate in the * given view. */ - virtual bool getYScaleValue(const View *, int /* y */, + virtual bool getYScaleValue(const LayerGeometryProvider *, int /* y */, double &/* value */, QString &/* unit */) const { return false; } @@ -475,7 +477,7 @@ * The default implementation just calls getYScaleValue twice and * returns the difference, with the same unit. */ - virtual bool getYScaleDifference(const View *v, int y0, int y1, + virtual bool getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1, double &diff, QString &unit) const; /** @@ -526,7 +528,7 @@ virtual bool canExistWithoutModel() const { return false; } public slots: - void showLayer(View *, bool show); + void showLayer(LayerGeometryProvider *, bool show); signals: void modelChanged(); @@ -545,9 +547,9 @@ protected: void connectSignals(const Model *); - virtual sv_frame_t alignToReference(View *v, sv_frame_t frame) const; - virtual sv_frame_t alignFromReference(View *v, sv_frame_t frame) const; - bool clipboardHasDifferentAlignment(View *v, const Clipboard &clip) const; + virtual sv_frame_t alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const; + virtual sv_frame_t alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const; + bool clipboardHasDifferentAlignment(LayerGeometryProvider *v, const Clipboard &clip) const; struct MeasureRect { @@ -612,16 +614,16 @@ // Note that pixrects are only correct for a single view. // So we should update them at the start of the paint procedure // (painting is single threaded) and only use them after that. - void updateMeasurePixrects(View *v) const; + void updateMeasurePixrects(LayerGeometryProvider *v) const; - virtual void updateMeasureRectYCoords(View *v, const MeasureRect &r) const; - virtual void setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const; - virtual void setMeasureRectFromPixrect(View *v, MeasureRect &r, QRect pixrect) const; + virtual void updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const; + virtual void setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const; + virtual void setMeasureRectFromPixrect(LayerGeometryProvider *v, MeasureRect &r, QRect pixrect) const; // This assumes updateMeasurementPixrects has been called MeasureRectSet::const_iterator findFocusedMeasureRect(QPoint) const; - void paintMeasurementRect(View *v, QPainter &paint, + void paintMeasurementRect(LayerGeometryProvider *v, QPainter &paint, const MeasureRect &r, bool focus) const; QString m_presentationName; diff -r e8102ff5573b -r dc2af6616c83 layer/LayerGeometryProvider.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/LayerGeometryProvider.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,184 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef LAYER_GEOMETRY_PROVIDER_H +#define LAYER_GEOMETRY_PROVIDER_H + +#include "base/BaseTypes.h" + +#include +#include +#include + +class ViewManager; +class View; +class Layer; + +/** + * Interface for classes that provide geometry information (such as + * size, start frame, and a large number of other properties) about + * the disposition of a layer. The main implementor of this interface + * is the View class, but other implementations may be used in + * different circumstances, e.g. as a proxy to handle hi-dpi + * coordinate mapping. + * + * Note it is expected that some implementations of this may be + * disposable, created on-the-fly for a single use. Code that receives + * a LayerGeometryProvider pointer as an argument to something should + * not, in general, store that pointer as it may be invalidated before + * the next use. Use getId() to instead obtain a persistent identifier + * for a LayerGeometryProvider, for example to establish whether the + * same one is being provided in two separate calls. + */ +class LayerGeometryProvider +{ +protected: + static int getNextId() { + static QMutex idMutex; + static int nextId = 1; + static int maxId = INT_MAX; + QMutexLocker locker(&idMutex); + int id = nextId; + if (nextId == maxId) { + // we don't expect this to happen in the lifetime of a + // process, but it would be undefined behaviour if it did + // since we're using a signed int, so we should really + // guard for it... + nextId = 1; + } else { + nextId++; + } + return id; + } + +public: + LayerGeometryProvider() { } + + /** + * Retrieve the id of this object. + */ + virtual int getId() const = 0; + + /** + * Retrieve the first visible sample frame on the widget. + * This is a calculated value based on the centre-frame, widget + * width and zoom level. The result may be negative. + */ + virtual sv_frame_t getStartFrame() const = 0; + + /** + * Return the centre frame of the visible widget. This is an + * exact value that does not depend on the zoom block size. Other + * frame values (start, end) are calculated from this based on the + * zoom and other factors. + */ + virtual sv_frame_t getCentreFrame() const = 0; + + /** + * Retrieve the last visible sample frame on the widget. + * This is a calculated value based on the centre-frame, widget + * width and zoom level. + */ + virtual sv_frame_t getEndFrame() const = 0; + + /** + * Return the pixel x-coordinate corresponding to a given sample + * frame (which may be negative). + */ + virtual int getXForFrame(sv_frame_t frame) const = 0; + + /** + * Return the closest frame to the given pixel x-coordinate. + */ + virtual sv_frame_t getFrameForX(int x) const = 0; + + virtual sv_frame_t getModelsStartFrame() const = 0; + virtual sv_frame_t getModelsEndFrame() const = 0; + + /** + * Return the closest pixel x-coordinate corresponding to a given + * view x-coordinate. + */ + virtual int getXForViewX(int viewx) const = 0; + + /** + * Return the closest view x-coordinate corresponding to a given + * pixel x-coordinate. + */ + virtual int getViewXForX(int x) const = 0; + + /** + * Return the (maybe fractional) pixel y-coordinate corresponding + * to a given frequency, if the frequency range is as specified. + * This does not imply any policy about layer frequency ranges, + * but it might be useful for layers to match theirs up if + * desired. + * + * Not thread-safe in logarithmic mode. Call only from GUI thread. + */ + virtual double getYForFrequency(double frequency, + double minFreq, double maxFreq, + bool logarithmic) const = 0; + + /** + * Return the closest frequency to the given (maybe fractional) + * pixel y-coordinate, if the frequency range is as specified. + * + * Not thread-safe in logarithmic mode. Call only from GUI thread. + */ + virtual double getFrequencyForY(double y, + double minFreq, double maxFreq, + bool logarithmic) const = 0; + + virtual int getTextLabelHeight(const Layer *layer, QPainter &) const = 0; + + virtual bool getValueExtents(QString unit, double &min, double &max, + bool &log) const = 0; + + /** + * Return the zoom level, i.e. the number of frames per pixel + */ + virtual int getZoomLevel() const = 0; + + /** + * To be called from a layer, to obtain the extent of the surface + * that the layer is currently painting to. This may be the extent + * of the view (if 1x display scaling is in effect) or of a larger + * cached pixmap (if greater display scaling is in effect). + */ + virtual QRect getPaintRect() const = 0; + + virtual QSize getPaintSize() const { return getPaintRect().size(); } + virtual int getPaintWidth() const { return getPaintRect().width(); } + virtual int getPaintHeight() const { return getPaintRect().height(); } + + virtual bool hasLightBackground() const = 0; + virtual QColor getForeground() const = 0; + virtual QColor getBackground() const = 0; + + virtual ViewManager *getViewManager() const = 0; + + virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const = 0; + virtual bool shouldShowFeatureLabels() const = 0; + + virtual void drawMeasurementRect(QPainter &p, const Layer *, + QRect rect, bool focus) const = 0; + + virtual void updatePaintRect(QRect r) = 0; + + virtual View *getView() = 0; + virtual const View *getView() const = 0; +}; + +#endif diff -r e8102ff5573b -r dc2af6616c83 layer/LinearColourScale.cpp --- a/layer/LinearColourScale.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/LinearColourScale.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -20,24 +20,24 @@ #include -#include "view/View.h" +#include "LayerGeometryProvider.h" int -LinearColourScale::getWidth(View *, +LinearColourScale::getWidth(LayerGeometryProvider *, QPainter &paint) { return paint.fontMetrics().width("-000.00") + 15; } void -LinearColourScale::paintVertical(View *v, +LinearColourScale::paintVertical(LayerGeometryProvider *v, const ColourScaleLayer *layer, QPainter &paint, int /* x0 */, double min, double max) { - int h = v->height(); + int h = v->getPaintHeight(); int n = 10; diff -r e8102ff5573b -r dc2af6616c83 layer/LinearColourScale.h --- a/layer/LinearColourScale.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/LinearColourScale.h Fri Jan 13 10:29:50 2017 +0000 @@ -19,16 +19,16 @@ #include class QPainter; -class View; +class LayerGeometryProvider; class ColourScaleLayer; class LinearColourScale { public: - int getWidth(View *v, QPainter &paint); + int getWidth(LayerGeometryProvider *v, QPainter &paint); void paintVertical - (View *v, const ColourScaleLayer *layer, QPainter &paint, int x0, + (LayerGeometryProvider *v, const ColourScaleLayer *layer, QPainter &paint, int x0, double minf, double maxf); }; diff -r e8102ff5573b -r dc2af6616c83 layer/LinearNumericalScale.cpp --- a/layer/LinearNumericalScale.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/LinearNumericalScale.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -20,17 +20,17 @@ #include -#include "view/View.h" +#include "LayerGeometryProvider.h" int -LinearNumericalScale::getWidth(View *, +LinearNumericalScale::getWidth(LayerGeometryProvider *, QPainter &paint) { return paint.fontMetrics().width("-000.00") + 10; } void -LinearNumericalScale::paintVertical(View *v, +LinearNumericalScale::paintVertical(LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, @@ -69,7 +69,7 @@ double dispval = val; if (i == n-1 && - v->height() < paint.fontMetrics().height() * (n*2)) { + v->getPaintHeight() < paint.fontMetrics().height() * (n*2)) { if (layer->getScaleUnits() != "") drawText = false; } dispval = int(rint(val / round) * round); diff -r e8102ff5573b -r dc2af6616c83 layer/LinearNumericalScale.h --- a/layer/LinearNumericalScale.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/LinearNumericalScale.h Fri Jan 13 10:29:50 2017 +0000 @@ -19,17 +19,17 @@ #include class QPainter; -class View; +class LayerGeometryProvider; class VerticalScaleLayer; class LinearNumericalScale { public: - int getWidth(View *v, QPainter &paint); + int getWidth(LayerGeometryProvider *v, QPainter &paint); void paintVertical - (View *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, - double minf, double maxf); + (LayerGeometryProvider *v, const VerticalScaleLayer *layer, + QPainter &paint, int x0, double minf, double maxf); }; #endif diff -r e8102ff5573b -r dc2af6616c83 layer/LogColourScale.cpp --- a/layer/LogColourScale.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/LogColourScale.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -22,24 +22,24 @@ #include -#include "view/View.h" +#include "LayerGeometryProvider.h" int -LogColourScale::getWidth(View *, +LogColourScale::getWidth(LayerGeometryProvider *, QPainter &paint) { return paint.fontMetrics().width("-000.00") + 15; } void -LogColourScale::paintVertical(View *v, +LogColourScale::paintVertical(LayerGeometryProvider *v, const ColourScaleLayer *layer, QPainter &paint, int /* x0 */, double minlog, double maxlog) { - int h = v->height(); + int h = v->getPaintHeight(); int n = 10; diff -r e8102ff5573b -r dc2af6616c83 layer/LogColourScale.h --- a/layer/LogColourScale.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/LogColourScale.h Fri Jan 13 10:29:50 2017 +0000 @@ -19,16 +19,16 @@ #include class QPainter; -class View; +class LayerGeometryProvider; class ColourScaleLayer; class LogColourScale { public: - int getWidth(View *v, QPainter &paint); + int getWidth(LayerGeometryProvider *v, QPainter &paint); void paintVertical - (View *v, const ColourScaleLayer *layer, QPainter &paint, int x0, + (LayerGeometryProvider *v, const ColourScaleLayer *layer, QPainter &paint, int x0, double minf, double maxf); }; diff -r e8102ff5573b -r dc2af6616c83 layer/LogNumericalScale.cpp --- a/layer/LogNumericalScale.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/LogNumericalScale.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -22,19 +22,19 @@ #include -#include "view/View.h" +#include "LayerGeometryProvider.h" //#define DEBUG_TIME_VALUE_LAYER 1 int -LogNumericalScale::getWidth(View *, +LogNumericalScale::getWidth(LayerGeometryProvider *, QPainter &paint) { return paint.fontMetrics().width("-000.00") + 10; } void -LogNumericalScale::paintVertical(View *v, +LogNumericalScale::paintVertical(LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, @@ -79,7 +79,7 @@ bool drawText = true; if (i == n-1 && - v->height() < paint.fontMetrics().height() * (n*2)) { + v->getPaintHeight() < paint.fontMetrics().height() * (n*2)) { if (layer->getScaleUnits() != "") drawText = false; } diff -r e8102ff5573b -r dc2af6616c83 layer/LogNumericalScale.h --- a/layer/LogNumericalScale.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/LogNumericalScale.h Fri Jan 13 10:29:50 2017 +0000 @@ -19,16 +19,16 @@ #include class QPainter; -class View; +class LayerGeometryProvider; class VerticalScaleLayer; class LogNumericalScale { public: - int getWidth(View *v, QPainter &paint); + int getWidth(LayerGeometryProvider *v, QPainter &paint); void paintVertical - (View *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, + (LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, double minlog, double maxlog); }; diff -r e8102ff5573b -r dc2af6616c83 layer/NoteLayer.cpp --- a/layer/NoteLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/NoteLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -21,12 +21,13 @@ #include "base/Pitch.h" #include "base/LogRange.h" #include "base/RangeMapper.h" -#include "ColourDatabase.h" #include "view/View.h" +#include "ColourDatabase.h" #include "PianoScale.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" +#include "PaintAssistant.h" #include "data/model/NoteModel.h" @@ -191,7 +192,7 @@ } bool -NoteLayer::isLayerScrollable(const View *v) const +NoteLayer::isLayerScrollable(const LayerGeometryProvider *v) const { QPoint discard; return !v->shouldIlluminateLocalFeatures(this, discard); @@ -389,7 +390,7 @@ } NoteModel::PointList -NoteLayer::getLocalPoints(View *v, int x) const +NoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const { if (!m_model) return NoteModel::PointList(); @@ -432,7 +433,7 @@ } bool -NoteLayer::getPointToDrag(View *v, int x, int y, NoteModel::Point &p) const +NoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, NoteModel::Point &p) const { if (!m_model) return false; @@ -460,7 +461,7 @@ } QString -NoteLayer::getFeatureDescription(View *v, QPoint &pos) const +NoteLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const { int x = pos.x(); @@ -547,7 +548,7 @@ } bool -NoteLayer::snapToFeatureFrame(View *v, sv_frame_t &frame, +NoteLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { @@ -619,7 +620,7 @@ } void -NoteLayer::getScaleExtents(View *v, double &min, double &max, bool &log) const +NoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const { min = 0.0; max = 0.0; @@ -677,11 +678,11 @@ } int -NoteLayer::getYForValue(View *v, double val) const +NoteLayer::getYForValue(LayerGeometryProvider *v, double val) const { double min = 0.0, max = 0.0; bool logarithmic = false; - int h = v->height(); + int h = v->getPaintHeight(); getScaleExtents(v, min, max, logarithmic); @@ -712,11 +713,11 @@ } double -NoteLayer::getValueForY(View *v, int y) const +NoteLayer::getValueForY(LayerGeometryProvider *v, int y) const { double min = 0.0, max = 0.0; bool logarithmic = false; - int h = v->height(); + int h = v->getPaintHeight(); getScaleExtents(v, min, max, logarithmic); @@ -741,7 +742,7 @@ } void -NoteLayer::paint(View *v, QPainter &paint, QRect rect) const +NoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) return; @@ -809,18 +810,18 @@ paint.setBrush(v->getForeground()); QString vlabel = QString("%1%2").arg(p.value).arg(getScaleUnits()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x - paint.fontMetrics().width(vlabel) - 2, y + paint.fontMetrics().height()/2 - paint.fontMetrics().descent(), - vlabel, View::OutlinedText); + vlabel, PaintAssistant::OutlinedText); QString hlabel = RealTime::frame2RealTime (p.frame, m_model->getSampleRate()).toText(true).c_str(); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - paint.fontMetrics().descent() - 2, - hlabel, View::OutlinedText); + hlabel, PaintAssistant::OutlinedText); } paint.drawRect(x, y - h/2, w, h); @@ -830,7 +831,7 @@ } int -NoteLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const +NoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const { if (!m_model || shouldAutoAlign()) { return 0; @@ -844,7 +845,7 @@ } void -NoteLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const +NoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const { if (!m_model || m_model->getPoints().empty()) return; @@ -853,7 +854,7 @@ bool logarithmic; int w = getVerticalScaleWidth(v, false, paint); - int h = v->height(); + int h = v->getPaintHeight(); getScaleExtents(v, min, max, logarithmic); @@ -882,7 +883,7 @@ } void -NoteLayer::drawStart(View *v, QMouseEvent *e) +NoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl; @@ -906,7 +907,7 @@ } void -NoteLayer::drawDrag(View *v, QMouseEvent *e) +NoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl; @@ -935,7 +936,7 @@ } void -NoteLayer::drawEnd(View *, QMouseEvent *) +NoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *) { // SVDEBUG << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl; if (!m_model || !m_editing) return; @@ -945,7 +946,7 @@ } void -NoteLayer::eraseStart(View *v, QMouseEvent *e) +NoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return; @@ -960,12 +961,12 @@ } void -NoteLayer::eraseDrag(View *, QMouseEvent *) +NoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *) { } void -NoteLayer::eraseEnd(View *v, QMouseEvent *e) +NoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model || !m_editing) return; @@ -985,7 +986,7 @@ } void -NoteLayer::editStart(View *v, QMouseEvent *e) +NoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl; @@ -1008,7 +1009,7 @@ } void -NoteLayer::editDrag(View *v, QMouseEvent *e) +NoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl; @@ -1037,7 +1038,7 @@ } void -NoteLayer::editEnd(View *, QMouseEvent *) +NoteLayer::editEnd(LayerGeometryProvider *, QMouseEvent *) { // SVDEBUG << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl; if (!m_model || !m_editing) return; @@ -1065,7 +1066,7 @@ } bool -NoteLayer::editOpen(View *v, QMouseEvent *e) +NoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return false; @@ -1193,7 +1194,7 @@ } void -NoteLayer::copy(View *v, Selection s, Clipboard &to) +NoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) { if (!m_model) return; @@ -1211,7 +1212,7 @@ } bool -NoteLayer::paste(View *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) +NoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) { if (!m_model) return false; @@ -1222,7 +1223,7 @@ if (clipboardHasDifferentAlignment(v, from)) { QMessageBox::StandardButton button = - QMessageBox::question(v, tr("Re-align pasted items?"), + QMessageBox::question(v->getView(), tr("Re-align pasted items?"), tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); diff -r e8102ff5573b -r dc2af6616c83 layer/NoteLayer.h --- a/layer/NoteLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/NoteLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -35,37 +35,37 @@ public: NoteLayer(); - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; - virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const; - virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const; + virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const; + virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; - virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame, + virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual void drawStart(View *v, QMouseEvent *); - virtual void drawDrag(View *v, QMouseEvent *); - virtual void drawEnd(View *v, QMouseEvent *); + virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void eraseStart(View *v, QMouseEvent *); - virtual void eraseDrag(View *v, QMouseEvent *); - virtual void eraseEnd(View *v, QMouseEvent *); + virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void editStart(View *v, QMouseEvent *); - virtual void editDrag(View *v, QMouseEvent *); - virtual void editEnd(View *v, QMouseEvent *); + virtual void editStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual bool editOpen(View *v, QMouseEvent *); + virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *); virtual void moveSelection(Selection s, sv_frame_t newStartFrame); virtual void resizeSelection(Selection s, Selection newSize); virtual void deleteSelection(Selection s); - virtual void copy(View *v, Selection s, Clipboard &to); - virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset, + virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to); + virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool interactive); virtual const Model *getModel() const { return m_model; } @@ -91,11 +91,11 @@ void setVerticalScale(VerticalScale scale); VerticalScale getVerticalScale() const { return m_verticalScale; } - virtual bool isLayerScrollable(const View *v) const; + virtual bool isLayerScrollable(const LayerGeometryProvider *v) const; virtual bool isLayerEditable() const { return true; } - virtual int getCompletion(View *) const { return m_model->getCompletion(); } + virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); } virtual bool getValueExtents(double &min, double &max, bool &log, QString &unit) const; @@ -132,19 +132,19 @@ void setProperties(const QXmlAttributes &attributes); /// VerticalScaleLayer methods - virtual int getYForValue(View *v, double value) const; - virtual double getValueForY(View *v, int y) const; + virtual int getYForValue(LayerGeometryProvider *v, double value) const; + virtual double getValueForY(LayerGeometryProvider *v, int y) const; virtual QString getScaleUnits() const; protected: - void getScaleExtents(View *, double &min, double &max, bool &log) const; + void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const; bool shouldConvertMIDIToHz() const; virtual int getDefaultColourHint(bool dark, bool &impose); - NoteModel::PointList getLocalPoints(View *v, int) const; + NoteModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const; - bool getPointToDrag(View *v, int x, int y, NoteModel::Point &) const; + bool getPointToDrag(LayerGeometryProvider *v, int x, int y, NoteModel::Point &) const; NoteModel *m_model; bool m_editing; diff -r e8102ff5573b -r dc2af6616c83 layer/PaintAssistant.cpp --- a/layer/PaintAssistant.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/PaintAssistant.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -15,7 +15,10 @@ #include "PaintAssistant.h" +#include "LayerGeometryProvider.h" + #include "base/AudioLevel.h" +#include "base/Strings.h" #include #include @@ -79,7 +82,7 @@ text = QString("%1").arg(meterdbs[i]); if (i == n) text = "0dB"; if (i == 0) { - text = "-Inf"; + text = Strings::minus_infinity; val = 0.0; } break; @@ -89,7 +92,7 @@ text = QString("%1").arg(-(10*n) + i * 10); if (i == n) text = "0dB"; if (i == 0) { - text = "-Inf"; + text = Strings::minus_infinity; val = 0.0; } break; @@ -207,3 +210,55 @@ return vy; } + +void +PaintAssistant::drawVisibleText(const LayerGeometryProvider *v, + QPainter &paint, int x, int y, + QString text, TextStyle style) +{ + if (style == OutlinedText || style == OutlinedItalicText) { + + paint.save(); + + if (style == OutlinedItalicText) { + QFont f(paint.font()); + f.setItalic(true); + paint.setFont(f); + } + + QColor penColour, surroundColour, boxColour; + + penColour = v->getForeground(); + surroundColour = v->getBackground(); + boxColour = surroundColour; + boxColour.setAlpha(127); + + paint.setPen(Qt::NoPen); + paint.setBrush(boxColour); + + QRect r = paint.fontMetrics().boundingRect(text); + r.translate(QPoint(x, y)); +// cerr << "drawVisibleText: r = " << r.x() << "," < #include class QPainter; +class Layer; +class LayerGeometryProvider; class PaintAssistant { @@ -34,6 +36,16 @@ static int getYForValue(Scale scale, double value, double minVal, double maxVal, int minY, int height); + + enum TextStyle { + BoxedText, + OutlinedText, + OutlinedItalicText + }; + + static void drawVisibleText(const LayerGeometryProvider *, + QPainter &p, int x, int y, + QString text, TextStyle style); }; #endif diff -r e8102ff5573b -r dc2af6616c83 layer/PianoScale.cpp --- a/layer/PianoScale.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/PianoScale.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -21,10 +21,10 @@ #include "base/Pitch.h" -#include "view/View.h" +#include "LayerGeometryProvider.h" void -PianoScale::paintPianoVertical(View *v, +PianoScale::paintPianoVertical(LayerGeometryProvider *v, QPainter &paint, QRect r, double minf, diff -r e8102ff5573b -r dc2af6616c83 layer/PianoScale.h --- a/layer/PianoScale.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/PianoScale.h Fri Jan 13 10:29:50 2017 +0000 @@ -19,13 +19,13 @@ #include class QPainter; -class View; +class LayerGeometryProvider; class PianoScale { public: void paintPianoVertical - (View *v, QPainter &paint, QRect rect, double minf, double maxf); + (LayerGeometryProvider *v, QPainter &paint, QRect rect, double minf, double maxf); }; #endif diff -r e8102ff5573b -r dc2af6616c83 layer/RegionLayer.cpp --- a/layer/RegionLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/RegionLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -19,13 +19,14 @@ #include "base/RealTime.h" #include "base/Profiler.h" #include "base/LogRange.h" + #include "ColourDatabase.h" - #include "ColourMapper.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" #include "LinearColourScale.h" #include "LogColourScale.h" +#include "PaintAssistant.h" #include "view/View.h" @@ -244,7 +245,7 @@ } bool -RegionLayer::isLayerScrollable(const View *v) const +RegionLayer::isLayerScrollable(const LayerGeometryProvider *v) const { QPoint discard; return !v->shouldIlluminateLocalFeatures(this, discard); @@ -302,7 +303,7 @@ } RegionModel::PointList -RegionLayer::getLocalPoints(View *v, int x) const +RegionLayer::getLocalPoints(LayerGeometryProvider *v, int x) const { if (!m_model) return RegionModel::PointList(); @@ -345,7 +346,7 @@ } bool -RegionLayer::getPointToDrag(View *v, int x, int y, RegionModel::Point &p) const +RegionLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, RegionModel::Point &p) const { if (!m_model) return false; @@ -383,7 +384,7 @@ } QString -RegionLayer::getFeatureDescription(View *v, QPoint &pos) const +RegionLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const { int x = pos.x(); @@ -453,7 +454,7 @@ } bool -RegionLayer::snapToFeatureFrame(View *v, sv_frame_t &frame, +RegionLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { @@ -536,7 +537,7 @@ } bool -RegionLayer::snapToSimilarFeature(View *v, sv_frame_t &frame, +RegionLayer::snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { @@ -624,7 +625,7 @@ } void -RegionLayer::getScaleExtents(View *v, double &min, double &max, bool &log) const +RegionLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const { min = 0.0; max = 0.0; @@ -676,9 +677,9 @@ } int -RegionLayer::spacingIndexToY(View *v, int i) const +RegionLayer::spacingIndexToY(LayerGeometryProvider *v, int i) const { - int h = v->height(); + int h = v->getPaintHeight(); int n = int(m_spacingMap.size()); // this maps from i (spacing of the value from the spacing // map) and n (number of region types) to y @@ -687,10 +688,10 @@ } double -RegionLayer::yToSpacingIndex(View *v, int y) const +RegionLayer::yToSpacingIndex(LayerGeometryProvider *v, int y) const { // we return an inexact result here (double rather than int) - int h = v->height(); + int h = v->getPaintHeight(); int n = int(m_spacingMap.size()); // from y = h - ((h * i) / n) + (h / (2 * n)) as above (vh taking place of i) double vh = double(2*h*n - h - 2*n*y) / double(2*h); @@ -698,11 +699,11 @@ } int -RegionLayer::getYForValue(View *v, double val) const +RegionLayer::getYForValue(LayerGeometryProvider *v, double val) const { double min = 0.0, max = 0.0; bool logarithmic = false; - int h = v->height(); + int h = v->getPaintHeight(); if (m_verticalScale == EqualSpaced) { @@ -733,17 +734,17 @@ } double -RegionLayer::getValueForY(View *v, int y) const +RegionLayer::getValueForY(LayerGeometryProvider *v, int y) const { return getValueForY(v, y, -1); } double -RegionLayer::getValueForY(View *v, int y, int avoid) const +RegionLayer::getValueForY(LayerGeometryProvider *v, int y, int avoid) const { double min = 0.0, max = 0.0; bool logarithmic = false; - int h = v->height(); + int h = v->getPaintHeight(); if (m_verticalScale == EqualSpaced) { @@ -836,7 +837,7 @@ } QColor -RegionLayer::getColourForValue(View *v, double val) const +RegionLayer::getColourForValue(LayerGeometryProvider *v, double val) const { double min, max; bool log; @@ -866,7 +867,7 @@ } void -RegionLayer::paint(View *v, QPainter &paint, QRect rect) const +RegionLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) return; @@ -942,7 +943,7 @@ if (w < 1) w = 1; if (m_plotStyle == PlotSegmentation) { - paint.setPen(getForegroundQColor(v)); + paint.setPen(getForegroundQColor(v->getView())); paint.setBrush(getColourForValue(v, p.value)); } else { paint.setPen(getBaseQColor()); @@ -958,15 +959,15 @@ RegionModel::Point::Comparator()(illuminatePoint, p) || RegionModel::Point::Comparator()(p, illuminatePoint)) { - paint.setPen(QPen(getForegroundQColor(v), 1)); - paint.drawLine(x, 0, x, v->height()); + paint.setPen(QPen(getForegroundQColor(v->getView()), 1)); + paint.drawLine(x, 0, x, v->getPaintHeight()); paint.setPen(Qt::NoPen); } else { - paint.setPen(QPen(getForegroundQColor(v), 2)); + paint.setPen(QPen(getForegroundQColor(v->getView()), 2)); } - paint.drawRect(x, -1, ex - x, v->height() + 2); + paint.drawRect(x, -1, ex - x, v->getPaintHeight() + 2); } else { @@ -979,18 +980,18 @@ paint.setBrush(v->getForeground()); QString vlabel = QString("%1%2").arg(p.value).arg(getScaleUnits()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x - paint.fontMetrics().width(vlabel) - 2, y + paint.fontMetrics().height()/2 - paint.fontMetrics().descent(), - vlabel, View::OutlinedText); + vlabel, PaintAssistant::OutlinedText); QString hlabel = RealTime::frame2RealTime (p.frame, m_model->getSampleRate()).toText(true).c_str(); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - paint.fontMetrics().descent() - 2, - hlabel, View::OutlinedText); + hlabel, PaintAssistant::OutlinedText); } paint.drawLine(x, y-1, x + w, y-1); @@ -1040,7 +1041,7 @@ labelX = x + 5; labelY = v->getTextLabelHeight(this, paint); if (labelX < nextLabelMinX) { - if (lastLabelY < v->height()/2) { + if (lastLabelY < v->getPaintHeight()/2) { labelY = lastLabelY + fontHeight; } } @@ -1048,7 +1049,7 @@ nextLabelMinX = labelX + paint.fontMetrics().width(label); } - v->drawVisibleText(paint, labelX, labelY, label, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label, PaintAssistant::OutlinedText); } } @@ -1056,7 +1057,7 @@ } int -RegionLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const +RegionLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const { if (!m_model || m_verticalScale == AutoAlignScale || @@ -1078,7 +1079,7 @@ } void -RegionLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const +RegionLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const { if (!m_model || m_model->getPoints().empty()) return; @@ -1121,7 +1122,7 @@ } void -RegionLayer::drawStart(View *v, QMouseEvent *e) +RegionLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return; @@ -1145,7 +1146,7 @@ } void -RegionLayer::drawDrag(View *v, QMouseEvent *e) +RegionLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model || !m_editing) return; @@ -1175,7 +1176,7 @@ } void -RegionLayer::drawEnd(View *, QMouseEvent *) +RegionLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *) { if (!m_model || !m_editing) return; finish(m_editingCommand); @@ -1186,7 +1187,7 @@ } void -RegionLayer::eraseStart(View *v, QMouseEvent *e) +RegionLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return; @@ -1202,12 +1203,12 @@ } void -RegionLayer::eraseDrag(View *, QMouseEvent *) +RegionLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *) { } void -RegionLayer::eraseEnd(View *v, QMouseEvent *e) +RegionLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model || !m_editing) return; @@ -1229,7 +1230,7 @@ } void -RegionLayer::editStart(View *v, QMouseEvent *e) +RegionLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return; @@ -1254,7 +1255,7 @@ } void -RegionLayer::editDrag(View *v, QMouseEvent *e) +RegionLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model || !m_editing) return; @@ -1289,7 +1290,7 @@ } void -RegionLayer::editEnd(View *, QMouseEvent *) +RegionLayer::editEnd(LayerGeometryProvider *, QMouseEvent *) { if (!m_model || !m_editing) return; @@ -1317,7 +1318,7 @@ } bool -RegionLayer::editOpen(View *v, QMouseEvent *e) +RegionLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return false; @@ -1447,7 +1448,7 @@ } void -RegionLayer::copy(View *v, Selection s, Clipboard &to) +RegionLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) { if (!m_model) return; @@ -1465,7 +1466,7 @@ } bool -RegionLayer::paste(View *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) +RegionLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) { if (!m_model) return false; @@ -1476,7 +1477,7 @@ if (clipboardHasDifferentAlignment(v, from)) { QMessageBox::StandardButton button = - QMessageBox::question(v, tr("Re-align pasted items?"), + QMessageBox::question(v->getView(), tr("Re-align pasted items?"), tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); diff -r e8102ff5573b -r dc2af6616c83 layer/RegionLayer.h --- a/layer/RegionLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/RegionLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -39,41 +39,41 @@ public: RegionLayer(); - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; - virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const; - virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const; + virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const; + virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; virtual QString getLabelPreceding(sv_frame_t) const; - virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame, + virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual bool snapToSimilarFeature(View *v, sv_frame_t &frame, + virtual bool snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual void drawStart(View *v, QMouseEvent *); - virtual void drawDrag(View *v, QMouseEvent *); - virtual void drawEnd(View *v, QMouseEvent *); + virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void eraseStart(View *v, QMouseEvent *); - virtual void eraseDrag(View *v, QMouseEvent *); - virtual void eraseEnd(View *v, QMouseEvent *); + virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void editStart(View *v, QMouseEvent *); - virtual void editDrag(View *v, QMouseEvent *); - virtual void editEnd(View *v, QMouseEvent *); + virtual void editStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual bool editOpen(View *v, QMouseEvent *); + virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *); virtual void moveSelection(Selection s, sv_frame_t newStartFrame); virtual void resizeSelection(Selection s, Selection newSize); virtual void deleteSelection(Selection s); - virtual void copy(View *v, Selection s, Clipboard &to); - virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset, + virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to); + virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool interactive); virtual const Model *getModel() const { return m_model; } @@ -110,11 +110,11 @@ void setPlotStyle(PlotStyle style); PlotStyle getPlotStyle() const { return m_plotStyle; } - virtual bool isLayerScrollable(const View *v) const; + virtual bool isLayerScrollable(const LayerGeometryProvider *v) const; virtual bool isLayerEditable() const { return true; } - virtual int getCompletion(View *) const { return m_model->getCompletion(); } + virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); } virtual bool getValueExtents(double &min, double &max, bool &log, QString &unit) const; @@ -127,23 +127,23 @@ void setProperties(const QXmlAttributes &attributes); /// VerticalScaleLayer and ColourScaleLayer methods - int getYForValue(View *v, double value) const; - double getValueForY(View *v, int y) const; + int getYForValue(LayerGeometryProvider *v, double value) const; + double getValueForY(LayerGeometryProvider *v, int y) const; virtual QString getScaleUnits() const; - QColor getColourForValue(View *v, double value) const; + QColor getColourForValue(LayerGeometryProvider *v, double value) const; protected slots: void recalcSpacing(); protected: - double getValueForY(View *v, int y, int avoid) const; - void getScaleExtents(View *, double &min, double &max, bool &log) const; + double getValueForY(LayerGeometryProvider *v, int y, int avoid) const; + void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const; virtual int getDefaultColourHint(bool dark, bool &impose); - RegionModel::PointList getLocalPoints(View *v, int x) const; + RegionModel::PointList getLocalPoints(LayerGeometryProvider *v, int x) const; - bool getPointToDrag(View *v, int x, int y, RegionModel::Point &) const; + bool getPointToDrag(LayerGeometryProvider *v, int x, int y, RegionModel::Point &) const; RegionModel *m_model; bool m_editing; @@ -166,8 +166,8 @@ // region value -> number of regions with this value SpacingMap m_distributionMap; - int spacingIndexToY(View *v, int i) const; - double yToSpacingIndex(View *v, int y) const; + int spacingIndexToY(LayerGeometryProvider *v, int i) const; + double yToSpacingIndex(LayerGeometryProvider *v, int y) const; void finish(RegionModel::EditCommand *command) { Command *c = command->finish(); diff -r e8102ff5573b -r dc2af6616c83 layer/RenderTimer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/RenderTimer.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,102 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef RENDER_TIMER_H +#define RENDER_TIMER_H + +#include + +class RenderTimer +{ +public: + enum Type { + /// A normal rendering operation with normal responsiveness demands + FastRender, + + /// An operation that the user might accept being slower + SlowRender, + + /// An operation that should always complete, i.e. as if there + /// were no RenderTimer in use, but without having to change + /// client code structurally + NoTimeout + }; + + /** + * Create a new RenderTimer and start timing. Make one of these + * before rendering, and then call outOfTime() regularly during + * rendering. If outOfTime() returns true, abandon rendering! and + * schedule the rest for after some user responsiveness has + * happened. + */ + RenderTimer(Type t) : + m_start(std::chrono::steady_clock::now()), + m_haveLimits(true), + m_minFraction(0.1), + m_softLimit(0.1), + m_hardLimit(0.2), + m_softLimitOverridden(false) { + + if (t == NoTimeout) { + m_haveLimits = false; + } else if (t == SlowRender) { + m_softLimit = 0.2; + m_hardLimit = 0.4; + } + } + + + /** + * Return true if we have run out of time and should suspend + * rendering and handle user events instead. Call this regularly + * during rendering work: fractionComplete should be an estimate + * of how much of the work has been done as of this call, as a + * number between 0.0 (none of it) and 1.0 (all of it). + */ + bool outOfTime(double fractionComplete) { + + if (!m_haveLimits || fractionComplete < m_minFraction) { + return false; + } + + auto t = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration(t - m_start).count(); + + if (elapsed > m_hardLimit) { + return true; + } else if (!m_softLimitOverridden && elapsed > m_softLimit) { + if (fractionComplete > 0.6) { + // If we're significantly more than half way by the + // time we reach the soft limit, ignore it (though + // always respect the hard limit, above). Otherwise + // respect the soft limit and report out of time now. + m_softLimitOverridden = true; + } else { + return true; + } + } + + return false; + } + +private: + std::chrono::time_point m_start; + bool m_haveLimits; + double m_minFraction; + double m_softLimit; + double m_hardLimit; + bool m_softLimitOverridden; +}; + +#endif diff -r e8102ff5573b -r dc2af6616c83 layer/ScrollableImageCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableImageCache.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,220 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "ScrollableImageCache.h" + +#include "base/HitCount.h" + +#include +using namespace std; + +//#define DEBUG_SCROLLABLE_IMAGE_CACHE 1 + +void +ScrollableImageCache::scrollTo(const LayerGeometryProvider *v, + sv_frame_t newStartFrame) +{ + static HitCount count("ScrollableImageCache: scrolling"); + + int dx = (v->getXForFrame(m_startFrame) - + v->getXForFrame(newStartFrame)); + +#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE + cerr << "ScrollableImageCache::scrollTo: start frame " << m_startFrame + << " -> " << newStartFrame << ", dx = " << dx << endl; +#endif + + if (m_startFrame == newStartFrame) { + // haven't moved + count.hit(); + return; + } + + m_startFrame = newStartFrame; + + if (!isValid()) { + count.miss(); + return; + } + + int w = m_image.width(); + + if (dx == 0) { + // haven't moved visibly (even though start frame may have changed) + count.hit(); + return; + } + + if (dx <= -w || dx >= w) { + // scrolled entirely off + invalidate(); + count.miss(); + return; + } + + count.partial(); + + // dx is in range, cache is scrollable + + int dxp = dx; + if (dxp < 0) dxp = -dxp; + + int copylen = (w - dxp) * int(sizeof(QRgb)); + for (int y = 0; y < m_image.height(); ++y) { + QRgb *line = (QRgb *)m_image.scanLine(y); + if (dx < 0) { + memmove(line, line + dxp, copylen); + } else { + memmove(line + dxp, line, copylen); + } + } + + // update valid area + + int px = m_validLeft; + int pw = m_validWidth; + + px += dx; + + if (dx < 0) { + // we scrolled left + if (px < 0) { + pw += px; + px = 0; + if (pw < 0) { + pw = 0; + } + } + } else { + // we scrolled right + if (px + pw > w) { + pw = w - px; + if (pw < 0) { + pw = 0; + } + } + } + + m_validLeft = px; + m_validWidth = pw; +} + +void +ScrollableImageCache::adjustToTouchValidArea(int &left, int &width, + bool &isLeftOfValidArea) const +{ +#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE + cerr << "ScrollableImageCache::adjustToTouchValidArea: left " << left + << ", width " << width << endl; + cerr << "ScrollableImageCache: my left " << m_validLeft + << ", width " << m_validWidth << " so right " << (m_validLeft + m_validWidth) << endl; +#endif + if (left < m_validLeft) { + isLeftOfValidArea = true; + if (left + width <= m_validLeft + m_validWidth) { + width = m_validLeft - left; + } +#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE + cerr << "ScrollableImageCache: we're left of valid area, adjusted width to " << width << endl; +#endif + } else { + isLeftOfValidArea = false; + width = left + width - (m_validLeft + m_validWidth); + left = m_validLeft + m_validWidth; + if (width < 0) width = 0; +#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE + cerr << "ScrollableImageCache: we're right of valid area, adjusted left to " << left << ", width to " << width << endl; +#endif + } +} + +void +ScrollableImageCache::drawImage(int left, + int width, + QImage image, + int imageLeft, + int imageWidth) +{ + if (image.height() != m_image.height()) { + cerr << "ScrollableImageCache::drawImage: ERROR: Supplied image height " + << image.height() << " does not match cache height " + << m_image.height() << endl; + throw std::logic_error("Image height must match cache height in ScrollableImageCache::drawImage"); + } + if (left < 0 || width < 0 || left + width > m_image.width()) { + cerr << "ScrollableImageCache::drawImage: ERROR: Target area (left = " + << left << ", width = " << width << ", so right = " << left + width + << ") out of bounds for cache of width " << m_image.width() << endl; + throw std::logic_error("Target area out of bounds in ScrollableImageCache::drawImage"); + } + if (imageLeft < 0 || imageWidth < 0 || + imageLeft + imageWidth > image.width()) { + cerr << "ScrollableImageCache::drawImage: ERROR: Source area (left = " + << imageLeft << ", width = " << imageWidth << ", so right = " + << imageLeft + imageWidth << ") out of bounds for image of " + << "width " << image.width() << endl; + throw std::logic_error("Source area out of bounds in ScrollableImageCache::drawImage"); + } + + QPainter painter(&m_image); + painter.drawImage(QRect(left, 0, width, m_image.height()), + image, + QRect(imageLeft, 0, imageWidth, image.height())); + painter.end(); + + if (!isValid()) { + m_validLeft = left; + m_validWidth = width; + return; + } + + if (left < m_validLeft) { + if (left + width > m_validLeft + m_validWidth) { + // new image completely contains the old valid area -- + // use the new area as is + m_validLeft = left; + m_validWidth = width; + } else if (left + width < m_validLeft) { + // new image completely off left of old valid area -- + // we can't extend the valid area because the bit in + // between is not valid, so must use the new area only + m_validLeft = left; + m_validWidth = width; + } else { + // new image overlaps old valid area on left side -- + // use new left edge, and extend width to existing + // right edge + m_validWidth = (m_validLeft + m_validWidth) - left; + m_validLeft = left; + } + } else { + if (left > m_validLeft + m_validWidth) { + // new image completely off right of old valid area -- + // we can't extend the valid area because the bit in + // between is not valid, so must use the new area only + m_validLeft = left; + m_validWidth = width; + } else if (left + width > m_validLeft + m_validWidth) { + // new image overlaps old valid area on right side -- + // use existing left edge, and extend width to new + // right edge + m_validWidth = (left + width) - m_validLeft; + // (m_validLeft unchanged) + } else { + // new image completely contained within old valid + // area -- leave the old area unchanged + } + } +} + diff -r e8102ff5573b -r dc2af6616c83 layer/ScrollableImageCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableImageCache.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,163 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SCROLLABLE_IMAGE_CACHE_H +#define SCROLLABLE_IMAGE_CACHE_H + +#include "base/BaseTypes.h" + +#include "LayerGeometryProvider.h" + +#include +#include +#include + +/** + * A cached image for a view that scrolls horizontally, such as a + * spectrogram. The cache object holds an image, reports the size of + * the image (likely the same as the underlying view, but it's the + * caller's responsibility to set the size appropriately), can scroll + * the image, and can report and update which contiguous horizontal + * range of the image is valid. + * + * The only way to *update* the valid area in a cache is to draw to it + * using the drawImage call. + */ +class ScrollableImageCache +{ +public: + ScrollableImageCache() : + m_validLeft(0), + m_validWidth(0), + m_startFrame(0), + m_zoomLevel(0) + {} + + void invalidate() { + m_validWidth = 0; + } + + bool isValid() const { + return m_validWidth > 0; + } + + QSize getSize() const { + return m_image.size(); + } + + /** + * Set the size of the cache. If the new size differs from the + * current size, the cache is invalidated. + */ + void resize(QSize newSize) { + if (getSize() != newSize) { + m_image = QImage(newSize, QImage::Format_ARGB32_Premultiplied); + invalidate(); + } + } + + int getValidLeft() const { + return m_validLeft; + } + + int getValidWidth() const { + return m_validWidth; + } + + int getValidRight() const { + return m_validLeft + m_validWidth; + } + + QRect getValidArea() const { + return QRect(m_validLeft, 0, m_validWidth, m_image.height()); + } + + int getZoomLevel() const { + return m_zoomLevel; + } + + /** + * Set the zoom level. If the new zoom level differs from the + * current one, the cache is invalidated. (Determining whether to + * invalidate the cache here is the only thing the zoom level is + * used for.) + */ + void setZoomLevel(int zoom) { + if (m_zoomLevel != zoom) { + m_zoomLevel = zoom; + invalidate(); + } + } + + sv_frame_t getStartFrame() const { + return m_startFrame; + } + + /** + * Set the start frame. If the new start frame differs from the + * current one, the cache is invalidated. To scroll, i.e. to set + * the start frame while retaining cache validity where possible, + * use scrollTo() instead. + */ + void setStartFrame(sv_frame_t frame) { + if (m_startFrame != frame) { + m_startFrame = frame; + invalidate(); + } + } + + const QImage &getImage() const { + return m_image; + } + + /** + * Set the new start frame for the cache, according to the + * geometry of the supplied LayerGeometryProvider, if possible + * also moving along any existing valid data within the cache so + * that it continues to be valid for the new start frame. + */ + void scrollTo(const LayerGeometryProvider *v, sv_frame_t newStartFrame); + + /** + * Take a left coordinate and width describing a region, and + * adjust them so that they are contiguous with the cache valid + * region and so that the union of the adjusted region with the + * cache valid region contains the supplied region. Does not + * modify anything about the cache, only about the arguments. + */ + void adjustToTouchValidArea(int &left, int &width, + bool &isLeftOfValidArea) const; + + /** + * Draw from an image onto the cache. The supplied image must have + * the same height as the cache and the full height is always + * drawn. The left and width parameters determine the target + * region of the cache, the imageLeft and imageWidth parameters + * the source region of the image. + */ + void drawImage(int left, + int width, + QImage image, + int imageLeft, + int imageWidth); + +private: + QImage m_image; + int m_validLeft; + int m_validWidth; + sv_frame_t m_startFrame; + int m_zoomLevel; +}; + +#endif diff -r e8102ff5573b -r dc2af6616c83 layer/ScrollableMagRangeCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableMagRangeCache.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,122 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "ScrollableMagRangeCache.h" + +#include "base/HitCount.h" + +#include +using namespace std; + +//#define DEBUG_SCROLLABLE_MAG_RANGE_CACHE 1 + +void +ScrollableMagRangeCache::scrollTo(const LayerGeometryProvider *v, + sv_frame_t newStartFrame) +{ + static HitCount count("ScrollableMagRangeCache: scrolling"); + + int dx = (v->getXForFrame(m_startFrame) - + v->getXForFrame(newStartFrame)); + +#ifdef DEBUG_SCROLLABLE_MAG_RANGE_CACHE + cerr << "ScrollableMagRangeCache::scrollTo: start frame " << m_startFrame + << " -> " << newStartFrame << ", dx = " << dx << endl; +#endif + + if (m_startFrame == newStartFrame) { + // haven't moved + count.hit(); + return; + } + + m_startFrame = newStartFrame; + + if (dx == 0) { + // haven't moved visibly (even though start frame may have changed) + count.hit(); + return; + } + + int w = int(m_ranges.size()); + + if (dx <= -w || dx >= w) { + // scrolled entirely off + invalidate(); + count.miss(); + return; + } + + count.partial(); + + // dx is in range, cache is scrollable + + if (dx < 0) { + // The new start frame is to the left of the old start + // frame. We need to add some empty ranges at the left (start) + // end and clip the right end. Assemble -dx new values, then + // w+dx old values starting at index 0. + + auto newRanges = vector(-dx); + newRanges.insert(newRanges.end(), + m_ranges.begin(), m_ranges.begin() + (w + dx)); + m_ranges = newRanges; + + } else { + // The new start frame is to the right of the old start + // frame. We want to clip the left (start) end and add some + // empty ranges at the right end. Assemble w-dx old values + // starting at index dx, then dx new values. + + auto newRanges = vector(dx); + newRanges.insert(newRanges.begin(), + m_ranges.begin() + dx, m_ranges.end()); + m_ranges = newRanges; + } + +#ifdef DEBUG_SCROLLABLE_MAG_RANGE_CACHE + cerr << "maxes (" << m_ranges.size() << ") now: "; + for (int i = 0; in_range_for(m_ranges, i); ++i) { + cerr << m_ranges[i].getMax() << " "; + } + cerr << endl; +#endif +} + +MagnitudeRange +ScrollableMagRangeCache::getRange(int x, int count) const +{ + MagnitudeRange r; +#ifdef DEBUG_SCROLLABLE_MAG_RANGE_CACHE + cerr << "ScrollableMagRangeCache::getRange(" << x << ", " << count << ")" << endl; +#endif + for (int i = 0; i < count; ++i) { + r.sample(m_ranges.at(x + i)); + } + return r; +} + +void +ScrollableMagRangeCache::sampleColumn(int column, const MagnitudeRange &r) +{ + if (!in_range_for(m_ranges, column)) { + cerr << "ERROR: ScrollableMagRangeCache::sampleColumn: column " << column + << " is out of range for cache of width " << m_ranges.size() + << " (with start frame " << m_startFrame << ")" << endl; + throw logic_error("column out of range"); + } else { + m_ranges[column].sample(r); + } +} + diff -r e8102ff5573b -r dc2af6616c83 layer/ScrollableMagRangeCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableMagRangeCache.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,139 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SCROLLABLE_MAG_RANGE_CACHE_H +#define SCROLLABLE_MAG_RANGE_CACHE_H + +#include "base/BaseTypes.h" +#include "base/MagnitudeRange.h" + +#include "LayerGeometryProvider.h" + +/** + * A cached set of magnitude range records for a view that scrolls + * horizontally, such as a spectrogram. The cache object holds a + * magnitude range per column of the view, can report width (likely + * the same as the underlying view, but it's the caller's + * responsibility to set the size appropriately), can scroll the set + * of ranges, and can report and update which columns have had a range + * specified. + * + * The only way to *update* the valid area in a cache is to update the + * magnitude range for a column using the sampleColumn call. + */ +class ScrollableMagRangeCache +{ +public: + ScrollableMagRangeCache() : + m_startFrame(0), + m_zoomLevel(0) + {} + + void invalidate() { + m_ranges = std::vector(m_ranges.size()); + } + + int getWidth() const { + return int(m_ranges.size()); + } + + /** + * Set the width of the cache in columns. If the new size differs + * from the current size, the cache is invalidated. + */ + void resize(int newWidth) { + if (getWidth() != newWidth) { + m_ranges = std::vector(newWidth); + } + } + + int getZoomLevel() const { + return m_zoomLevel; + } + + /** + * Set the zoom level. If the new zoom level differs from the + * current one, the cache is invalidated. (Determining whether to + * invalidate the cache here is the only thing the zoom level is + * used for.) + */ + void setZoomLevel(int zoom) { + if (m_zoomLevel != zoom) { + m_zoomLevel = zoom; + invalidate(); + } + } + + sv_frame_t getStartFrame() const { + return m_startFrame; + } + + /** + * Set the start frame. If the new start frame differs from the + * current one, the cache is invalidated. To scroll, i.e. to set + * the start frame while retaining cache validity where possible, + * use scrollTo() instead. + */ + void setStartFrame(sv_frame_t frame) { + if (m_startFrame != frame) { + m_startFrame = frame; + invalidate(); + } + } + + bool isColumnSet(int column) const { + return in_range_for(m_ranges, column) && m_ranges.at(column).isSet(); + } + + bool areColumnsSet(int x, int count) const { + for (int i = 0; i < count; ++i) { + if (!isColumnSet(x + i)) return false; + } + return true; + } + + /** + * Get the magnitude range for a single column. + */ + MagnitudeRange getRange(int column) const { + return m_ranges.at(column); + } + + /** + * Get the magnitude range for a range of columns. + */ + MagnitudeRange getRange(int x, int count) const; + + /** + * Set the new start frame for the cache, according to the + * geometry of the supplied LayerGeometryProvider, if possible + * also moving along any existing valid data within the cache so + * that it continues to be valid for the new start frame. + */ + void scrollTo(const LayerGeometryProvider *v, sv_frame_t newStartFrame); + + /** + * Update a column in the cache, by column index. (Column zero is + * the first column in the cache, it has nothing to do with any + * underlying model that the cache may be used with.) + */ + void sampleColumn(int column, const MagnitudeRange &r); + +private: + std::vector m_ranges; + sv_frame_t m_startFrame; + int m_zoomLevel; +}; + +#endif diff -r e8102ff5573b -r dc2af6616c83 layer/SingleColourLayer.cpp --- a/layer/SingleColourLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/SingleColourLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -137,7 +137,7 @@ } void -SingleColourLayer::setDefaultColourFor(View *v) +SingleColourLayer::setDefaultColourFor(LayerGeometryProvider *v) { #ifdef DEBUG_COLOUR_SELECTION SVDEBUG << "SingleColourLayer::setDefaultColourFor: m_colourExplicitlySet = " << m_colourExplicitlySet << ", m_defaultColourSet " << m_defaultColourSet << endl; @@ -244,19 +244,19 @@ } QColor -SingleColourLayer::getBackgroundQColor(View *v) const +SingleColourLayer::getBackgroundQColor(LayerGeometryProvider *v) const { return v->getBackground(); } QColor -SingleColourLayer::getForegroundQColor(View *v) const +SingleColourLayer::getForegroundQColor(LayerGeometryProvider *v) const { return v->getForeground(); } std::vector -SingleColourLayer::getPartialShades(View *v) const +SingleColourLayer::getPartialShades(LayerGeometryProvider *v) const { std::vector s; QColor base = getBaseQColor(); diff -r e8102ff5573b -r dc2af6616c83 layer/SingleColourLayer.h --- a/layer/SingleColourLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/SingleColourLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -71,16 +71,16 @@ virtual void setProperties(const QXmlAttributes &attributes); - virtual void setDefaultColourFor(View *v); + virtual void setDefaultColourFor(LayerGeometryProvider *v); protected: SingleColourLayer(); virtual ~SingleColourLayer(); virtual QColor getBaseQColor() const; - virtual QColor getBackgroundQColor(View *v) const; - virtual QColor getForegroundQColor(View *v) const; - std::vector getPartialShades(View *v) const; + virtual QColor getBackgroundQColor(LayerGeometryProvider *v) const; + virtual QColor getForegroundQColor(LayerGeometryProvider *v) const; + std::vector getPartialShades(LayerGeometryProvider *v) const; virtual void flagBaseColourChanged() { } virtual int getDefaultColourHint(bool /* darkBackground */, diff -r e8102ff5573b -r dc2af6616c83 layer/SliceLayer.cpp --- a/layer/SliceLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/SliceLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -92,14 +92,14 @@ } QString -SliceLayer::getFeatureDescription(View *v, QPoint &p) const +SliceLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const { int minbin, maxbin, range; return getFeatureDescriptionAux(v, p, true, minbin, maxbin, range); } QString -SliceLayer::getFeatureDescriptionAux(View *v, QPoint &p, +SliceLayer::getFeatureDescriptionAux(LayerGeometryProvider *v, QPoint &p, bool includeBinDescription, int &minbin, int &maxbin, int &range) const { @@ -108,7 +108,7 @@ if (!m_sliceableModel) return ""; int xorigin = m_xorigins[v]; - int w = v->width() - xorigin - 1; + int w = v->getPaintWidth() - xorigin - 1; int mh = m_sliceableModel->getHeight(); minbin = getBinForX(p.x() - xorigin, mh, w); @@ -226,7 +226,7 @@ } double -SliceLayer::getYForValue(double value, const View *v, double &norm) const +SliceLayer::getYForValue(double value, const LayerGeometryProvider *v, double &norm) const { norm = 0.0; @@ -276,7 +276,7 @@ } double -SliceLayer::getValueForY(double y, const View *v) const +SliceLayer::getValueForY(double y, const LayerGeometryProvider *v) const { double value = 0.0; @@ -313,7 +313,7 @@ } void -SliceLayer::paint(View *v, QPainter &paint, QRect rect) const +SliceLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { if (!m_sliceableModel || !m_sliceableModel->isOK() || !m_sliceableModel->isReady()) return; @@ -334,11 +334,11 @@ paint.setPen(getBaseQColor()); int xorigin = getVerticalScaleWidth(v, true, paint) + 1; - int w = v->width() - xorigin - 1; + int w = v->getPaintWidth() - xorigin - 1; m_xorigins[v] = xorigin; // for use in getFeatureDescription - int yorigin = v->height() - 20 - paint.fontMetrics().height() - 7; + int yorigin = v->getPaintHeight() - 20 - paint.fontMetrics().height() - 7; int h = yorigin - paint.fontMetrics().height() - 8; m_yorigins[v] = yorigin; // for getYForValue etc @@ -485,23 +485,23 @@ v->drawVisibleText (paint, xorigin + 5, paint.fontMetrics().ascent() + 5, - startText, View::OutlinedText); + startText, PaintAssistant::OutlinedText); v->drawVisibleText (paint, xorigin + 5, paint.fontMetrics().ascent() + paint.fontMetrics().height() + 10, - endText, View::OutlinedText); + endText, PaintAssistant::OutlinedText); v->drawVisibleText (paint, xorigin + 5, paint.fontMetrics().ascent() + 2*paint.fontMetrics().height() + 15, - durationText, View::OutlinedText); + durationText, PaintAssistant::OutlinedText); } */ } int -SliceLayer::getVerticalScaleWidth(View *, bool, QPainter &paint) const +SliceLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const { if (m_energyScale == LinearScale || m_energyScale == AbsoluteScale) { return std::max(paint.fontMetrics().width("0.0") + 13, @@ -513,7 +513,7 @@ } void -SliceLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const +SliceLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const { double thresh = m_threshold; if (m_energyScale != LinearScale && m_energyScale != AbsoluteScale) { @@ -523,7 +523,7 @@ // int h = (rect.height() * 3) / 4; // int y = (rect.height() / 2) - (h / 2); - int yorigin = v->height() - 20 - paint.fontMetrics().height() - 6; + int yorigin = v->getPaintHeight() - 20 - paint.fontMetrics().height() - 6; int h = yorigin - paint.fontMetrics().height() - 8; if (h < 0) return; diff -r e8102ff5573b -r dc2af6616c83 layer/SliceLayer.h --- a/layer/SliceLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/SliceLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -38,12 +38,12 @@ void setSliceableModel(const Model *model); - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; - virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const; - virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const; + virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const; + virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const; virtual ColourSignificance getLayerColourSignificance() const { return ColourAndBackgroundSignificant; @@ -67,7 +67,7 @@ virtual bool hasTimeXAxis() const { return false; } - virtual bool isLayerScrollable(const View *) const { return false; } + virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return false; } enum EnergyScale { LinearScale, MeterScale, dBScale, AbsoluteScale }; @@ -112,10 +112,10 @@ virtual double getXForBin(int bin, int totalBins, double w) const; virtual int getBinForX(double x, int totalBins, double w) const; - virtual double getYForValue(double value, const View *v, double &norm) const; - virtual double getValueForY(double y, const View *v) const; + virtual double getYForValue(double value, const LayerGeometryProvider *v, double &norm) const; + virtual double getValueForY(double y, const LayerGeometryProvider *v) const; - virtual QString getFeatureDescriptionAux(View *v, QPoint &, + virtual QString getFeatureDescriptionAux(LayerGeometryProvider *v, QPoint &, bool includeBinDescription, int &minbin, int &maxbin, int &range) const; @@ -141,9 +141,9 @@ float m_initialThreshold; float m_gain; mutable std::vector m_scalePoints; - mutable std::map m_xorigins; - mutable std::map m_yorigins; - mutable std::map m_heights; + mutable std::map m_xorigins; + mutable std::map m_yorigins; + mutable std::map m_heights; mutable sv_frame_t m_currentf0; mutable sv_frame_t m_currentf1; mutable std::vector m_values; diff -r e8102ff5573b -r dc2af6616c83 layer/SpectrogramLayer.cpp --- a/layer/SpectrogramLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/SpectrogramLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -23,34 +23,37 @@ #include "base/Preferences.h" #include "base/RangeMapper.h" #include "base/LogRange.h" +#include "base/ColumnOp.h" +#include "base/Strings.h" +#include "base/StorageAdviser.h" +#include "base/Exceptions.h" #include "widgets/CommandHistory.h" +#include "data/model/Dense3DModelPeakCache.h" + #include "ColourMapper.h" -#include "ImageRegionFinder.h" -#include "data/model/Dense3DModelPeakCache.h" #include "PianoScale.h" +#include "PaintAssistant.h" +#include "Colour3DPlotRenderer.h" #include #include #include #include -#include #include #include #include #include +#include #include #include #include -#ifndef __GNUC__ -#include -#endif - +//#define DEBUG_SPECTROGRAM 1 //#define DEBUG_SPECTROGRAM_REPAINT 1 -using std::vector; +using namespace std; SpectrogramLayer::SpectrogramLayer(Configuration config) : m_model(0), @@ -58,33 +61,34 @@ m_windowSize(1024), m_windowType(HanningWindow), m_windowHopLevel(2), - m_zeroPadLevel(0), - m_fftSize(1024), m_gain(1.0), m_initialGain(1.0), - m_threshold(0.0), - m_initialThreshold(0.0), + m_threshold(1.0e-8f), + m_initialThreshold(1.0e-8f), m_colourRotation(0), m_initialRotation(0), m_minFrequency(10), m_maxFrequency(8000), m_initialMaxFrequency(8000), - m_colourScale(dBColourScale), + m_colourScale(ColourScaleType::Log), + m_colourScaleMultiple(1.0), m_colourMap(0), - m_frequencyScale(LinearFrequencyScale), - m_binDisplay(AllBins), - m_normalizeColumns(false), + m_binScale(BinScale::Linear), + m_binDisplay(BinDisplay::AllBins), + m_normalization(ColumnNormalization::None), m_normalizeVisibleArea(false), - m_normalizeHybrid(false), m_lastEmittedZoomStep(-1), m_synchronous(false), m_haveDetailedScale(false), - m_lastPaintBlockWidth(0), - m_updateTimer(0), - m_candidateFillStartFrame(0), m_exiting(false), - m_sliceableModel(0) + m_fftModel(0), + m_wholeCache(0), + m_peakCache(0), + m_peakCacheDivisor(8) { + QString colourConfigName = "spectrogram-colour"; + int colourConfigDefault = int(ColourMapper::Green); + if (config == FullRangeDb) { m_initialMaxFrequency = 0; setMaxFrequency(0); @@ -94,9 +98,11 @@ m_initialMaxFrequency = 1500; setMaxFrequency(1500); setMinFrequency(40); - setColourScale(LinearColourScale); + setColourScale(ColourScaleType::Linear); setColourMap(ColourMapper::Sunset); - setFrequencyScale(LogFrequencyScale); + setBinScale(BinScale::Log); + colourConfigName = "spectrogram-melodic-colour"; + colourConfigDefault = int(ColourMapper::Sunset); // setGain(20); } else if (config == MelodicPeaks) { setWindowSize(4096); @@ -104,26 +110,85 @@ m_initialMaxFrequency = 2000; setMaxFrequency(2000); setMinFrequency(40); - setFrequencyScale(LogFrequencyScale); - setColourScale(LinearColourScale); - setBinDisplay(PeakFrequencies); - setNormalizeColumns(true); + setBinScale(BinScale::Log); + setColourScale(ColourScaleType::Linear); + setBinDisplay(BinDisplay::PeakFrequencies); + setNormalization(ColumnNormalization::Max1); + colourConfigName = "spectrogram-melodic-colour"; + colourConfigDefault = int(ColourMapper::Sunset); } + QSettings settings; + settings.beginGroup("Preferences"); + setColourMap(settings.value(colourConfigName, colourConfigDefault).toInt()); + settings.endGroup(); + Preferences *prefs = Preferences::getInstance(); connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); setWindowType(prefs->getWindowType()); - - initialisePalette(); } SpectrogramLayer::~SpectrogramLayer() { - delete m_updateTimer; - m_updateTimer = 0; - - invalidateFFTModels(); + invalidateRenderers(); + + delete m_fftModel; + delete m_peakCache; + delete m_wholeCache; +} + +pair +SpectrogramLayer::convertToColourScale(int value) +{ + switch (value) { + case 0: return { ColourScaleType::Linear, 1.0 }; + case 1: return { ColourScaleType::Meter, 1.0 }; + case 2: return { ColourScaleType::Log, 2.0 }; // dB^2 (i.e. log of power) + case 3: return { ColourScaleType::Log, 1.0 }; // dB (of magnitude) + case 4: return { ColourScaleType::Phase, 1.0 }; + default: return { ColourScaleType::Linear, 1.0 }; + } +} + +int +SpectrogramLayer::convertFromColourScale(ColourScaleType scale, double multiple) +{ + switch (scale) { + case ColourScaleType::Linear: return 0; + case ColourScaleType::Meter: return 1; + case ColourScaleType::Log: return (multiple > 1.5 ? 2 : 3); + case ColourScaleType::Phase: return 4; + case ColourScaleType::PlusMinusOne: + case ColourScaleType::Absolute: + default: return 0; + } +} + +std::pair +SpectrogramLayer::convertToColumnNorm(int value) +{ + switch (value) { + default: + case 0: return { ColumnNormalization::None, false }; + case 1: return { ColumnNormalization::Max1, false }; + case 2: return { ColumnNormalization::None, true }; // visible area + case 3: return { ColumnNormalization::Hybrid, false }; + } +} + +int +SpectrogramLayer::convertFromColumnNorm(ColumnNormalization norm, bool visible) +{ + if (visible) return 2; + switch (norm) { + case ColumnNormalization::None: return 0; + case ColumnNormalization::Max1: return 1; + case ColumnNormalization::Hybrid: return 3; + + case ColumnNormalization::Sum1: + default: return 0; + } } void @@ -134,7 +199,8 @@ if (model == m_model) return; m_model = model; - invalidateFFTModels(); + + recreateFFTModel(); if (!m_model || !m_model->isOK()) return; @@ -155,8 +221,7 @@ list.push_back("Colour Scale"); list.push_back("Window Size"); list.push_back("Window Increment"); - list.push_back("Normalize Columns"); - list.push_back("Normalize Visible Area"); + list.push_back("Normalization"); list.push_back("Bin Display"); list.push_back("Threshold"); list.push_back("Gain"); @@ -164,7 +229,6 @@ // list.push_back("Min Frequency"); // list.push_back("Max Frequency"); list.push_back("Frequency Scale"); -//// list.push_back("Zero Padding"); return list; } @@ -175,8 +239,7 @@ if (name == "Colour Scale") return tr("Colour Scale"); if (name == "Window Size") return tr("Window Size"); if (name == "Window Increment") return tr("Window Overlap"); - if (name == "Normalize Columns") return tr("Normalize Columns"); - if (name == "Normalize Visible Area") return tr("Normalize Visible Area"); + if (name == "Normalization") return tr("Normalization"); if (name == "Bin Display") return tr("Bin Display"); if (name == "Threshold") return tr("Threshold"); if (name == "Gain") return tr("Gain"); @@ -184,15 +247,12 @@ if (name == "Min Frequency") return tr("Min Frequency"); if (name == "Max Frequency") return tr("Max Frequency"); if (name == "Frequency Scale") return tr("Frequency Scale"); - if (name == "Zero Padding") return tr("Smoothing"); return ""; } QString -SpectrogramLayer::getPropertyIconName(const PropertyName &name) const +SpectrogramLayer::getPropertyIconName(const PropertyName &) const { - if (name == "Normalize Columns") return "normalise-columns"; - if (name == "Normalize Visible Area") return "normalise"; return ""; } @@ -201,10 +261,8 @@ { if (name == "Gain") return RangeProperty; if (name == "Colour Rotation") return RangeProperty; - if (name == "Normalize Columns") return ToggleProperty; - if (name == "Normalize Visible Area") return ToggleProperty; if (name == "Threshold") return RangeProperty; - if (name == "Zero Padding") return ToggleProperty; + if (name == "Colour") return ColourMapProperty; return ValueProperty; } @@ -214,13 +272,11 @@ if (name == "Bin Display" || name == "Frequency Scale") return tr("Bins"); if (name == "Window Size" || - name == "Window Increment" || - name == "Zero Padding") return tr("Window"); + name == "Window Increment") return tr("Window"); if (name == "Colour" || name == "Threshold" || name == "Colour Rotation") return tr("Colour"); - if (name == "Normalize Columns" || - name == "Normalize Visible Area" || + if (name == "Normalization" || name == "Gain" || name == "Colour Scale") return tr("Scale"); return QString(); @@ -252,8 +308,8 @@ } else if (name == "Threshold") { - *min = -50; - *max = 0; + *min = -81; + *max = -1; *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold))); if (*deflt < *min) *deflt = *min; @@ -273,11 +329,12 @@ } else if (name == "Colour Scale") { + // linear, meter, db^2, db, phase *min = 0; *max = 4; - *deflt = int(dBColourScale); - - val = (int)m_colourScale; + *deflt = 2; + + val = convertFromColourScale(m_colourScale, m_colourScaleMultiple); } else if (name == "Colour") { @@ -305,14 +362,6 @@ val = m_windowHopLevel; - } else if (name == "Zero Padding") { - - *min = 0; - *max = 1; - *deflt = 0; - - val = m_zeroPadLevel > 0 ? 1 : 0; - } else if (name == "Min Frequency") { *min = 0; @@ -355,25 +404,23 @@ *min = 0; *max = 1; - *deflt = int(LinearFrequencyScale); - val = (int)m_frequencyScale; + *deflt = int(BinScale::Linear); + val = (int)m_binScale; } else if (name == "Bin Display") { *min = 0; *max = 2; - *deflt = int(AllBins); + *deflt = int(BinDisplay::AllBins); val = (int)m_binDisplay; - } else if (name == "Normalize Columns") { + } else if (name == "Normalization") { + *min = 0; + *max = 3; *deflt = 0; - val = (m_normalizeColumns ? 1 : 0); - - } else if (name == "Normalize Visible Area") { - - *deflt = 0; - val = (m_normalizeVisibleArea ? 1 : 0); + + val = convertFromColumnNorm(m_normalization, m_normalizeVisibleArea); } else { val = Layer::getPropertyRangeAndValue(name, min, max, deflt); @@ -399,6 +446,16 @@ case 4: return tr("Phase"); } } + if (name == "Normalization") { + switch(value) { + default: + case 0: return tr("None"); + case 1: return tr("Col"); + case 2: return tr("View"); + case 3: return tr("Hybrid"); + } +// return ""; // icon only + } if (name == "Window Size") { return QString("%1").arg(32 << value); } @@ -413,10 +470,6 @@ case 5: return tr("93.75 %"); } } - if (name == "Zero Padding") { - if (value == 0) return tr("None"); - return QString("%1x").arg(value + 1); - } if (name == "Min Frequency") { switch (value) { default: @@ -465,6 +518,22 @@ return tr(""); } +QString +SpectrogramLayer::getPropertyValueIconName(const PropertyName &name, + int value) const +{ + if (name == "Normalization") { + switch(value) { + default: + case 0: return "normalise-none"; + case 1: return "normalise-columns"; + case 2: return "normalise"; + case 3: return "normalise-hybrid"; + } + } + return ""; +} + RangeMapper * SpectrogramLayer::getNewPropertyRangeMapper(const PropertyName &name) const { @@ -472,7 +541,8 @@ return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); } if (name == "Threshold") { - return new LinearRangeMapper(-50, 0, -50, 0, tr("dB")); + return new LinearRangeMapper(-81, -1, -81, -1, tr("dB"), false, + { { -81, Strings::minus_infinity } }); } return 0; } @@ -483,7 +553,7 @@ if (name == "Gain") { setGain(float(pow(10, float(value)/20.0))); } else if (name == "Threshold") { - if (value == -50) setThreshold(0.0); + if (value == -81) setThreshold(0.0); else setThreshold(float(AudioLevel::dB_to_multiplier(value))); } else if (name == "Colour Rotation") { setColourRotation(value); @@ -493,8 +563,6 @@ setWindowSize(32 << value); } else if (name == "Window Increment") { setWindowHopLevel(value); - } else if (name == "Zero Padding") { - setZeroPadLevel(value > 0.1 ? 3 : 0); } else if (name == "Min Frequency") { switch (value) { default: @@ -534,108 +602,50 @@ m_lastEmittedZoomStep = vs; } } else if (name == "Colour Scale") { + setColourScaleMultiple(1.0); switch (value) { default: - case 0: setColourScale(LinearColourScale); break; - case 1: setColourScale(MeterColourScale); break; - case 2: setColourScale(dBSquaredColourScale); break; - case 3: setColourScale(dBColourScale); break; - case 4: setColourScale(PhaseColourScale); break; + case 0: setColourScale(ColourScaleType::Linear); break; + case 1: setColourScale(ColourScaleType::Meter); break; + case 2: + setColourScale(ColourScaleType::Log); + setColourScaleMultiple(2.0); + break; + case 3: setColourScale(ColourScaleType::Log); break; + case 4: setColourScale(ColourScaleType::Phase); break; } } else if (name == "Frequency Scale") { switch (value) { default: - case 0: setFrequencyScale(LinearFrequencyScale); break; - case 1: setFrequencyScale(LogFrequencyScale); break; + case 0: setBinScale(BinScale::Linear); break; + case 1: setBinScale(BinScale::Log); break; } } else if (name == "Bin Display") { switch (value) { default: - case 0: setBinDisplay(AllBins); break; - case 1: setBinDisplay(PeakBins); break; - case 2: setBinDisplay(PeakFrequencies); break; + case 0: setBinDisplay(BinDisplay::AllBins); break; + case 1: setBinDisplay(BinDisplay::PeakBins); break; + case 2: setBinDisplay(BinDisplay::PeakFrequencies); break; } - } else if (name == "Normalize Columns") { - setNormalizeColumns(value ? true : false); - } else if (name == "Normalize Visible Area") { - setNormalizeVisibleArea(value ? true : false); + } else if (name == "Normalization") { + auto n = convertToColumnNorm(value); + setNormalization(n.first); + setNormalizeVisibleArea(n.second); } } void -SpectrogramLayer::invalidateImageCaches() +SpectrogramLayer::invalidateRenderers() { - for (ViewImageCache::iterator i = m_imageCaches.begin(); - i != m_imageCaches.end(); ++i) { - i->second.validArea = QRect(); +#ifdef DEBUG_SPECTROGRAM + cerr << "SpectrogramLayer::invalidateRenderers called" << endl; +#endif + + for (ViewRendererMap::iterator i = m_renderers.begin(); + i != m_renderers.end(); ++i) { + delete i->second; } -} - -void -SpectrogramLayer::invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame) -{ - for (ViewImageCache::iterator i = m_imageCaches.begin(); - i != m_imageCaches.end(); ++i) { - - //!!! when are views removed from the map? on setLayerDormant? - const View *v = i->first; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::invalidateImageCaches(" - << startFrame << ", " << endFrame << "): view range is " - << v->getStartFrame() << ", " << v->getEndFrame() - << endl; - - cerr << "Valid area was: " << i->second.validArea.x() << ", " - << i->second.validArea.y() << " " - << i->second.validArea.width() << "x" - << i->second.validArea.height() << endl; -#endif - - if (int(startFrame) > v->getStartFrame()) { - if (startFrame >= v->getEndFrame()) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Modified start frame is off right of view" << endl; -#endif - return; - } - int x = v->getXForFrame(startFrame); -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "clipping from 0 to " << x-1 << endl; -#endif - if (x > 1) { - i->second.validArea &= - QRect(0, 0, x-1, v->height()); - } else { - i->second.validArea = QRect(); - } - } else { - if (int(endFrame) < v->getStartFrame()) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Modified end frame is off left of view" << endl; -#endif - return; - } - int x = v->getXForFrame(endFrame); -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "clipping from " << x+1 << " to " << v->width() - << endl; -#endif - if (x < v->width()) { - i->second.validArea &= - QRect(x+1, 0, v->width()-(x+1), v->height()); - } else { - i->second.validArea = QRect(); - } - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Valid area is now: " << i->second.validArea.x() << ", " - << i->second.validArea.y() << " " - << i->second.validArea.width() << "x" - << i->second.validArea.height() << endl; -#endif - } + m_renderers.clear(); } void @@ -648,12 +658,13 @@ return; } if (name == "Spectrogram Y Smoothing") { - invalidateImageCaches(); + setWindowSize(m_windowSize); + invalidateRenderers(); invalidateMagnitudes(); emit layerParametersChanged(); } if (name == "Spectrogram X Smoothing") { - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); emit layerParametersChanged(); } @@ -667,9 +678,9 @@ { if (m_channel == ch) return; - invalidateImageCaches(); + invalidateRenderers(); m_channel = ch; - invalidateFFTModels(); + recreateFFTModel(); emit layerParametersChanged(); } @@ -680,17 +691,40 @@ return m_channel; } +int +SpectrogramLayer::getFFTOversampling() const +{ + if (m_binDisplay != BinDisplay::AllBins) { + return 1; + } + + Preferences::SpectrogramSmoothing smoothing = + Preferences::getInstance()->getSpectrogramSmoothing(); + + if (smoothing == Preferences::NoSpectrogramSmoothing || + smoothing == Preferences::SpectrogramInterpolated) { + return 1; + } + + return 4; +} + +int +SpectrogramLayer::getFFTSize() const +{ + return m_windowSize * getFFTOversampling(); +} + void SpectrogramLayer::setWindowSize(int ws) { if (m_windowSize == ws) return; - invalidateImageCaches(); + invalidateRenderers(); m_windowSize = ws; - m_fftSize = ws * (m_zeroPadLevel + 1); - invalidateFFTModels(); + recreateFFTModel(); emit layerParametersChanged(); } @@ -706,15 +740,13 @@ { if (m_windowHopLevel == v) return; - invalidateImageCaches(); + invalidateRenderers(); m_windowHopLevel = v; - invalidateFFTModels(); + recreateFFTModel(); emit layerParametersChanged(); - -// fillCache(); } int @@ -724,36 +756,15 @@ } void -SpectrogramLayer::setZeroPadLevel(int v) -{ - if (m_zeroPadLevel == v) return; - - invalidateImageCaches(); - - m_zeroPadLevel = v; - m_fftSize = m_windowSize * (v + 1); - - invalidateFFTModels(); - - emit layerParametersChanged(); -} - -int -SpectrogramLayer::getZeroPadLevel() const -{ - return m_zeroPadLevel; -} - -void SpectrogramLayer::setWindowType(WindowType w) { if (m_windowType == w) return; - invalidateImageCaches(); + invalidateRenderers(); m_windowType = w; - invalidateFFTModels(); + recreateFFTModel(); emit layerParametersChanged(); } @@ -772,7 +783,7 @@ if (m_gain == gain) return; - invalidateImageCaches(); + invalidateRenderers(); m_gain = gain; @@ -790,7 +801,7 @@ { if (m_threshold == threshold) return; - invalidateImageCaches(); + invalidateRenderers(); m_threshold = threshold; @@ -810,7 +821,7 @@ // SVDEBUG << "SpectrogramLayer::setMinFrequency: " << mf << endl; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_minFrequency = mf; @@ -831,7 +842,7 @@ // SVDEBUG << "SpectrogramLayer::setMaxFrequency: " << mf << endl; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_maxFrequency = mf; @@ -848,47 +859,67 @@ void SpectrogramLayer::setColourRotation(int r) { - invalidateImageCaches(); - if (r < 0) r = 0; if (r > 256) r = 256; int distance = r - m_colourRotation; if (distance != 0) { - rotatePalette(-distance); m_colourRotation = r; } + + // Initially the idea with colour rotation was that we would just + // rotate the palette of an already-generated cache. That's not + // really practical now that cacheing is handled in a separate + // class in which the main cache no longer has a palette. + invalidateRenderers(); emit layerParametersChanged(); } void -SpectrogramLayer::setColourScale(ColourScale colourScale) +SpectrogramLayer::setColourScale(ColourScaleType colourScale) { if (m_colourScale == colourScale) return; - invalidateImageCaches(); + invalidateRenderers(); m_colourScale = colourScale; emit layerParametersChanged(); } -SpectrogramLayer::ColourScale +ColourScaleType SpectrogramLayer::getColourScale() const { return m_colourScale; } void +SpectrogramLayer::setColourScaleMultiple(double multiple) +{ + if (m_colourScaleMultiple == multiple) return; + + invalidateRenderers(); + + m_colourScaleMultiple = multiple; + + emit layerParametersChanged(); +} + +double +SpectrogramLayer::getColourScaleMultiple() const +{ + return m_colourScaleMultiple; +} + +void SpectrogramLayer::setColourMap(int map) { if (m_colourMap == map) return; - invalidateImageCaches(); + invalidateRenderers(); m_colourMap = map; - initialisePalette(); emit layerParametersChanged(); } @@ -900,20 +931,20 @@ } void -SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale) +SpectrogramLayer::setBinScale(BinScale binScale) { - if (m_frequencyScale == frequencyScale) return; - - invalidateImageCaches(); - m_frequencyScale = frequencyScale; + if (m_binScale == binScale) return; + + invalidateRenderers(); + m_binScale = binScale; emit layerParametersChanged(); } -SpectrogramLayer::FrequencyScale -SpectrogramLayer::getFrequencyScale() const +BinScale +SpectrogramLayer::getBinScale() const { - return m_frequencyScale; + return m_binScale; } void @@ -921,66 +952,45 @@ { if (m_binDisplay == binDisplay) return; - invalidateImageCaches(); + invalidateRenderers(); m_binDisplay = binDisplay; emit layerParametersChanged(); } -SpectrogramLayer::BinDisplay +BinDisplay SpectrogramLayer::getBinDisplay() const { return m_binDisplay; } void -SpectrogramLayer::setNormalizeColumns(bool n) +SpectrogramLayer::setNormalization(ColumnNormalization n) { - if (m_normalizeColumns == n) return; - - invalidateImageCaches(); + if (m_normalization == n) return; + + invalidateRenderers(); invalidateMagnitudes(); - m_normalizeColumns = n; + m_normalization = n; emit layerParametersChanged(); } -bool -SpectrogramLayer::getNormalizeColumns() const +ColumnNormalization +SpectrogramLayer::getNormalization() const { - return m_normalizeColumns; -} - -void -SpectrogramLayer::setNormalizeHybrid(bool n) -{ - if (m_normalizeHybrid == n) return; - - invalidateImageCaches(); - invalidateMagnitudes(); - m_normalizeHybrid = n; - - emit layerParametersChanged(); -} - -bool -SpectrogramLayer::getNormalizeHybrid() const -{ - return m_normalizeHybrid; + return m_normalization; } void SpectrogramLayer::setNormalizeVisibleArea(bool n) { - SVDEBUG << "SpectrogramLayer::setNormalizeVisibleArea(" << n - << ") (from " << m_normalizeVisibleArea << ")" << endl; - if (m_normalizeVisibleArea == n) return; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_normalizeVisibleArea = n; - + emit layerParametersChanged(); } @@ -991,12 +1001,12 @@ } void -SpectrogramLayer::setLayerDormant(const View *v, bool dormant) +SpectrogramLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant) { if (dormant) { #ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::setLayerDormant(" << dormant << ")" + cerr << "SpectrogramLayer::setLayerDormant(" << dormant << ")" << endl; #endif @@ -1006,30 +1016,7 @@ Layer::setLayerDormant(v, true); - invalidateImageCaches(); - m_imageCaches.erase(v); - - if (m_fftModels.find(v) != m_fftModels.end()) { - - if (m_sliceableModel == m_fftModels[v].first) { - bool replaced = false; - for (ViewFFTMap::iterator i = m_fftModels.begin(); - i != m_fftModels.end(); ++i) { - if (i->second.first != m_sliceableModel) { - emit sliceableModelReplaced(m_sliceableModel, i->second.first); - replaced = true; - break; - } - } - if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0); - } - - delete m_fftModels[v].first; - m_fftModels.erase(v); - - delete m_peakCaches[v]; - m_peakCaches.erase(v); - } + invalidateRenderers(); } else { @@ -1041,238 +1028,52 @@ SpectrogramLayer::cacheInvalid() { #ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::cacheInvalid()" << endl; + cerr << "SpectrogramLayer::cacheInvalid()" << endl; #endif - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); } void -SpectrogramLayer::cacheInvalid(sv_frame_t from, sv_frame_t to) +SpectrogramLayer::cacheInvalid( +#ifdef DEBUG_SPECTROGRAM_REPAINT + sv_frame_t from, sv_frame_t to +#else + sv_frame_t , sv_frame_t +#endif + ) { #ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl; + cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl; #endif - invalidateImageCaches(from, to); + // We used to call invalidateMagnitudes(from, to) to invalidate + // only those caches whose views contained some of the (from, to) + // range. That's the right thing to do; it has been lost in + // pulling out the image cache code, but it might not matter very + // much, since the underlying models for spectrogram layers don't + // change very often. Let's see. + invalidateRenderers(); invalidateMagnitudes(); } -void -SpectrogramLayer::fillTimerTimedOut() -{ - if (!m_model) return; - - bool allDone = true; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::fillTimerTimedOut: have " << m_fftModels.size() << " FFT models associated with views" << endl; -#endif - - for (ViewFFTMap::iterator i = m_fftModels.begin(); - i != m_fftModels.end(); ++i) { - - const FFTModel *model = i->second.first; - sv_frame_t lastFill = i->second.second; - - if (model) { - - sv_frame_t fill = model->getFillExtent(); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::fillTimerTimedOut: extent for " << model << ": " << fill << ", last " << lastFill << ", total " << m_model->getEndFrame() << endl; -#endif - - if (fill >= lastFill) { - if (fill >= m_model->getEndFrame() && lastFill > 0) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "complete!" << endl; -#endif - invalidateImageCaches(); - i->second.second = -1; - emit modelChanged(); - - } else if (fill > lastFill) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: emitting modelChanged(" - << lastFill << "," << fill << ")" << endl; -#endif - invalidateImageCaches(lastFill, fill); - i->second.second = fill; - emit modelChangedWithin(lastFill, fill); - } - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: going backwards, emitting modelChanged(" - << m_model->getStartFrame() << "," << m_model->getEndFrame() << ")" << endl; -#endif - invalidateImageCaches(); - i->second.second = fill; - emit modelChangedWithin(m_model->getStartFrame(), m_model->getEndFrame()); - } - - if (i->second.second >= 0) { - allDone = false; - } - } - } - - if (allDone) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: all complete!" << endl; -#endif - delete m_updateTimer; - m_updateTimer = 0; - } -} - bool SpectrogramLayer::hasLightBackground() const { return ColourMapper(m_colourMap, 1.f, 255.f).hasLightBackground(); } -void -SpectrogramLayer::initialisePalette() -{ - int formerRotation = m_colourRotation; - - if (m_colourMap == (int)ColourMapper::BlackOnWhite) { - m_palette.setColour(NO_VALUE, Qt::white); - } else { - m_palette.setColour(NO_VALUE, Qt::black); - } - - ColourMapper mapper(m_colourMap, 1.f, 255.f); - - for (int pixel = 1; pixel < 256; ++pixel) { - m_palette.setColour((unsigned char)pixel, mapper.map(pixel)); - } - - m_crosshairColour = mapper.getContrastingColour(); - - m_colourRotation = 0; - rotatePalette(m_colourRotation - formerRotation); - m_colourRotation = formerRotation; - - m_drawBuffer = QImage(); -} - -void -SpectrogramLayer::rotatePalette(int distance) -{ - QColor newPixels[256]; - - newPixels[NO_VALUE] = m_palette.getColour(NO_VALUE); - - for (int pixel = 1; pixel < 256; ++pixel) { - int target = pixel + distance; - while (target < 1) target += 255; - while (target > 255) target -= 255; - newPixels[target] = m_palette.getColour((unsigned char)pixel); - } - - for (int pixel = 0; pixel < 256; ++pixel) { - m_palette.setColour((unsigned char)pixel, newPixels[pixel]); - } - - m_drawBuffer = QImage(); -} - -unsigned char -SpectrogramLayer::getDisplayValue(View *v, double input) const -{ - int value; - - double min = 0.0; - double max = 1.0; - - if (m_normalizeVisibleArea) { - min = m_viewMags[v].getMin(); - max = m_viewMags[v].getMax(); - } else if (!m_normalizeColumns) { - if (m_colourScale == LinearColourScale //|| -// m_colourScale == MeterColourScale) { - ) { - max = 0.1; - } - } - - double thresh = -80.0; - - if (max == 0.0) max = 1.0; - if (max == min) min = max - 0.0001; - - switch (m_colourScale) { - - default: - case LinearColourScale: - value = int(((input - min) / (max - min)) * 255.0) + 1; - break; - - case MeterColourScale: - value = AudioLevel::multiplier_to_preview - ((input - min) / (max - min), 254) + 1; - break; - - case dBSquaredColourScale: - input = ((input - min) * (input - min)) / ((max - min) * (max - min)); - if (input > 0.0) { - input = 10.0 * log10(input); - } else { - input = thresh; - } - if (min > 0.0) { - thresh = 10.0 * log10(min * min); - if (thresh < -80.0) thresh = -80.0; - } - input = (input - thresh) / (-thresh); - if (input < 0.0) input = 0.0; - if (input > 1.0) input = 1.0; - value = int(input * 255.0) + 1; - break; - - case dBColourScale: - //!!! experiment with normalizing the visible area this way. - //In any case, we need to have some indication of what the dB - //scale is relative to. - input = (input - min) / (max - min); - if (input > 0.0) { - input = 10.0 * log10(input); - } else { - input = thresh; - } - if (min > 0.0) { - thresh = 10.0 * log10(min); - if (thresh < -80.0) thresh = -80.0; - } - input = (input - thresh) / (-thresh); - if (input < 0.0) input = 0.0; - if (input > 1.0) input = 1.0; - value = int(input * 255.0) + 1; - break; - - case PhaseColourScale: - value = int((input * 127.0 / M_PI) + 128); - break; - } - - if (value > UCHAR_MAX) value = UCHAR_MAX; - if (value < 0) value = 0; - return (unsigned char)value; -} - double SpectrogramLayer::getEffectiveMinFrequency() const { sv_samplerate_t sr = m_model->getSampleRate(); - double minf = double(sr) / m_fftSize; + double minf = double(sr) / getFFTSize(); if (m_minFrequency > 0.0) { - int minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.01); + int minbin = int((double(m_minFrequency) * getFFTSize()) / sr + 0.01); if (minbin < 1) minbin = 1; - minf = minbin * sr / m_fftSize; + minf = minbin * sr / getFFTSize(); } return minf; @@ -1285,68 +1086,59 @@ double maxf = double(sr) / 2; if (m_maxFrequency > 0.0) { - int maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); - if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; - maxf = maxbin * sr / m_fftSize; + int maxbin = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1); + if (maxbin > getFFTSize() / 2) maxbin = getFFTSize() / 2; + maxf = maxbin * sr / getFFTSize(); } return maxf; } bool -SpectrogramLayer::getYBinRange(View *v, int y, double &q0, double &q1) const +SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const { Profiler profiler("SpectrogramLayer::getYBinRange"); + int h = v->getPaintHeight(); + if (y < 0 || y >= h) return false; + q0 = getBinForY(v, y); + q1 = getBinForY(v, y-1); + return true; +} + +double +SpectrogramLayer::getYForBin(const LayerGeometryProvider *v, double bin) const +{ + double minf = getEffectiveMinFrequency(); + double maxf = getEffectiveMaxFrequency(); + bool logarithmic = (m_binScale == BinScale::Log); + sv_samplerate_t sr = m_model->getSampleRate(); + + double freq = (bin * sr) / getFFTSize(); - int h = v->height(); - if (y < 0 || y >= h) return false; - + double y = v->getYForFrequency(freq, minf, maxf, logarithmic); + + return y; +} + +double +SpectrogramLayer::getBinForY(const LayerGeometryProvider *v, double y) const +{ sv_samplerate_t sr = m_model->getSampleRate(); double minf = getEffectiveMinFrequency(); double maxf = getEffectiveMaxFrequency(); - bool logarithmic = (m_frequencyScale == LogFrequencyScale); - - q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); - q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); - - // Now map these on to ("proportions of") actual bins, using raw - // FFT size (unsmoothed) - - q0 = (q0 * m_fftSize) / sr; - q1 = (q1 * m_fftSize) / sr; - - return true; + bool logarithmic = (m_binScale == BinScale::Log); + + double freq = v->getFrequencyForY(y, minf, maxf, logarithmic); + + // Now map on to ("proportion of") actual bins + double bin = (freq * getFFTSize()) / sr; + + return bin; } bool -SpectrogramLayer::getSmoothedYBinRange(View *v, int y, double &q0, double &q1) const -{ - Profiler profiler("SpectrogramLayer::getSmoothedYBinRange"); - - int h = v->height(); - if (y < 0 || y >= h) return false; - - sv_samplerate_t sr = m_model->getSampleRate(); - double minf = getEffectiveMinFrequency(); - double maxf = getEffectiveMaxFrequency(); - - bool logarithmic = (m_frequencyScale == LogFrequencyScale); - - q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); - q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); - - // Now map these on to ("proportions of") actual bins, using raw - // FFT size (unsmoothed) - - q0 = (q0 * getFFTSize(v)) / sr; - q1 = (q1 * getFFTSize(v)) / sr; - - return true; -} - -bool -SpectrogramLayer::getXBinRange(View *v, int x, double &s0, double &s1) const +SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const { sv_frame_t modelStart = m_model->getStartFrame(); sv_frame_t modelEnd = m_model->getEndFrame(); @@ -1370,7 +1162,7 @@ } bool -SpectrogramLayer::getXBinSourceRange(View *v, int x, RealTime &min, RealTime &max) const +SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const { double s0 = 0, s1 = 0; if (!getXBinRange(v, x, s0, s1)) return false; @@ -1389,7 +1181,7 @@ } bool -SpectrogramLayer::getYBinSourceRange(View *v, int y, double &freqMin, double &freqMax) +SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax) const { double q0 = 0, q1 = 0; @@ -1401,14 +1193,14 @@ sv_samplerate_t sr = m_model->getSampleRate(); for (int q = q0i; q <= q1i; ++q) { - if (q == q0i) freqMin = (sr * q) / m_fftSize; - if (q == q1i) freqMax = (sr * (q+1)) / m_fftSize; + if (q == q0i) freqMin = (sr * q) / getFFTSize(); + if (q == q1i) freqMax = (sr * (q+1)) / getFFTSize(); } return true; } bool -SpectrogramLayer::getAdjustedYBinSourceRange(View *v, int x, int y, +SpectrogramLayer::getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y, double &freqMin, double &freqMax, double &adjFreqMin, double &adjFreqMax) const @@ -1417,7 +1209,7 @@ return false; } - FFTModel *fft = getFFTModel(v); + FFTModel *fft = getFFTModel(); if (!fft) return false; double s0 = 0, s1 = 0; @@ -1436,22 +1228,23 @@ bool haveAdj = false; - bool peaksOnly = (m_binDisplay == PeakBins || - m_binDisplay == PeakFrequencies); + bool peaksOnly = (m_binDisplay == BinDisplay::PeakBins || + m_binDisplay == BinDisplay::PeakFrequencies); for (int q = q0i; q <= q1i; ++q) { for (int s = s0i; s <= s1i; ++s) { - if (!fft->isColumnAvailable(s)) continue; - double binfreq = (double(sr) * q) / m_windowSize; if (q == q0i) freqMin = binfreq; if (q == q1i) freqMax = binfreq; if (peaksOnly && !fft->isLocalPeak(s, q)) continue; - if (!fft->isOverThreshold(s, q, float(m_threshold * double(m_fftSize)/2.0))) continue; + if (!fft->isOverThreshold + (s, q, float(m_threshold * double(getFFTSize())/2.0))) { + continue; + } double freq = binfreq; @@ -1475,7 +1268,7 @@ } bool -SpectrogramLayer::getXYBinSourceRange(View *v, int x, int y, +SpectrogramLayer::getXYBinSourceRange(LayerGeometryProvider *v, int x, int y, double &min, double &max, double &phaseMin, double &phaseMax) const { @@ -1497,11 +1290,7 @@ bool rv = false; - int zp = getZeroPadLevel(v); - q0i *= zp + 1; - q1i *= zp + 1; - - FFTModel *fft = getFFTModel(v); + FFTModel *fft = getFFTModel(); if (fft) { @@ -1518,15 +1307,13 @@ for (int s = s0i; s <= s1i; ++s) { if (s >= 0 && q >= 0 && s < cw && q < ch) { - if (!fft->isColumnAvailable(s)) continue; - double value; value = fft->getPhaseAt(s, q); if (!have || value < phaseMin) { phaseMin = value; } if (!have || value > phaseMax) { phaseMax = value; } - value = fft->getMagnitudeAt(s, q) / (m_fftSize/2.0); + value = fft->getMagnitudeAt(s, q) / (getFFTSize()/2.0); if (!have || value < min) { min = value; } if (!have || value > max) { max = value; } @@ -1542,222 +1329,109 @@ return rv; } - -int -SpectrogramLayer::getZeroPadLevel(const View *v) const + +void +SpectrogramLayer::recreateFFTModel() { - //!!! tidy all this stuff - - if (m_binDisplay != AllBins) return 0; - - Preferences::SpectrogramSmoothing smoothing = - Preferences::getInstance()->getSpectrogramSmoothing(); +#ifdef DEBUG_SPECTROGRAM + cerr << "SpectrogramLayer::recreateFFTModel called" << endl; +#endif + + if (!m_model || !m_model->isOK()) { + emit sliceableModelReplaced(m_fftModel, 0); + delete m_fftModel; + delete m_peakCache; + delete m_wholeCache; + m_fftModel = 0; + m_peakCache = 0; + m_wholeCache = 0; + return; + } + + FFTModel *oldModel = m_fftModel; + + m_fftModel = new FFTModel(m_model, + m_channel, + m_windowType, + m_windowSize, + getWindowIncrement(), + getFFTSize()); + + delete m_peakCache; + m_peakCache = 0; + + delete m_wholeCache; + m_wholeCache = 0; - if (smoothing == Preferences::NoSpectrogramSmoothing || - smoothing == Preferences::SpectrogramInterpolated) return 0; - - if (m_frequencyScale == LogFrequencyScale) return 3; - - sv_samplerate_t sr = m_model->getSampleRate(); - - int maxbin = m_fftSize / 2; - if (m_maxFrequency > 0) { - maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); - if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; + if (!m_fftModel->isOK()) { + QMessageBox::critical + (0, tr("FFT cache failed"), + tr("Failed to create the FFT model for this spectrogram.\n" + "There may be insufficient memory or disc space to continue.")); + delete m_fftModel; + m_fftModel = 0; + return; } - int minbin = 1; - if (m_minFrequency > 0) { - minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.1); - if (minbin < 1) minbin = 1; - if (minbin >= maxbin) minbin = maxbin - 1; + if (canStoreWholeCache()) { // i.e. if enough memory + m_wholeCache = new Dense3DModelPeakCache(m_fftModel, 1); + m_peakCache = new Dense3DModelPeakCache(m_wholeCache, m_peakCacheDivisor); + } else { + m_peakCache = new Dense3DModelPeakCache(m_fftModel, m_peakCacheDivisor); } - double perPixel = - double(v->height()) / - double((maxbin - minbin) / (m_zeroPadLevel + 1)); - - if (perPixel > 2.8) { - return 3; // 4x oversampling - } else if (perPixel > 1.5) { - return 1; // 2x - } else { - return 0; // 1x + emit sliceableModelReplaced(oldModel, m_fftModel); + + delete oldModel; +} + +bool +SpectrogramLayer::canStoreWholeCache() const +{ + if (!m_fftModel) { + return false; // or true, doesn't really matter } -} - -int -SpectrogramLayer::getFFTSize(const View *v) const -{ - return m_fftSize * (getZeroPadLevel(v) + 1); -} - -FFTModel * -SpectrogramLayer::getFFTModel(const View *v) const -{ - if (!m_model) return 0; - - int fftSize = getFFTSize(v); - - if (m_fftModels.find(v) != m_fftModels.end()) { - if (m_fftModels[v].first == 0) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << endl; -#endif - return 0; + + size_t sz = + size_t(m_fftModel->getWidth()) * + size_t(m_fftModel->getHeight()) * + sizeof(float); + + try { + SVDEBUG << "Requesting advice from StorageAdviser on whether to create whole-model cache" << endl; + StorageAdviser::Recommendation recommendation = + StorageAdviser::recommend + (StorageAdviser::Criteria(StorageAdviser::SpeedCritical | + StorageAdviser::PrecisionCritical | + StorageAdviser::FrequentLookupLikely), + sz / 1024, sz / 1024); + if ((recommendation & StorageAdviser::UseDisc) || + (recommendation & StorageAdviser::ConserveSpace)) { + SVDEBUG << "Seems inadvisable to create whole-model cache" << endl; + return false; + } else { + SVDEBUG << "Seems fine to create whole-model cache" << endl; + return true; } - if (m_fftModels[v].first->getHeight() != fftSize / 2 + 1) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[v].first->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl; -#endif - delete m_fftModels[v].first; - m_fftModels.erase(v); - delete m_peakCaches[v]; - m_peakCaches.erase(v); - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[v].first->getHeight() << endl; -#endif - return m_fftModels[v].first; - } + } catch (const InsufficientDiscSpace &) { + SVDEBUG << "Seems like a terrible idea to create whole-model cache" << endl; + return false; } - - if (m_fftModels.find(v) == m_fftModels.end()) { - - FFTModel *model = new FFTModel(m_model, - m_channel, - m_windowType, - m_windowSize, - getWindowIncrement(), - fftSize, - true, // polar - StorageAdviser::SpeedCritical, - m_candidateFillStartFrame); - - if (!model->isOK()) { - QMessageBox::critical - (0, tr("FFT cache failed"), - tr("Failed to create the FFT model for this spectrogram.\n" - "There may be insufficient memory or disc space to continue.")); - delete model; - m_fftModels[v] = FFTFillPair(0, 0); - return 0; - } - - if (!m_sliceableModel) { -#ifdef DEBUG_SPECTROGRAM - cerr << "SpectrogramLayer: emitting sliceableModelReplaced(0, " << model << ")" << endl; -#endif - ((SpectrogramLayer *)this)->sliceableModelReplaced(0, model); - m_sliceableModel = model; - } - - m_fftModels[v] = FFTFillPair(model, 0); - - model->resume(); - - delete m_updateTimer; - m_updateTimer = new QTimer((SpectrogramLayer *)this); - connect(m_updateTimer, SIGNAL(timeout()), - this, SLOT(fillTimerTimedOut())); - m_updateTimer->start(200); - } - - return m_fftModels[v].first; -} - -Dense3DModelPeakCache * -SpectrogramLayer::getPeakCache(const View *v) const -{ - if (!m_peakCaches[v]) { - FFTModel *f = getFFTModel(v); - if (!f) return 0; - m_peakCaches[v] = new Dense3DModelPeakCache(f, 8); - } - return m_peakCaches[v]; } const Model * SpectrogramLayer::getSliceableModel() const { - if (m_sliceableModel) return m_sliceableModel; - if (m_fftModels.empty()) return 0; - m_sliceableModel = m_fftModels.begin()->second.first; - return m_sliceableModel; -} - -void -SpectrogramLayer::invalidateFFTModels() -{ - for (ViewFFTMap::iterator i = m_fftModels.begin(); - i != m_fftModels.end(); ++i) { - delete i->second.first; - } - for (PeakCacheMap::iterator i = m_peakCaches.begin(); - i != m_peakCaches.end(); ++i) { - delete i->second; - } - - m_fftModels.clear(); - m_peakCaches.clear(); - - if (m_sliceableModel) { - cerr << "SpectrogramLayer: emitting sliceableModelReplaced(" << m_sliceableModel << ", 0)" << endl; - emit sliceableModelReplaced(m_sliceableModel, 0); - m_sliceableModel = 0; - } + return m_fftModel; } void SpectrogramLayer::invalidateMagnitudes() { +#ifdef DEBUG_SPECTROGRAM + cerr << "SpectrogramLayer::invalidateMagnitudes called" << endl; +#endif m_viewMags.clear(); - for (std::vector::iterator i = m_columnMags.begin(); - i != m_columnMags.end(); ++i) { - *i = MagnitudeRange(); - } -} - -bool -SpectrogramLayer::updateViewMagnitudes(View *v) const -{ - MagnitudeRange mag; - - int x0 = 0, x1 = v->width(); - double s00 = 0, s01 = 0, s10 = 0, s11 = 0; - - if (!getXBinRange(v, x0, s00, s01)) { - s00 = s01 = double(m_model->getStartFrame()) / getWindowIncrement(); - } - - if (!getXBinRange(v, x1, s10, s11)) { - s10 = s11 = double(m_model->getEndFrame()) / getWindowIncrement(); - } - - int s0 = int(std::min(s00, s10) + 0.0001); - int s1 = int(std::max(s01, s11) + 0.0001); - -// SVDEBUG << "SpectrogramLayer::updateViewMagnitudes: x0 = " << x0 << ", x1 = " << x1 << ", s00 = " << s00 << ", s11 = " << s11 << " s0 = " << s0 << ", s1 = " << s1 << endl; - - if (int(m_columnMags.size()) <= s1) { - m_columnMags.resize(s1 + 1); - } - - for (int s = s0; s <= s1; ++s) { - if (m_columnMags[s].isSet()) { - mag.sample(m_columnMags[s]); - } - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::updateViewMagnitudes returning from cols " - << s0 << " -> " << s1 << " inclusive" << endl; -#endif - - if (!mag.isSet()) return false; - if (mag == m_viewMags[v]) return false; - m_viewMags[v] = mag; - return true; } void @@ -1766,24 +1440,150 @@ m_synchronous = synchronous; } +Colour3DPlotRenderer * +SpectrogramLayer::getRenderer(LayerGeometryProvider *v) const +{ + int viewId = v->getId(); + + if (m_renderers.find(viewId) == m_renderers.end()) { + + Colour3DPlotRenderer::Sources sources; + sources.verticalBinLayer = this; + sources.fft = getFFTModel(); + sources.source = sources.fft; + if (m_peakCache) sources.peakCaches.push_back(m_peakCache); + if (m_wholeCache) sources.peakCaches.push_back(m_wholeCache); + + ColourScale::Parameters cparams; + cparams.colourMap = m_colourMap; + cparams.scaleType = m_colourScale; + cparams.multiple = m_colourScaleMultiple; + + if (m_colourScale != ColourScaleType::Phase) { + cparams.gain = m_gain; + cparams.threshold = m_threshold; + } + + float minValue = 0.0f; + float maxValue = 1.0f; + + if (m_normalizeVisibleArea && m_viewMags[viewId].isSet()) { + minValue = m_viewMags[viewId].getMin(); + maxValue = m_viewMags[viewId].getMax(); + } else if (m_colourScale == ColourScaleType::Linear && + m_normalization == ColumnNormalization::None) { + maxValue = 0.1f; + } + + if (maxValue <= minValue) { + maxValue = minValue + 0.1f; + } + if (maxValue <= m_threshold) { + maxValue = m_threshold + 0.1f; + } + + cparams.minValue = minValue; + cparams.maxValue = maxValue; + + m_lastRenderedMags[viewId] = MagnitudeRange(minValue, maxValue); + + Colour3DPlotRenderer::Parameters params; + params.colourScale = ColourScale(cparams); + params.normalization = m_normalization; + params.binDisplay = m_binDisplay; + params.binScale = m_binScale; + params.alwaysOpaque = true; + params.invertVertical = false; + params.scaleFactor = 1.0; + params.colourRotation = m_colourRotation; + + if (m_colourScale != ColourScaleType::Phase && + m_normalization != ColumnNormalization::Hybrid) { + params.scaleFactor *= 2.f / float(getFFTSize()); + } + + Preferences::SpectrogramSmoothing smoothing = + Preferences::getInstance()->getSpectrogramSmoothing(); + params.interpolate = + (smoothing == Preferences::SpectrogramInterpolated || + smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated); + + m_renderers[v->getId()] = new Colour3DPlotRenderer(sources, params); + } + + return m_renderers[v->getId()]; +} + void -SpectrogramLayer::paint(View *v, QPainter &paint, QRect rect) const +SpectrogramLayer::paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { - // What a lovely, old-fashioned function this is. - // It's practically FORTRAN 77 in its clarity and linearity. - + Colour3DPlotRenderer *renderer = getRenderer(v); + + Colour3DPlotRenderer::RenderResult result; + MagnitudeRange magRange; + int viewId = v->getId(); + + bool continuingPaint = !renderer->geometryChanged(v); + + if (continuingPaint) { + magRange = m_viewMags[viewId]; + } + + if (m_synchronous) { + + result = renderer->render(v, paint, rect); + + } else { + + result = renderer->renderTimeConstrained(v, paint, rect); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "rect width from this paint: " << result.rendered.width() + << ", mag range in this paint: " << result.range.getMin() << " -> " + << result.range.getMax() << endl; +#endif + + QRect uncached = renderer->getLargestUncachedRect(v); + if (uncached.width() > 0) { + v->updatePaintRect(uncached); + } + } + + magRange.sample(result.range); + + if (magRange.isSet()) { + if (m_viewMags[viewId] != magRange) { + m_viewMags[viewId] = magRange; +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "mag range in this view has changed: " + << magRange.getMin() << " -> " << magRange.getMax() << endl; +#endif + } + } + + if (!continuingPaint && m_normalizeVisibleArea && + m_viewMags[viewId] != m_lastRenderedMags[viewId]) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "mag range has changed from last rendered range: re-rendering" + << endl; +#endif + delete m_renderers[viewId]; + m_renderers.erase(viewId); + v->updatePaintRect(v->getPaintRect()); + } +} + +void +SpectrogramLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const +{ Profiler profiler("SpectrogramLayer::paint", false); #ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << ", m_updateTimer " << m_updateTimer << endl; + cerr << "SpectrogramLayer::paint() entering: m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl; - cerr << "rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl; + cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl; #endif - sv_frame_t startFrame = v->getStartFrame(); - if (startFrame < 0) m_candidateFillStartFrame = 0; - else m_candidateFillStartFrame = startFrame; - if (!m_model || !m_model->isOK() || !m_model->isReady()) { return; } @@ -1792,1034 +1592,13 @@ SVDEBUG << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << endl; } - // Need to do this even if !isLayerDormant, as that could mean v - // is not in the dormancy map at all -- we need it to be present - // and accountable for when determining whether we need the cache - // in the cache-fill thread above. - //!!! no inter use cache-fill thread - const_cast(this)->Layer::setLayerDormant(v, false); - - int fftSize = getFFTSize(v); -/* - FFTModel *fft = getFFTModel(v); - if (!fft) { - cerr << "ERROR: SpectrogramLayer::paint(): No FFT model, returning" << endl; - return; - } -*/ - ImageCache &cache = m_imageCaches[v]; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::paint(): image cache valid area " << cache. - -validArea.x() << ", " << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << endl; -#endif - -#ifdef DEBUG_SPECTROGRAM_REPAINT - bool stillCacheing = (m_updateTimer != 0); - SVDEBUG << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << endl; -#endif - - int zoomLevel = v->getZoomLevel(); - - int x0 = 0; - int x1 = v->width(); - - bool recreateWholeImageCache = true; - - x0 = rect.left(); - x1 = rect.right() + 1; -/* - double xPixelRatio = double(fft->getResolution()) / double(zoomLevel); - cerr << "xPixelRatio = " << xPixelRatio << endl; - if (xPixelRatio < 1.f) xPixelRatio = 1.f; -*/ - if (cache.validArea.width() > 0) { - - int cw = cache.image.width(); - int ch = cache.image.height(); - - if (int(cache.zoomLevel) == zoomLevel && - cw == v->width() && - ch == v->height()) { - - if (v->getXForFrame(cache.startFrame) == - v->getXForFrame(startFrame) && - cache.validArea.x() <= x0 && - cache.validArea.x() + cache.validArea.width() >= x1) { - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: image cache good" << endl; -#endif - - paint.drawImage(rect, cache.image, rect); - //!!! -// paint.drawImage(v->rect(), cache.image, -// QRect(QPoint(0, 0), cache.image.size())); - - illuminateLocalFeatures(v, paint); - return; - - } else { - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: image cache partially OK" << endl; -#endif - - recreateWholeImageCache = false; - - int dx = v->getXForFrame(cache.startFrame) - - v->getXForFrame(startFrame); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: dx = " << dx << " (image cache " << cw << "x" << ch << ")" << endl; -#endif - - if (dx != 0 && - dx > -cw && - dx < cw) { - - int dxp = dx; - if (dxp < 0) dxp = -dxp; - size_t copy = (cw - dxp) * sizeof(QRgb); - for (int y = 0; y < ch; ++y) { - QRgb *line = (QRgb *)cache.image.scanLine(y); - if (dx < 0) { - memmove(line, line + dxp, copy); - } else { - memmove(line + dxp, line, copy); - } - } - - int px = cache.validArea.x(); - int pw = cache.validArea.width(); - - if (dx < 0) { - x0 = cw + dx; - x1 = cw; - px += dx; - if (px < 0) { - pw += px; - px = 0; - if (pw < 0) pw = 0; - } - } else { - x0 = 0; - x1 = dx; - px += dx; - if (px + pw > cw) { - pw = int(cw) - px; - if (pw < 0) pw = 0; - } - } - - cache.validArea = - QRect(px, cache.validArea.y(), - pw, cache.validArea.height()); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "valid area now " - << px << "," << cache.validArea.y() - << " " << pw << "x" << cache.validArea.height() - << endl; -#endif -/* - paint.drawImage(rect & cache.validArea, - cache.image, - rect & cache.validArea); -*/ - } else if (dx != 0) { - - // we scrolled too far to be of use - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "dx == " << dx << ": scrolled too far for cache to be useful" << endl; -#endif - - cache.validArea = QRect(); - recreateWholeImageCache = true; - } - } - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: image cache useless" << endl; - if (int(cache.zoomLevel) != zoomLevel) { - cerr << "(cache zoomLevel " << cache.zoomLevel - << " != " << zoomLevel << ")" << endl; - } - if (cw != v->width()) { - cerr << "(cache width " << cw - << " != " << v->width(); - } - if (ch != v->height()) { - cerr << "(cache height " << ch - << " != " << v->height(); - } -#endif - cache.validArea = QRect(); -// recreateWholeImageCache = true; - } - } - - if (updateViewMagnitudes(v)) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; -#endif - if (m_normalizeVisibleArea) { - cache.validArea = QRect(); - recreateWholeImageCache = true; - } - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "No change in magnitude range [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; -#endif - } - - if (recreateWholeImageCache) { - x0 = 0; - x1 = v->width(); - } - - struct timeval tv; - (void)gettimeofday(&tv, 0); - RealTime mainPaintStart = RealTime::fromTimeval(tv); - - int paintBlockWidth = m_lastPaintBlockWidth; - - if (m_synchronous) { - if (paintBlockWidth < x1 - x0) { - // always paint full width - paintBlockWidth = x1 - x0; - } - } else { - if (paintBlockWidth == 0) { - paintBlockWidth = (300000 / zoomLevel); - } else { - RealTime lastTime = m_lastPaintTime; - while (lastTime > RealTime::fromMilliseconds(200) && - paintBlockWidth > 50) { - paintBlockWidth /= 2; - lastTime = lastTime / 2; - } - while (lastTime < RealTime::fromMilliseconds(90) && - paintBlockWidth < 1500) { - paintBlockWidth *= 2; - lastTime = lastTime * 2; - } - } - - if (paintBlockWidth < 20) paintBlockWidth = 20; - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "[" << this << "]: last paint width: " << m_lastPaintBlockWidth << ", last paint time: " << m_lastPaintTime << ", new paint width: " << paintBlockWidth << endl; -#endif - - // We always paint the full height when refreshing the cache. - // Smaller heights can be used when painting direct from cache - // (further up in this function), but we want to ensure the cache - // is coherent without having to worry about vertical matching of - // required and valid areas as well as horizontal. - - int h = v->height(); - - if (cache.validArea.width() > 0) { - - // If part of the cache is known to be valid, select a strip - // immediately to left or right of the valid part - - //!!! this really needs to be coordinated with the selection - //!!! of m_drawBuffer boundaries in the bufferBinResolution - //!!! case below - - int vx0 = 0, vx1 = 0; - vx0 = cache.validArea.x(); - vx1 = cache.validArea.x() + cache.validArea.width(); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "x0 " << x0 << ", x1 " << x1 << ", vx0 " << vx0 << ", vx1 " << vx1 << ", paintBlockWidth " << paintBlockWidth << endl; -#endif - if (x0 < vx0) { - if (x0 + paintBlockWidth < vx0) { - x0 = vx0 - paintBlockWidth; - } - x1 = vx0; - } else if (x0 >= vx1) { - x0 = vx1; - if (x1 > x0 + paintBlockWidth) { - x1 = x0 + paintBlockWidth; - } - } else { - // x0 is within the valid area - if (x1 > vx1) { - x0 = vx1; - if (x0 + paintBlockWidth < x1) { - x1 = x0 + paintBlockWidth; - } - } else { - x1 = x0; // it's all valid, paint nothing - } - } - - cache.validArea = QRect - (std::min(vx0, x0), cache.validArea.y(), - std::max(vx1 - std::min(vx0, x0), - x1 - std::min(vx0, x0)), - cache.validArea.height()); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Valid area becomes " << cache.validArea.x() - << ", " << cache.validArea.y() << ", " - << cache.validArea.width() << "x" - << cache.validArea.height() << endl; -#endif - - } else { - if (x1 > x0 + paintBlockWidth) { - int sfx = x1; - if (startFrame < 0) sfx = v->getXForFrame(0); - if (sfx >= x0 && sfx + paintBlockWidth <= x1) { - x0 = sfx; - x1 = x0 + paintBlockWidth; - } else { - int mid = (x1 + x0) / 2; - x0 = mid - paintBlockWidth/2; - x1 = x0 + paintBlockWidth; - } - } -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Valid area becomes " << x0 << ", 0, " << (x1-x0) - << "x" << h << endl; -#endif - cache.validArea = QRect(x0, 0, x1 - x0, h); - } - -/* - if (xPixelRatio != 1.f) { - x0 = int((int(x0 / xPixelRatio) - 4) * xPixelRatio + 0.0001); - x1 = int((int(x1 / xPixelRatio) + 4) * xPixelRatio + 0.0001); - } -*/ - int w = x1 - x0; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << endl; -#endif - - sv_samplerate_t sr = m_model->getSampleRate(); - - // Set minFreq and maxFreq to the frequency extents of the possibly - // zero-padded visible bin range, and displayMinFreq and displayMaxFreq - // to the actual scale frequency extents (presumably not zero padded). - - // If we are zero padding, we want to use the zero-padded - // equivalents of the bins that we would be using if not zero - // padded, to avoid spaces at the top and bottom of the display. - - // Note fftSize is the actual zero-padded fft size, m_fftSize the - // nominal fft size. - - int maxbin = m_fftSize / 2; - if (m_maxFrequency > 0) { - maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.001); - if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; - } - - int minbin = 1; - if (m_minFrequency > 0) { - minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.001); -// cerr << "m_minFrequency = " << m_minFrequency << " -> minbin = " << minbin << endl; - if (minbin < 1) minbin = 1; - if (minbin >= maxbin) minbin = maxbin - 1; - } - - int zpl = getZeroPadLevel(v) + 1; - minbin = minbin * zpl; - maxbin = (maxbin + 1) * zpl - 1; - - double minFreq = (double(minbin) * sr) / fftSize; - double maxFreq = (double(maxbin) * sr) / fftSize; - - double displayMinFreq = minFreq; - double displayMaxFreq = maxFreq; - - if (fftSize != m_fftSize) { - displayMinFreq = getEffectiveMinFrequency(); - displayMaxFreq = getEffectiveMaxFrequency(); - } - -// cerr << "(giving actual minFreq " << minFreq << " and display minFreq " << displayMinFreq << ")" << endl; - - int increment = getWindowIncrement(); - - bool logarithmic = (m_frequencyScale == LogFrequencyScale); -/* - double yforbin[maxbin - minbin + 1]; - - for (int q = minbin; q <= maxbin; ++q) { - double f0 = (double(q) * sr) / fftSize; - yforbin[q - minbin] = - v->getYForFrequency(f0, displayMinFreq, displayMaxFreq, - logarithmic); - } -*/ - MagnitudeRange overallMag = m_viewMags[v]; - bool overallMagChanged = false; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl; -#endif - - if (w == 0) { - SVDEBUG << "*** NOTE: w == 0" << endl; - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - int pixels = 0; -#endif - - Profiler outerprof("SpectrogramLayer::paint: all cols"); - - // The draw buffer contains a fragment at either our pixel - // resolution (if there is more than one time-bin per pixel) or - // time-bin resolution (if a time-bin spans more than one pixel). - // We need to ensure that it starts and ends at points where a - // time-bin boundary occurs at an exact pixel boundary, and with a - // certain amount of overlap across existing pixels so that we can - // scale and draw from it without smoothing errors at the edges. - - // If (getFrameForX(x) / increment) * increment == - // getFrameForX(x), then x is a time-bin boundary. We want two - // such boundaries at either side of the draw buffer -- one which - // we draw up to, and one which we subsequently crop at. - - bool bufferBinResolution = false; - if (increment > zoomLevel) bufferBinResolution = true; - - sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1; - sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1; - - int bufwid; - - if (bufferBinResolution) { - - for (int x = x0; ; --x) { - sv_frame_t f = v->getFrameForX(x); - if ((f / increment) * increment == f) { - if (leftCropFrame == -1) leftCropFrame = f; - else if (x < x0 - 2) { leftBoundaryFrame = f; break; } - } - } - for (int x = x0 + w; ; ++x) { - sv_frame_t f = v->getFrameForX(x); - if ((f / increment) * increment == f) { - if (rightCropFrame == -1) rightCropFrame = f; - else if (x > x0 + w + 2) { rightBoundaryFrame = f; break; } - } - } -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Left: crop: " << leftCropFrame << " (bin " << leftCropFrame/increment << "); boundary: " << leftBoundaryFrame << " (bin " << leftBoundaryFrame/increment << ")" << endl; - cerr << "Right: crop: " << rightCropFrame << " (bin " << rightCropFrame/increment << "); boundary: " << rightBoundaryFrame << " (bin " << rightBoundaryFrame/increment << ")" << endl; -#endif - - bufwid = int((rightBoundaryFrame - leftBoundaryFrame) / increment); - - } else { - - bufwid = w; - } - - vector binforx(bufwid); - vector binfory(h); - - bool usePeaksCache = false; - - if (bufferBinResolution) { - for (int x = 0; x < bufwid; ++x) { - binforx[x] = int(leftBoundaryFrame / increment) + x; -// cerr << "binforx[" << x << "] = " << binforx[x] << endl; - } - m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); - } else { - for (int x = 0; x < bufwid; ++x) { - double s0 = 0, s1 = 0; - if (getXBinRange(v, x + x0, s0, s1)) { - binforx[x] = int(s0 + 0.0001); - } else { - binforx[x] = -1; //??? - } - } - if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() < h) { - m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); - } - usePeaksCache = (increment * 8) < zoomLevel; - if (m_colourScale == PhaseColourScale) usePeaksCache = false; - } - -// No longer exists in Qt5: m_drawBuffer.setNumColors(256); - for (int pixel = 0; pixel < 256; ++pixel) { - m_drawBuffer.setColor((unsigned char)pixel, - m_palette.getColour((unsigned char)pixel).rgb()); - } - - m_drawBuffer.fill(0); - - if (m_binDisplay != PeakFrequencies) { - - for (int y = 0; y < h; ++y) { - double q0 = 0, q1 = 0; - if (!getSmoothedYBinRange(v, h-y-1, q0, q1)) { - binfory[y] = -1; - } else { - binfory[y] = q0; -// cerr << "binfory[" << y << "] = " << binfory[y] << endl; - } - } - - paintDrawBuffer(v, bufwid, h, binforx, binfory, usePeaksCache, - overallMag, overallMagChanged); - - } else { - - paintDrawBufferPeakFrequencies(v, bufwid, h, binforx, - minbin, maxbin, - displayMinFreq, displayMaxFreq, - logarithmic, - overallMag, overallMagChanged); - } - -/* - for (int x = 0; x < w / xPixelRatio; ++x) { - - Profiler innerprof("SpectrogramLayer::paint: 1 pixel column"); - - runOutOfData = !paintColumnValues(v, fft, x0, x, - minbin, maxbin, - displayMinFreq, displayMaxFreq, - xPixelRatio, - h, yforbin); - - if (runOutOfData) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Run out of data -- dropping out of loop" << endl; -#endif - break; - } - } -*/ -#ifdef DEBUG_SPECTROGRAM_REPAINT -// cerr << pixels << " pixels drawn" << endl; -#endif - - if (overallMagChanged) { - m_viewMags[v] = overallMag; -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << endl; -#endif - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Overall mag unchanged at [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; -#endif - } - - outerprof.end(); - - Profiler profiler2("SpectrogramLayer::paint: draw image"); - - if (recreateWholeImageCache) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "Recreating image cache: width = " << v->width() - << ", height = " << h << endl; -#endif - cache.image = QImage(v->width(), h, QImage::Format_ARGB32_Premultiplied); - } - - if (w > 0) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "Painting " << w << "x" << h - << " from draw buffer at " << 0 << "," << 0 - << " to " << w << "x" << h << " on cache at " - << x0 << "," << 0 << endl; -#endif - - QPainter cachePainter(&cache.image); - - if (bufferBinResolution) { - int scaledLeft = v->getXForFrame(leftBoundaryFrame); - int scaledRight = v->getXForFrame(rightBoundaryFrame); -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "Rescaling image from " << bufwid - << "x" << h << " to " - << scaledRight-scaledLeft << "x" << h << endl; -#endif - Preferences::SpectrogramXSmoothing xsmoothing = - Preferences::getInstance()->getSpectrogramXSmoothing(); -// SVDEBUG << "xsmoothing == " << xsmoothing << endl; - QImage scaled = m_drawBuffer.scaled - (scaledRight - scaledLeft, h, - Qt::IgnoreAspectRatio, - ((xsmoothing == Preferences::SpectrogramXInterpolated) ? - Qt::SmoothTransformation : Qt::FastTransformation)); - int scaledLeftCrop = v->getXForFrame(leftCropFrame); - int scaledRightCrop = v->getXForFrame(rightCropFrame); -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to " - << scaledLeftCrop << " from " << scaledLeftCrop - scaledLeft << endl; -#endif - cachePainter.drawImage - (QRect(scaledLeftCrop, 0, - scaledRightCrop - scaledLeftCrop, h), - scaled, - QRect(scaledLeftCrop - scaledLeft, 0, - scaledRightCrop - scaledLeftCrop, h)); - } else { - cachePainter.drawImage(QRect(x0, 0, w, h), - m_drawBuffer, - QRect(0, 0, w, h)); - } - - cachePainter.end(); - } - - QRect pr = rect & cache.validArea; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "Painting " << pr.width() << "x" << pr.height() - << " from cache at " << pr.x() << "," << pr.y() - << " to window" << endl; -#endif - - paint.drawImage(pr.x(), pr.y(), cache.image, - pr.x(), pr.y(), pr.width(), pr.height()); - //!!! -// paint.drawImage(v->rect(), cache.image, -// QRect(QPoint(0, 0), cache.image.size())); - - cache.startFrame = startFrame; - cache.zoomLevel = zoomLevel; - - if (!m_synchronous) { - - if (!m_normalizeVisibleArea || !overallMagChanged) { - - if (cache.validArea.x() > 0) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::paint() updating left (0, " - << cache.validArea.x() << ")" << endl; -#endif - v->update(0, 0, cache.validArea.x(), h); - } - - if (cache.validArea.x() + cache.validArea.width() < - cache.image.width()) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::paint() updating right (" - << cache.validArea.x() + cache.validArea.width() - << ", " - << cache.image.width() - (cache.validArea.x() + - cache.validArea.width()) - << ")" << endl; -#endif - v->update(cache.validArea.x() + cache.validArea.width(), - 0, - cache.image.width() - (cache.validArea.x() + - cache.validArea.width()), - h); - } - } else { - // overallMagChanged - cerr << "\noverallMagChanged - updating all\n" << endl; - cache.validArea = QRect(); - v->update(); - } - } + paintWithRenderer(v, paint, rect); illuminateLocalFeatures(v, paint); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::paint() returning" << endl; -#endif - - if (!m_synchronous) { - m_lastPaintBlockWidth = paintBlockWidth; - (void)gettimeofday(&tv, 0); - m_lastPaintTime = RealTime::fromTimeval(tv) - mainPaintStart; - } - -//!!! if (fftSuspended) fft->resume(); } -bool -SpectrogramLayer::paintDrawBufferPeakFrequencies(View *v, - int w, - int h, - const vector &binforx, - int minbin, - int maxbin, - double displayMinFreq, - double displayMaxFreq, - bool logarithmic, - MagnitudeRange &overallMag, - bool &overallMagChanged) const -{ - Profiler profiler("SpectrogramLayer::paintDrawBufferPeakFrequencies"); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; -#endif - if (minbin < 0) minbin = 0; - if (maxbin < 0) maxbin = minbin+1; - - FFTModel *fft = getFFTModel(v); - if (!fft) return false; - - FFTModel::PeakSet peakfreqs; - - int psx = -1; - -#ifdef __GNUC__ - float values[maxbin - minbin + 1]; -#else - float *values = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); -#endif - - for (int x = 0; x < w; ++x) { - - if (binforx[x] < 0) continue; - - int sx0 = binforx[x]; - int sx1 = sx0; - if (x+1 < w) sx1 = binforx[x+1]; - if (sx0 < 0) sx0 = sx1 - 1; - if (sx0 < 0) continue; - if (sx1 <= sx0) sx1 = sx0 + 1; - - for (int sx = sx0; sx < sx1; ++sx) { - - if (sx < 0 || sx >= int(fft->getWidth())) continue; - - if (!m_synchronous) { - if (!fft->isColumnAvailable(sx)) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Met unavailable column at col " << sx << endl; -#endif - return false; - } - } - - MagnitudeRange mag; - - if (sx != psx) { - peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx, - minbin, maxbin - 1); - if (m_colourScale == PhaseColourScale) { - fft->getPhasesAt(sx, values, minbin, maxbin - minbin + 1); - } else if (m_normalizeColumns) { - fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); - } else if (m_normalizeHybrid) { - fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); - double max = fft->getMaximumMagnitudeAt(sx); - if (max > 0.f) { - for (int i = minbin; i <= maxbin; ++i) { - values[i - minbin] = float(values[i - minbin] * log10(max)); - } - } - } else { - fft->getMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); - } - psx = sx; - } - - for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin(); - pi != peakfreqs.end(); ++pi) { - - int bin = pi->first; - double freq = pi->second; - - if (bin < minbin) continue; - if (bin > maxbin) break; - - double value = values[bin - minbin]; - - if (m_colourScale != PhaseColourScale) { - if (!m_normalizeColumns && !m_normalizeHybrid) { - value /= (m_fftSize/2.0); - } - mag.sample(float(value)); - value *= m_gain; - } - - double y = v->getYForFrequency - (freq, displayMinFreq, displayMaxFreq, logarithmic); - - int iy = int(y + 0.5); - if (iy < 0 || iy >= h) continue; - - m_drawBuffer.setPixel(x, iy, getDisplayValue(v, value)); - } - - if (mag.isSet()) { - if (sx >= int(m_columnMags.size())) { -#ifdef DEBUG_SPECTROGRAM - cerr << "INTERNAL ERROR: " << sx << " >= " - << m_columnMags.size() - << " at SpectrogramLayer.cpp::paintDrawBuffer" - << endl; -#endif - } else { - m_columnMags[sx].sample(mag); - if (overallMag.sample(mag)) overallMagChanged = true; - } - } - } - } - - return true; -} - -bool -SpectrogramLayer::paintDrawBuffer(View *v, - int w, - int h, - const vector &binforx, - const vector &binfory, - bool usePeaksCache, - MagnitudeRange &overallMag, - bool &overallMagChanged) const -{ - Profiler profiler("SpectrogramLayer::paintDrawBuffer"); - - int minbin = int(binfory[0] + 0.0001); - int maxbin = int(binfory[h-1]); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; -#endif - if (minbin < 0) minbin = 0; - if (maxbin < 0) maxbin = minbin+1; - - DenseThreeDimensionalModel *sourceModel = 0; - FFTModel *fft = 0; - int divisor = 1; -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Note: bin display = " << m_binDisplay << ", w = " << w << ", binforx[" << w-1 << "] = " << binforx[w-1] << ", binforx[0] = " << binforx[0] << endl; -#endif - if (usePeaksCache) { //!!! - sourceModel = getPeakCache(v); - divisor = 8;//!!! - minbin = 0; - maxbin = sourceModel->getHeight(); - } else { - sourceModel = fft = getFFTModel(v); - } - - if (!sourceModel) return false; - - bool interpolate = false; - Preferences::SpectrogramSmoothing smoothing = - Preferences::getInstance()->getSpectrogramSmoothing(); - if (smoothing == Preferences::SpectrogramInterpolated || - smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated) { - if (m_binDisplay != PeakBins && - m_binDisplay != PeakFrequencies) { - interpolate = true; - } - } - - int psx = -1; - -#ifdef __GNUC__ - float autoarray[maxbin - minbin + 1]; - float peaks[h]; -#else - float *autoarray = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); - float *peaks = (float *)alloca(h * sizeof(float)); -#endif - - const float *values = autoarray; - DenseThreeDimensionalModel::Column c; - - for (int x = 0; x < w; ++x) { - - if (binforx[x] < 0) continue; - -// float columnGain = m_gain; - float columnMax = 0.f; - - int sx0 = binforx[x] / divisor; - int sx1 = sx0; - if (x+1 < w) sx1 = binforx[x+1] / divisor; - if (sx0 < 0) sx0 = sx1 - 1; - if (sx0 < 0) continue; - if (sx1 <= sx0) sx1 = sx0 + 1; - - for (int y = 0; y < h; ++y) peaks[y] = 0.f; - - for (int sx = sx0; sx < sx1; ++sx) { - -#ifdef DEBUG_SPECTROGRAM_REPAINT -// cerr << "sx = " << sx << endl; -#endif - - if (sx < 0 || sx >= int(sourceModel->getWidth())) continue; - - if (!m_synchronous) { - if (!sourceModel->isColumnAvailable(sx)) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Met unavailable column at col " << sx << endl; -#endif - return false; - } - } - - MagnitudeRange mag; - - if (sx != psx) { - if (fft) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "Retrieving column " << sx << " from fft directly" << endl; -#endif - if (m_colourScale == PhaseColourScale) { - fft->getPhasesAt(sx, autoarray, minbin, maxbin - minbin + 1); - } else if (m_normalizeColumns) { - fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); - } else if (m_normalizeHybrid) { - fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); - double max = fft->getMaximumMagnitudeAt(sx); - for (int i = minbin; i <= maxbin; ++i) { - if (max > 0.0) { - autoarray[i - minbin] = float(autoarray[i - minbin] * log10(max)); - } - } - } else { - fft->getMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); - } - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "Retrieving column " << sx << " from peaks cache" << endl; -#endif - c = sourceModel->getColumn(sx); - if (m_normalizeColumns || m_normalizeHybrid) { - for (int y = 0; y < h; ++y) { - if (c[y] > columnMax) columnMax = c[y]; - } - } - values = c.constData() + minbin; - } - psx = sx; - } - - for (int y = 0; y < h; ++y) { - - double sy0 = binfory[y]; - double sy1 = sy0 + 1; - if (y+1 < h) sy1 = binfory[y+1]; - - double value = 0.0; - - if (interpolate && fabs(sy1 - sy0) < 1.0) { - - double centre = (sy0 + sy1) / 2; - double dist = (centre - 0.5) - rint(centre - 0.5); - int bin = int(centre); - int other = (dist < 0 ? (bin-1) : (bin+1)); - if (bin < minbin) bin = minbin; - if (bin > maxbin) bin = maxbin; - if (other < minbin || other > maxbin) other = bin; - double prop = 1.0 - fabs(dist); - - double v0 = values[bin - minbin]; - double v1 = values[other - minbin]; - if (m_binDisplay == PeakBins) { - if (bin == minbin || bin == maxbin || - v0 < values[bin-minbin-1] || - v0 < values[bin-minbin+1]) v0 = 0.0; - if (other == minbin || other == maxbin || - v1 < values[other-minbin-1] || - v1 < values[other-minbin+1]) v1 = 0.0; - } - if (v0 == 0.0 && v1 == 0.0) continue; - value = prop * v0 + (1.0 - prop) * v1; - - if (m_colourScale != PhaseColourScale) { - if (!m_normalizeColumns) { - value /= (m_fftSize/2.0); - } - mag.sample(float(value)); - value *= m_gain; - } - - peaks[y] = float(value); - - } else { - - int by0 = int(sy0 + 0.0001); - int by1 = int(sy1 + 0.0001); - if (by1 < by0 + 1) by1 = by0 + 1; - - for (int bin = by0; bin < by1; ++bin) { - - value = values[bin - minbin]; - if (m_binDisplay == PeakBins) { - if (bin == minbin || bin == maxbin || - value < values[bin-minbin-1] || - value < values[bin-minbin+1]) continue; - } - - if (m_colourScale != PhaseColourScale) { - if (!m_normalizeColumns) { - value /= (m_fftSize/2.0); - } - mag.sample(float(value)); - value *= m_gain; - } - - if (value > peaks[y]) { - peaks[y] = float(value); //!!! not right for phase! - } - } - } - } - - if (mag.isSet()) { - if (sx >= int(m_columnMags.size())) { -#ifdef DEBUG_SPECTROGRAM - cerr << "INTERNAL ERROR: " << sx << " >= " - << m_columnMags.size() - << " at SpectrogramLayer.cpp::paintDrawBuffer" - << endl; -#endif - } else { - m_columnMags[sx].sample(mag); - if (overallMag.sample(mag)) overallMagChanged = true; - } - } - } - - for (int y = 0; y < h; ++y) { - - double peak = peaks[y]; - - if (m_colourScale != PhaseColourScale && - (m_normalizeColumns || m_normalizeHybrid) && - columnMax > 0.f) { - peak /= columnMax; - if (m_normalizeHybrid) { - peak *= log10(columnMax); - } - } - - unsigned char peakpix = getDisplayValue(v, peak); - - m_drawBuffer.setPixel(x, h-y-1, peakpix); - } - } - - return true; -} - void -SpectrogramLayer::illuminateLocalFeatures(View *v, QPainter &paint) const +SpectrogramLayer::illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &paint) const { Profiler profiler("SpectrogramLayer::illuminateLocalFeatures"); @@ -2828,8 +1607,10 @@ return; } -// cerr << "SpectrogramLayer: illuminateLocalFeatures(" -// << localPos.x() << "," << localPos.y() << ")" << endl; +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: illuminateLocalFeatures(" + << localPos.x() << "," << localPos.y() << ")" << endl; +#endif double s0, s1; double f0, f1; @@ -2846,8 +1627,10 @@ int y1 = int(getYForFrequency(v, f1)); int y0 = int(getYForFrequency(v, f0)); -// cerr << "SpectrogramLayer: illuminate " -// << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl; +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: illuminate " + << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl; +#endif paint.setPen(v->getForeground()); @@ -2858,41 +1641,39 @@ } double -SpectrogramLayer::getYForFrequency(const View *v, double frequency) const +SpectrogramLayer::getYForFrequency(const LayerGeometryProvider *v, double frequency) const { return v->getYForFrequency(frequency, getEffectiveMinFrequency(), getEffectiveMaxFrequency(), - m_frequencyScale == LogFrequencyScale); + m_binScale == BinScale::Log); } double -SpectrogramLayer::getFrequencyForY(const View *v, int y) const +SpectrogramLayer::getFrequencyForY(const LayerGeometryProvider *v, int y) const { return v->getFrequencyForY(y, getEffectiveMinFrequency(), getEffectiveMaxFrequency(), - m_frequencyScale == LogFrequencyScale); + m_binScale == BinScale::Log); } int -SpectrogramLayer::getCompletion(View *v) const +SpectrogramLayer::getCompletion(LayerGeometryProvider *) const { - if (m_updateTimer == 0) return 100; - if (m_fftModels.find(v) == m_fftModels.end()) return 100; - - int completion = m_fftModels[v].first->getCompletion(); + if (!m_fftModel) return 100; + int completion = m_fftModel->getCompletion(); #ifdef DEBUG_SPECTROGRAM_REPAINT - SVDEBUG << "SpectrogramLayer::getCompletion: completion = " << completion << endl; + cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl; #endif return completion; } QString -SpectrogramLayer::getError(View *v) const +SpectrogramLayer::getError(LayerGeometryProvider *) const { - if (m_fftModels.find(v) == m_fftModels.end()) return ""; - return m_fftModels[v].first->getError(); + if (!m_fftModel) return ""; + return m_fftModel->getError(); } bool @@ -2902,10 +1683,10 @@ if (!m_model) return false; sv_samplerate_t sr = m_model->getSampleRate(); - min = double(sr) / m_fftSize; + min = double(sr) / getFFTSize(); max = double(sr) / 2; - logarithmic = (m_frequencyScale == LogFrequencyScale); + logarithmic = (m_binScale == BinScale::Log); unit = "Hz"; return true; } @@ -2935,7 +1716,7 @@ if (m_minFrequency == minf && m_maxFrequency == maxf) return true; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_minFrequency = minf; @@ -2953,7 +1734,7 @@ } bool -SpectrogramLayer::getYScaleValue(const View *v, int y, +SpectrogramLayer::getYScaleValue(const LayerGeometryProvider *v, int y, double &value, QString &unit) const { value = getFrequencyForY(v, y); @@ -2962,7 +1743,7 @@ } bool -SpectrogramLayer::snapToFeatureFrame(View *, +SpectrogramLayer::snapToFeatureFrame(LayerGeometryProvider *, sv_frame_t &frame, int &resolution, SnapType snap) const @@ -2985,17 +1766,12 @@ } void -SpectrogramLayer::measureDoubleClick(View *v, QMouseEvent *e) +SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e) { - ImageCache &cache = m_imageCaches[v]; - - cerr << "cache width: " << cache.image.width() << ", height: " - << cache.image.height() << endl; - - QImage image = cache.image; - - ImageRegionFinder finder; - QRect rect = finder.findRegionExtents(&image, e->pos()); + const Colour3DPlotRenderer *renderer = getRenderer(v); + if (!renderer) return; + + QRect rect = renderer->findSimilarRegionExtents(e->pos()); if (rect.isValid()) { MeasureRect mr; setMeasureRectFromPixrect(v, mr, rect); @@ -3005,11 +1781,11 @@ } bool -SpectrogramLayer::getCrosshairExtents(View *v, QPainter &paint, +SpectrogramLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint, QPoint cursorPos, - std::vector &extents) const + vector &extents) const { - QRect vertical(cursorPos.x() - 12, 0, 12, v->height()); + QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight()); extents.push_back(vertical); QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1); @@ -3028,14 +1804,14 @@ extents.push_back(pitch); QRect rt(cursorPos.x(), - v->height() - paint.fontMetrics().height() - 2, + v->getPaintHeight() - paint.fontMetrics().height() - 2, paint.fontMetrics().width("1234.567 s"), paint.fontMetrics().height()); extents.push_back(rt); int w(paint.fontMetrics().width("1234567890") + 2); QRect frame(cursorPos.x() - w - 2, - v->height() - paint.fontMetrics().height() - 2, + v->getPaintHeight() - paint.fontMetrics().height() - 2, w, paint.fontMetrics().height()); extents.push_back(frame); @@ -3044,7 +1820,7 @@ } void -SpectrogramLayer::paintCrosshairs(View *v, QPainter &paint, +SpectrogramLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint, QPoint cursorPos) const { paint.save(); @@ -3059,46 +1835,46 @@ paint.setPen(m_crosshairColour); paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y()); - paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->height()); + paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->getPaintHeight()); double fundamental = getFrequencyForY(v, cursorPos.y()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, sw + 2, cursorPos.y() - 2, QString("%1 Hz").arg(fundamental), - View::OutlinedText); + PaintAssistant::OutlinedText); if (Pitch::isFrequencyInMidiRange(fundamental)) { QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, sw + 2, cursorPos.y() + paint.fontMetrics().ascent() + 2, pitchLabel, - View::OutlinedText); + PaintAssistant::OutlinedText); } sv_frame_t frame = v->getFrameForX(cursorPos.x()); RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate()); QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str()); QString frameLabel = QString("%1").arg(frame); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2, - v->height() - 2, + v->getPaintHeight() - 2, frameLabel, - View::OutlinedText); - v->drawVisibleText(paint, + PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() + 2, - v->height() - 2, + v->getPaintHeight() - 2, rtLabel, - View::OutlinedText); + PaintAssistant::OutlinedText); int harmonic = 2; while (harmonic < 100) { int hy = int(lrint(getYForFrequency(v, fundamental * harmonic))); - if (hy < 0 || hy > v->height()) break; + if (hy < 0 || hy > v->getPaintHeight()) break; int len = 7; @@ -3122,7 +1898,7 @@ } QString -SpectrogramLayer::getFeatureDescription(View *v, QPoint &pos) const +SpectrogramLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const { int x = pos.x(); int y = pos.y(); @@ -3147,7 +1923,7 @@ QString adjFreqText = "", adjPitchText = ""; - if (m_binDisplay == PeakFrequencies) { + if (m_binDisplay == BinDisplay::PeakFrequencies) { if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax, adjFreqMin, adjFreqMax)) { @@ -3209,12 +1985,12 @@ QString dbMinString; QString dbMaxString; if (dbMin == AudioLevel::DB_FLOOR) { - dbMinString = tr("-Inf"); + dbMinString = Strings::minus_infinity; } else { dbMinString = QString("%1").arg(lrint(dbMin)); } if (dbMax == AudioLevel::DB_FLOOR) { - dbMaxString = tr("-Inf"); + dbMaxString = Strings::minus_infinity; } else { dbMaxString = QString("%1").arg(lrint(dbMax)); } @@ -3244,7 +2020,7 @@ } int -SpectrogramLayer::getVerticalScaleWidth(View *, bool detailed, QPainter &paint) const +SpectrogramLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &paint) const { if (!m_model || !m_model->isOK()) return 0; @@ -3259,13 +2035,14 @@ int fw = paint.fontMetrics().width(tr("43Hz")); if (tw < fw) tw = fw; - int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); + int tickw = (m_binScale == BinScale::Log ? 10 : 4); return cw + tickw + tw + 13; } void -SpectrogramLayer::paintVerticalScale(View *v, bool detailed, QPainter &paint, QRect rect) const +SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed, + QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) { return; @@ -3274,115 +2051,40 @@ Profiler profiler("SpectrogramLayer::paintVerticalScale"); //!!! cache this? - + int h = rect.height(), w = rect.width(); - - int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); - int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0); - - int bins = m_fftSize / 2; + int textHeight = paint.fontMetrics().height(); + + if (detailed && (h > textHeight * 3 + 10)) { + paintDetailedScale(v, paint, rect); + } + m_haveDetailedScale = detailed; + + int tickw = (m_binScale == BinScale::Log ? 10 : 4); + int pkw = (m_binScale == BinScale::Log ? 10 : 0); + + int bins = getFFTSize() / 2; sv_samplerate_t sr = m_model->getSampleRate(); if (m_maxFrequency > 0) { - bins = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); - if (bins > m_fftSize / 2) bins = m_fftSize / 2; + bins = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1); + if (bins > getFFTSize() / 2) bins = getFFTSize() / 2; } int cw = 0; - if (detailed) cw = getColourScaleWidth(paint); - int cbw = paint.fontMetrics().width("dB"); int py = -1; - int textHeight = paint.fontMetrics().height(); int toff = -textHeight + paint.fontMetrics().ascent() + 2; - if (detailed && (h > textHeight * 3 + 10)) { - - int topLines = 2; - if (m_colourScale == PhaseColourScale) topLines = 1; - - int ch = h - textHeight * (topLines + 1) - 8; -// paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); - paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); - - QString top, bottom; - double min = m_viewMags[v].getMin(); - double max = m_viewMags[v].getMax(); - - double dBmin = AudioLevel::multiplier_to_dB(min); - double dBmax = AudioLevel::multiplier_to_dB(max); - - if (dBmax < -60.f) dBmax = -60.f; - else top = QString("%1").arg(lrint(dBmax)); - - if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f; - bottom = QString("%1").arg(lrint(dBmin)); - - //!!! & phase etc - - if (m_colourScale != PhaseColourScale) { - paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2, - 2 + textHeight + toff, "dBFS"); - } - -// paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2, - paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), - 2 + textHeight * topLines + toff + textHeight/2, top); - - paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), - h + toff - 3 - textHeight/2, bottom); - - paint.save(); - paint.setBrush(Qt::NoBrush); - - int lasty = 0; - int lastdb = 0; - - for (int i = 0; i < ch; ++i) { - - double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1)); - int idb = int(dBval); - - double value = AudioLevel::dB_to_multiplier(dBval); - int colour = getDisplayValue(v, value * m_gain); - - paint.setPen(m_palette.getColour((unsigned char)colour)); - - int y = textHeight * topLines + 4 + ch - i; - - paint.drawLine(5 + cw - cbw, y, cw + 2, y); - - if (i == 0) { - lasty = y; - lastdb = idb; - } else if (i < ch - paint.fontMetrics().ascent() && - idb != lastdb && - ((abs(y - lasty) > textHeight && - idb % 10 == 0) || - (abs(y - lasty) > paint.fontMetrics().ascent() && - idb % 5 == 0))) { - paint.setPen(v->getBackground()); - QString text = QString("%1").arg(idb); - paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text), - y + toff + textHeight/2, text); - paint.setPen(v->getForeground()); - paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y); - lasty = y; - lastdb = idb; - } - } - paint.restore(); - } - paint.drawLine(cw + 7, 0, cw + 7, h); int bin = -1; - for (int y = 0; y < v->height(); ++y) { + for (int y = 0; y < v->getPaintHeight(); ++y) { double q0, q1; - if (!getYBinRange(v, v->height() - y, q0, q1)) continue; + if (!getYBinRange(v, v->getPaintHeight() - y, q0, q1)) continue; int vy; @@ -3393,10 +2095,10 @@ continue; } - int freq = int((sr * bin) / m_fftSize); + int freq = int((sr * bin) / getFFTSize()); if (py >= 0 && (vy - py) < textHeight - 1) { - if (m_frequencyScale == LinearFrequencyScale) { + if (m_binScale == BinScale::Linear) { paint.drawLine(w - tickw, h - vy, w, h - vy); } continue; @@ -3407,14 +2109,14 @@ paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy); if (h - vy - textHeight >= -2) { - int tx = w - 3 - paint.fontMetrics().width(text) - std::max(tickw, pkw); + int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw); paint.drawText(tx, h - vy + toff, text); } py = vy; } - if (m_frequencyScale == LogFrequencyScale) { + if (m_binScale == BinScale::Log) { // piano keyboard @@ -3426,6 +2128,152 @@ m_haveDetailedScale = detailed; } +void +SpectrogramLayer::paintDetailedScale(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const +{ + // The colour scale + + if (m_colourScale == ColourScaleType::Phase) { + paintDetailedScalePhase(v, paint, rect); + return; + } + + int h = rect.height(); + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight + paint.fontMetrics().ascent() + 2; + + int cw = getColourScaleWidth(paint); + int cbw = paint.fontMetrics().width("dB"); + + int topLines = 2; + + int ch = h - textHeight * (topLines + 1) - 8; +// paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); + paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); + + QString top, bottom; + double min = m_viewMags[v->getId()].getMin(); + double max = m_viewMags[v->getId()].getMax(); + + if (min < m_threshold) min = m_threshold; + if (max <= min) max = min + 0.1; + + double dBmin = AudioLevel::multiplier_to_dB(min); + double dBmax = AudioLevel::multiplier_to_dB(max); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "paintVerticalScale: for view id " << v->getId() + << ": min = " << min << ", max = " << max + << ", dBmin = " << dBmin << ", dBmax = " << dBmax << endl; +#endif + + if (dBmax < -60.f) dBmax = -60.f; + else top = QString("%1").arg(lrint(dBmax)); + + if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f; + bottom = QString("%1").arg(lrint(dBmin)); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "adjusted dB range to min = " << dBmin << ", max = " << dBmax + << endl; +#endif + + paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2, + 2 + textHeight + toff, "dBFS"); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), + 2 + textHeight * topLines + toff + textHeight/2, top); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), + h + toff - 3 - textHeight/2, bottom); + + paint.save(); + paint.setBrush(Qt::NoBrush); + + int lasty = 0; + int lastdb = 0; + + for (int i = 0; i < ch; ++i) { + + double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1)); + int idb = int(dBval); + + double value = AudioLevel::dB_to_multiplier(dBval); + paint.setPen(getRenderer(v)->getColour(value)); + + int y = textHeight * topLines + 4 + ch - i; + + paint.drawLine(5 + cw - cbw, y, cw + 2, y); + + if (i == 0) { + lasty = y; + lastdb = idb; + } else if (i < ch - paint.fontMetrics().ascent() && + idb != lastdb && + ((abs(y - lasty) > textHeight && + idb % 10 == 0) || + (abs(y - lasty) > paint.fontMetrics().ascent() && + idb % 5 == 0))) { + paint.setPen(v->getForeground()); + QString text = QString("%1").arg(idb); + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text), + y + toff + textHeight/2, text); + paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y); + lasty = y; + lastdb = idb; + } + } + paint.restore(); +} + +void +SpectrogramLayer::paintDetailedScalePhase(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const +{ + // The colour scale in phase mode + + int h = rect.height(); + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight + paint.fontMetrics().ascent() + 2; + + int cw = getColourScaleWidth(paint); + + // Phase is not measured in dB of course, but this places the + // scale at the same position as in the magnitude spectrogram + int cbw = paint.fontMetrics().width("dB"); + + int topLines = 1; + + int ch = h - textHeight * (topLines + 1) - 8; + paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); + + QString top = Strings::pi, bottom = Strings::minus_pi, middle = "0"; + + double min = -M_PI; + double max = M_PI; + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), + 2 + textHeight * topLines + toff + textHeight/2, top); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(middle), + 2 + textHeight * topLines + ch/2 + toff + textHeight/2, middle); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), + h + toff - 3 - textHeight/2, bottom); + + paint.save(); + paint.setBrush(Qt::NoBrush); + + for (int i = 0; i < ch; ++i) { + double val = min + (((max - min) * i) / (ch - 1)); + paint.setPen(getRenderer(v)->getColour(val)); + int y = textHeight * topLines + 4 + ch - i; + paint.drawLine(5 + cw - cbw, y, cw + 2, y); + } + paint.restore(); +} + class SpectrogramRangeMapper : public RangeMapper { public: @@ -3490,9 +2338,9 @@ sv_samplerate_t sr = m_model->getSampleRate(); - SpectrogramRangeMapper mapper(sr, m_fftSize); - -// int maxStep = mapper.getPositionForValue((double(sr) / m_fftSize) + 0.001); + SpectrogramRangeMapper mapper(sr, getFFTSize()); + +// int maxStep = mapper.getPositionForValue((double(sr) / getFFTSize()) + 0.001); int maxStep = mapper.getPositionForValue(0); int minStep = mapper.getPositionForValue(double(sr) / 2); @@ -3514,7 +2362,7 @@ double dmin, dmax; getDisplayExtents(dmin, dmax); - SpectrogramRangeMapper mapper(m_model->getSampleRate(), m_fftSize); + SpectrogramRangeMapper mapper(m_model->getSampleRate(), getFFTSize()); int n = mapper.getPositionForValue(dmax - dmin); // SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl; return n; @@ -3531,12 +2379,12 @@ // cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl; sv_samplerate_t sr = m_model->getSampleRate(); - SpectrogramRangeMapper mapper(sr, m_fftSize); + SpectrogramRangeMapper mapper(sr, getFFTSize()); double newdist = mapper.getValueForPosition(step); double newmin, newmax; - if (m_frequencyScale == LogFrequencyScale) { + if (m_binScale == BinScale::Log) { // need to pick newmin and newmax such that // @@ -3592,11 +2440,11 @@ SpectrogramLayer::getNewVerticalZoomRangeMapper() const { if (!m_model) return 0; - return new SpectrogramRangeMapper(m_model->getSampleRate(), m_fftSize); + return new SpectrogramRangeMapper(m_model->getSampleRate(), getFFTSize()); } void -SpectrogramLayer::updateMeasureRectYCoords(View *v, const MeasureRect &r) const +SpectrogramLayer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const { int y0 = 0; if (r.startY > 0.0) y0 = int(getYForFrequency(v, r.startY)); @@ -3610,7 +2458,7 @@ } void -SpectrogramLayer::setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const +SpectrogramLayer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const { if (start) { r.startY = getFrequencyForY(v, y); @@ -3648,19 +2496,35 @@ "binDisplay=\"%7\" ") .arg(m_minFrequency) .arg(m_maxFrequency) - .arg(m_colourScale) + .arg(convertFromColourScale(m_colourScale, m_colourScaleMultiple)) .arg(m_colourMap) .arg(m_colourRotation) - .arg(m_frequencyScale) - .arg(m_binDisplay); - - s += QString("normalizeColumns=\"%1\" " - "normalizeVisibleArea=\"%2\" " - "normalizeHybrid=\"%3\" ") - .arg(m_normalizeColumns ? "true" : "false") - .arg(m_normalizeVisibleArea ? "true" : "false") - .arg(m_normalizeHybrid ? "true" : "false"); - + .arg(int(m_binScale)) + .arg(int(m_binDisplay)); + + // New-style normalization attributes, allowing for more types of + // normalization in future: write out the column normalization + // type separately, and then whether we are normalizing visible + // area as well afterwards + + s += QString("columnNormalization=\"%1\" ") + .arg(m_normalization == ColumnNormalization::Max1 ? "peak" : + m_normalization == ColumnNormalization::Hybrid ? "hybrid" : "none"); + + // Old-style normalization attribute. We *don't* write out + // normalizeHybrid here because the only release that would accept + // it (Tony v1.0) has a totally different scale factor for + // it. We'll just have to accept that session files from Tony + // v2.0+ will look odd in Tony v1.0 + + s += QString("normalizeColumns=\"%1\" ") + .arg(m_normalization == ColumnNormalization::Max1 ? "true" : "false"); + + // And this applies to both old- and new-style attributes + + s += QString("normalizeVisibleArea=\"%1\" ") + .arg(m_normalizeVisibleArea ? "true" : "false"); + Layer::toXml(stream, indent, extraAttributes + " " + s); } @@ -3707,9 +2571,12 @@ setMaxFrequency(maxFrequency); } - ColourScale colourScale = (ColourScale) - attributes.value("colourScale").toInt(&ok); - if (ok) setColourScale(colourScale); + auto colourScale = convertToColourScale + (attributes.value("colourScale").toInt(&ok)); + if (ok) { + setColourScale(colourScale.first); + setColourScaleMultiple(colourScale.second); + } int colourMap = attributes.value("colourScheme").toInt(&ok); if (ok) setColourMap(colourMap); @@ -3717,24 +2584,60 @@ int colourRotation = attributes.value("colourRotation").toInt(&ok); if (ok) setColourRotation(colourRotation); - FrequencyScale frequencyScale = (FrequencyScale) + BinScale binScale = (BinScale) attributes.value("frequencyScale").toInt(&ok); - if (ok) setFrequencyScale(frequencyScale); + if (ok) setBinScale(binScale); BinDisplay binDisplay = (BinDisplay) attributes.value("binDisplay").toInt(&ok); if (ok) setBinDisplay(binDisplay); - bool normalizeColumns = - (attributes.value("normalizeColumns").trimmed() == "true"); - setNormalizeColumns(normalizeColumns); + bool haveNewStyleNormalization = false; + + QString columnNormalization = attributes.value("columnNormalization"); + + if (columnNormalization != "") { + + haveNewStyleNormalization = true; + + if (columnNormalization == "peak") { + setNormalization(ColumnNormalization::Max1); + } else if (columnNormalization == "hybrid") { + setNormalization(ColumnNormalization::Hybrid); + } else if (columnNormalization == "none") { + setNormalization(ColumnNormalization::None); + } else { + cerr << "NOTE: Unknown or unsupported columnNormalization attribute \"" + << columnNormalization << "\"" << endl; + } + } + + if (!haveNewStyleNormalization) { + + bool normalizeColumns = + (attributes.value("normalizeColumns").trimmed() == "true"); + if (normalizeColumns) { + setNormalization(ColumnNormalization::Max1); + } + + bool normalizeHybrid = + (attributes.value("normalizeHybrid").trimmed() == "true"); + if (normalizeHybrid) { + setNormalization(ColumnNormalization::Hybrid); + } + } bool normalizeVisibleArea = - (attributes.value("normalizeVisibleArea").trimmed() == "true"); + (attributes.value("normalizeVisibleArea").trimmed() == "true"); setNormalizeVisibleArea(normalizeVisibleArea); - bool normalizeHybrid = - (attributes.value("normalizeHybrid").trimmed() == "true"); - setNormalizeHybrid(normalizeHybrid); + if (!haveNewStyleNormalization && m_normalization == ColumnNormalization::Hybrid) { + // Tony v1.0 is (and hopefully will remain!) the only released + // SV-a-like to use old-style attributes when saving sessions + // that ask for hybrid normalization. It saves them with the + // wrong gain factor, so hack in a fix for that here -- this + // gives us backward but not forward compatibility. + setGain(m_gain / float(getFFTSize() / 2)); + } } diff -r e8102ff5573b -r dc2af6616c83 layer/SpectrogramLayer.h --- a/layer/SpectrogramLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/SpectrogramLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -13,11 +13,12 @@ COPYING included with this distribution for more information. */ -#ifndef _SPECTROGRAM_LAYER_H_ -#define _SPECTROGRAM_LAYER_H_ +#ifndef SPECTROGRAM_LAYER_H +#define SPECTROGRAM_LAYER_H #include "SliceableLayer.h" #include "base/Window.h" +#include "base/MagnitudeRange.h" #include "base/RealTime.h" #include "base/Thread.h" #include "base/PropertyContainer.h" @@ -25,6 +26,10 @@ #include "data/model/DenseTimeValueModel.h" #include "data/model/FFTModel.h" +#include "VerticalBinLayer.h" +#include "ColourScale.h" +#include "Colour3DPlotRenderer.h" + #include #include #include @@ -38,13 +43,12 @@ class FFTModel; class Dense3DModelPeakCache; - /** * SpectrogramLayer represents waveform data (obtained from a * DenseTimeValueModel) in spectrogram form. */ -class SpectrogramLayer : public SliceableLayer, +class SpectrogramLayer : public VerticalBinLayer, public PowerOfSqrtTwoZoomConstraint { Q_OBJECT @@ -57,23 +61,23 @@ virtual const ZoomConstraint *getZoomConstraint() const { return this; } virtual const Model *getModel() const { return m_model; } - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; virtual void setSynchronousPainting(bool synchronous); - virtual int getVerticalScaleWidth(View *v, bool detailed, QPainter &) const; - virtual void paintVerticalScale(View *v, bool detailed, QPainter &paint, QRect rect) const; + virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool detailed, QPainter &) const; + virtual void paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const; - virtual bool getCrosshairExtents(View *, QPainter &, QPoint cursorPos, + virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint cursorPos, std::vector &extents) const; - virtual void paintCrosshairs(View *, QPainter &, QPoint) const; + virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; - virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame, + virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual void measureDoubleClick(View *, QMouseEvent *); + virtual void measureDoubleClick(LayerGeometryProvider *, QMouseEvent *); virtual bool hasLightBackground() const; @@ -88,6 +92,8 @@ int *min, int *max, int *deflt) const; virtual QString getPropertyValueLabel(const PropertyName &, int value) const; + virtual QString getPropertyValueIconName(const PropertyName &, + int value) const; virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const; virtual void setProperty(const PropertyName &, int value); @@ -108,9 +114,6 @@ void setWindowType(WindowType type); WindowType getWindowType() const; - void setZeroPadLevel(int level); - int getZeroPadLevel() const; - /** * Set the gain multiplier for sample values in this view. * The default is 1.0. @@ -122,7 +125,7 @@ * Set the threshold for sample values to qualify for being shown * in the FFT, in voltage units. * - * The default is 0.0. + * The default is 10^-8 (-80dB). */ void setThreshold(float threshold); float getThreshold() const; @@ -133,64 +136,49 @@ void setMaxFrequency(int); // 0 -> no maximum int getMaxFrequency() const; - enum ColourScale { - LinearColourScale, - MeterColourScale, - dBSquaredColourScale, - dBColourScale, - PhaseColourScale - }; + /** + * Specify the scale for sample levels. See ColourScale and + * WaveformLayer for comparison and details of meter and dB + * scaling. The default is LogColourScale. + */ + void setColourScale(ColourScaleType); + ColourScaleType getColourScale() const; /** - * Specify the scale for sample levels. See WaveformLayer for - * details of meter and dB scaling. The default is dBColourScale. + * Specify multiple factor for colour scale. This is 2.0 for + * log-power spectrogram and 1.0 otherwise. */ - void setColourScale(ColourScale); - ColourScale getColourScale() const; - - enum FrequencyScale { - LinearFrequencyScale, - LogFrequencyScale - }; + void setColourScaleMultiple(double); + double getColourScaleMultiple() const; /** * Specify the scale for the y axis. */ - void setFrequencyScale(FrequencyScale); - FrequencyScale getFrequencyScale() const; + void setBinScale(BinScale); + BinScale getBinScale() const; - enum BinDisplay { - AllBins, - PeakBins, - PeakFrequencies - }; - /** * Specify the processing of frequency bins for the y axis. */ void setBinDisplay(BinDisplay); BinDisplay getBinDisplay() const; - - /** - * Normalize each column to its maximum value, independent of its - * neighbours. - */ - void setNormalizeColumns(bool n); - bool getNormalizeColumns() const; /** - * Normalize each value against the maximum in the visible region. + * Specify the normalization mode for individual columns. */ - void setNormalizeVisibleArea(bool n); + void setNormalization(ColumnNormalization); + ColumnNormalization getNormalization() const; + + /** + * Specify whether to normalize the visible area. + */ + void setNormalizeVisibleArea(bool); bool getNormalizeVisibleArea() const; /** - * Normalize each column to its maximum value, and then scale by - * the log of the (absolute) maximum value. + * Specify the colour map. See ColourMapper for the colour map + * values. */ - void setNormalizeHybrid(bool n); - bool getNormalizeHybrid() const; - void setColourMap(int map); int getColourMap() const; @@ -210,11 +198,15 @@ return ColourHasMeaningfulValue; } - double getYForFrequency(const View *v, double frequency) const; - double getFrequencyForY(const View *v, int y) const; + double getYForFrequency(const LayerGeometryProvider *v, double frequency) const; + double getFrequencyForY(const LayerGeometryProvider *v, int y) const; - virtual int getCompletion(View *v) const; - virtual QString getError(View *v) const; + //!!! VerticalBinLayer methods. Note overlap with get*BinRange() + double getYForBin(const LayerGeometryProvider *, double bin) const; + double getBinForY(const LayerGeometryProvider *, double y) const; + + virtual int getCompletion(LayerGeometryProvider *v) const; + virtual QString getError(LayerGeometryProvider *v) const; virtual bool getValueExtents(double &min, double &max, bool &logarithmic, QString &unit) const; @@ -223,16 +215,16 @@ virtual bool setDisplayExtents(double min, double max); - virtual bool getYScaleValue(const View *, int, double &, QString &) const; + virtual bool getYScaleValue(const LayerGeometryProvider *, int, double &, QString &) const; virtual void toXml(QTextStream &stream, QString indent = "", QString extraAttributes = "") const; void setProperties(const QXmlAttributes &attributes); - virtual void setLayerDormant(const View *v, bool dormant); + virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant); - virtual bool isLayerScrollable(const View *) const { return false; } + virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return false; } virtual int getVerticalZoomSteps(int &defaultStep) const; virtual int getCurrentVerticalZoomStep() const; @@ -247,118 +239,58 @@ void preferenceChanged(PropertyContainer::PropertyName name); - void fillTimerTimedOut(); - protected: const DenseTimeValueModel *m_model; // I do not own this int m_channel; - int m_windowSize; + int m_windowSize; WindowType m_windowType; - int m_windowHopLevel; - int m_zeroPadLevel; - int m_fftSize; + int m_windowHopLevel; float m_gain; float m_initialGain; float m_threshold; float m_initialThreshold; int m_colourRotation; int m_initialRotation; - int m_minFrequency; - int m_maxFrequency; - int m_initialMaxFrequency; - ColourScale m_colourScale; + int m_minFrequency; + int m_maxFrequency; + int m_initialMaxFrequency; + ColourScaleType m_colourScale; + double m_colourScaleMultiple; int m_colourMap; QColor m_crosshairColour; - FrequencyScale m_frequencyScale; + BinScale m_binScale; BinDisplay m_binDisplay; - bool m_normalizeColumns; + ColumnNormalization m_normalization; // of individual columns bool m_normalizeVisibleArea; - bool m_normalizeHybrid; int m_lastEmittedZoomStep; bool m_synchronous; mutable bool m_haveDetailedScale; - mutable int m_lastPaintBlockWidth; - mutable RealTime m_lastPaintTime; - enum { NO_VALUE = 0 }; // colour index for unused pixels + static std::pair convertToColourScale(int value); + static int convertFromColourScale(ColourScaleType type, double multiple); + static std::pair convertToColumnNorm(int value); + static int convertFromColumnNorm(ColumnNormalization norm, bool visible); - class Palette - { - public: - QColor getColour(unsigned char index) const { - return m_colours[index]; - } - - void setColour(unsigned char index, QColor colour) { - m_colours[index] = colour; - } - - private: - QColor m_colours[256]; - }; - - Palette m_palette; - - /** - * ImageCache covers the area of the view, at view resolution. - * Not all of it is necessarily valid at once (it is refreshed - * in parts when scrolling, for example). - */ - struct ImageCache - { - QImage image; - QRect validArea; - sv_frame_t startFrame; - int zoomLevel; - }; - typedef std::map ViewImageCache; - void invalidateImageCaches(); - void invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame); - mutable ViewImageCache m_imageCaches; - - /** - * When painting, we draw directly onto the draw buffer and then - * copy this to the part of the image cache that needed refreshing - * before copying the image cache onto the window. (Remind me why - * we don't draw directly onto the cache?) - */ - mutable QImage m_drawBuffer; - - mutable QTimer *m_updateTimer; - - mutable sv_frame_t m_candidateFillStartFrame; bool m_exiting; - void initialisePalette(); - void rotatePalette(int distance); - - unsigned char getDisplayValue(View *v, double input) const; - int getColourScaleWidth(QPainter &) const; - void illuminateLocalFeatures(View *v, QPainter &painter) const; + void illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &painter) const; double getEffectiveMinFrequency() const; double getEffectiveMaxFrequency() const; - // Note that the getYBin... methods return the nominal bin in the - // un-smoothed spectrogram. This is not necessarily the same bin - // as is pulled from the spectrogram and drawn at the given - // position, if the spectrogram has oversampling smoothing. Use - // getSmoothedYBinRange to obtain that. + bool getXBinRange(LayerGeometryProvider *v, int x, double &windowMin, double &windowMax) const; + bool getYBinRange(LayerGeometryProvider *v, int y, double &freqBinMin, double &freqBinMax) const; - bool getXBinRange(View *v, int x, double &windowMin, double &windowMax) const; - bool getYBinRange(View *v, int y, double &freqBinMin, double &freqBinMax) const; - bool getSmoothedYBinRange(View *v, int y, double &freqBinMin, double &freqBinMax) const; - - bool getYBinSourceRange(View *v, int y, double &freqMin, double &freqMax) const; - bool getAdjustedYBinSourceRange(View *v, int x, int y, + bool getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax) const; + bool getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y, double &freqMin, double &freqMax, double &adjFreqMin, double &adjFreqMax) const; - bool getXBinSourceRange(View *v, int x, RealTime &timeMin, RealTime &timeMax) const; - bool getXYBinSourceRange(View *v, int x, int y, double &min, double &max, + bool getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &timeMin, RealTime &timeMax) const; + bool getXYBinSourceRange(LayerGeometryProvider *v, int x, int y, double &min, double &max, double &phaseMin, double &phaseMax) const; int getWindowIncrement() const { @@ -367,84 +299,39 @@ else return m_windowSize / (1 << (m_windowHopLevel - 1)); } - int getZeroPadLevel(const View *v) const; - int getFFTSize(const View *v) const; - FFTModel *getFFTModel(const View *v) const; - Dense3DModelPeakCache *getPeakCache(const View *v) const; - void invalidateFFTModels(); + int getFFTOversampling() const; + int getFFTSize() const; // m_windowSize * getFFTOversampling() - typedef std::pair FFTFillPair; // model, last fill - typedef std::map ViewFFTMap; - typedef std::map PeakCacheMap; - mutable ViewFFTMap m_fftModels; - mutable PeakCacheMap m_peakCaches; - mutable Model *m_sliceableModel; + FFTModel *m_fftModel; + FFTModel *getFFTModel() const { return m_fftModel; } + Dense3DModelPeakCache *m_wholeCache; + Dense3DModelPeakCache *m_peakCache; + Dense3DModelPeakCache *getPeakCache() const { return m_peakCache; } + const int m_peakCacheDivisor; + bool canStoreWholeCache() const; + void recreateFFTModel(); - class MagnitudeRange { - public: - MagnitudeRange() : m_min(0), m_max(0) { } - bool operator==(const MagnitudeRange &r) { - return r.m_min == m_min && r.m_max == m_max; - } - bool isSet() const { return (m_min != 0.f || m_max != 0.f); } - void set(float min, float max) { - m_min = min; - m_max = max; - if (m_max < m_min) m_max = m_min; - } - bool sample(float f) { - bool changed = false; - if (isSet()) { - if (f < m_min) { m_min = f; changed = true; } - if (f > m_max) { m_max = f; changed = true; } - } else { - m_max = m_min = f; - changed = true; - } - return changed; - } - bool sample(const MagnitudeRange &r) { - bool changed = false; - if (isSet()) { - if (r.m_min < m_min) { m_min = r.m_min; changed = true; } - if (r.m_max > m_max) { m_max = r.m_max; changed = true; } - } else { - m_min = r.m_min; - m_max = r.m_max; - changed = true; - } - return changed; - } - float getMin() const { return m_min; } - float getMax() const { return m_max; } - private: - float m_min; - float m_max; - }; + typedef std::map ViewMagMap; // key is view id + mutable ViewMagMap m_viewMags; + mutable ViewMagMap m_lastRenderedMags; // when in normalizeVisibleArea mode + void invalidateMagnitudes(); - typedef std::map ViewMagMap; - mutable ViewMagMap m_viewMags; - mutable std::vector m_columnMags; - void invalidateMagnitudes(); - bool updateViewMagnitudes(View *v) const; - bool paintDrawBuffer(View *v, int w, int h, - const std::vector &binforx, - const std::vector &binfory, - bool usePeaksCache, - MagnitudeRange &overallMag, - bool &overallMagChanged) const; - bool paintDrawBufferPeakFrequencies(View *v, int w, int h, - const std::vector &binforx, - int minbin, - int maxbin, - double displayMinFreq, - double displayMaxFreq, - bool logarithmic, - MagnitudeRange &overallMag, - bool &overallMagChanged) const; + typedef std::map ViewRendererMap; // key is view id + mutable ViewRendererMap m_renderers; + Colour3DPlotRenderer *getRenderer(LayerGeometryProvider *) const; + void invalidateRenderers(); + + void paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; - virtual void updateMeasureRectYCoords(View *v, const MeasureRect &r) const; - virtual void setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const; + void paintDetailedScale(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const; + void paintDetailedScalePhase(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const; + + virtual void updateMeasureRectYCoords(LayerGeometryProvider *v, + const MeasureRect &r) const; + virtual void setMeasureRectYCoord(LayerGeometryProvider *v, + MeasureRect &r, bool start, int y) const; }; #endif diff -r e8102ff5573b -r dc2af6616c83 layer/SpectrumLayer.cpp --- a/layer/SpectrumLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/SpectrumLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -21,7 +21,10 @@ #include "base/Preferences.h" #include "base/RangeMapper.h" #include "base/Pitch.h" +#include "base/Strings.h" + #include "ColourMapper.h" +#include "PaintAssistant.h" #include #include @@ -112,11 +115,7 @@ m_windowType, m_windowSize, getWindowIncrement(), - m_windowSize, - false, - StorageAdviser::Criteria - (StorageAdviser::SpeedCritical | - StorageAdviser::FrequentLookupLikely)); + m_windowSize); setSliceableModel(newFFT); @@ -125,8 +124,6 @@ m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f)); } - newFFT->resume(); - m_newFFTNeeded = false; } @@ -386,18 +383,18 @@ } bool -SpectrumLayer::getXScaleValue(const View *v, int x, +SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x, double &value, QString &unit) const { if (m_xorigins.find(v) == m_xorigins.end()) return false; int xorigin = m_xorigins.find(v)->second; - value = getFrequencyForX(x - xorigin, v->width() - xorigin - 1); + value = getFrequencyForX(x - xorigin, v->getPaintWidth() - xorigin - 1); unit = "Hz"; return true; } bool -SpectrumLayer::getYScaleValue(const View *v, int y, +SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y, double &value, QString &unit) const { value = getValueForY(y, v); @@ -419,7 +416,7 @@ } bool -SpectrumLayer::getYScaleDifference(const View *v, int y0, int y1, +SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1, double &diff, QString &unit) const { bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit); @@ -429,14 +426,14 @@ bool -SpectrumLayer::getCrosshairExtents(View *v, QPainter &paint, +SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint, QPoint cursorPos, std::vector &extents) const { - QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->height() - cursorPos.y()); + QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y()); extents.push_back(vertical); - QRect horizontal(0, cursorPos.y(), v->width(), 12); + QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12); extents.push_back(horizontal); int hoffset = 2; @@ -455,14 +452,14 @@ extents.push_back(log); QRect freq(cursorPos.x(), - v->height() - paint.fontMetrics().height() - hoffset, + v->getPaintHeight() - paint.fontMetrics().height() - hoffset, paint.fontMetrics().width("123456 Hz") + 2, paint.fontMetrics().height()); extents.push_back(freq); int w(paint.fontMetrics().width("C#10+50c") + 2); QRect pitch(cursorPos.x() - w, - v->height() - paint.fontMetrics().height() - hoffset, + v->getPaintHeight() - paint.fontMetrics().height() - hoffset, w, paint.fontMetrics().height()); extents.push_back(pitch); @@ -471,7 +468,7 @@ } void -SpectrumLayer::paintCrosshairs(View *v, QPainter &paint, +SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint, QPoint cursorPos) const { if (!m_sliceableModel) return; @@ -487,29 +484,29 @@ paint.setPen(mapper.getContrastingColour()); int xorigin = m_xorigins[v]; - int w = v->width() - xorigin - 1; + int w = v->getPaintWidth() - xorigin - 1; - paint.drawLine(xorigin, cursorPos.y(), v->width(), cursorPos.y()); - paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->height()); + paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y()); + paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight()); double fundamental = getFrequencyForX(cursorPos.x() - xorigin, w); int hoffset = 2; if (m_binScale == LogBins) hoffset = 13; - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() + 2, - v->height() - 2 - hoffset, + v->getPaintHeight() - 2 - hoffset, QString("%1 Hz").arg(fundamental), - View::OutlinedText); + PaintAssistant::OutlinedText); if (Pitch::isFrequencyInMidiRange(fundamental)) { QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2, - v->height() - 2 - hoffset, + v->getPaintHeight() - 2 - hoffset, pitchLabel, - View::OutlinedText); + PaintAssistant::OutlinedText); } double value = getValueForY(cursorPos.y(), v); @@ -518,17 +515,17 @@ if (value > 0.0) db = 10.0 * log10(value); if (db < thresh) db = thresh; - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, xorigin + 2, cursorPos.y() - 2, QString("%1 V").arg(value), - View::OutlinedText); + PaintAssistant::OutlinedText); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, xorigin + 2, cursorPos.y() + 2 + paint.fontMetrics().ascent(), QString("%1 dBV").arg(db), - View::OutlinedText); + PaintAssistant::OutlinedText); int harmonic = 2; @@ -537,7 +534,7 @@ int hx = int(lrint(getXForFrequency(fundamental * harmonic, w))); hx += xorigin; - if (hx < xorigin || hx > v->width()) break; + if (hx < xorigin || hx > v->getPaintWidth()) break; int len = 7; @@ -561,7 +558,7 @@ } QString -SpectrumLayer::getFeatureDescription(View *v, QPoint &p) const +SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const { if (!m_sliceableModel) return ""; @@ -611,12 +608,12 @@ QString mindbstr; QString maxdbstr; if (mindb == AudioLevel::DB_FLOOR) { - mindbstr = tr("-Inf"); + mindbstr = Strings::minus_infinity; } else { mindbstr = QString("%1").arg(lrint(mindb)); } if (maxdb == AudioLevel::DB_FLOOR) { - maxdbstr = tr("-Inf"); + maxdbstr = Strings::minus_infinity; } else { maxdbstr = QString("%1").arg(lrint(maxdb)); } @@ -650,7 +647,7 @@ } void -SpectrumLayer::paint(View *v, QPainter &paint, QRect rect) const +SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { if (!m_originModel || !m_originModel->isOK() || !m_originModel->isReady()) { @@ -669,7 +666,7 @@ double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj int xorigin = getVerticalScaleWidth(v, false, paint) + 1; - int w = v->width() - xorigin - 1; + int w = v->getPaintWidth() - xorigin - 1; int pkh = 0; //!!! if (m_binScale == LogBins) { @@ -729,7 +726,7 @@ (void)getYForValue(values[bin], v, norm); // don't need return value, need norm paint.setPen(mapper.map(norm)); - paint.drawLine(xorigin + x, 0, xorigin + x, v->height() - pkh - 1); + paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1); } paint.restore(); @@ -749,7 +746,7 @@ // if (m_binScale == LogBins) { // int pkh = 10; - int h = v->height(); + int h = v->getPaintHeight(); // piano keyboard //!!! should be in a new paintHorizontalScale()? diff -r e8102ff5573b -r dc2af6616c83 layer/SpectrumLayer.h --- a/layer/SpectrumLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/SpectrumLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -39,13 +39,13 @@ void setModel(DenseTimeValueModel *model); virtual const Model *getModel() const { return m_originModel; } - virtual bool getCrosshairExtents(View *, QPainter &, QPoint cursorPos, + virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint cursorPos, std::vector &extents) const; - virtual void paintCrosshairs(View *, QPainter &, QPoint) const; + virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; virtual VerticalPosition getPreferredFrameCountPosition() const { return PositionTop; @@ -67,16 +67,16 @@ virtual bool getValueExtents(double &min, double &max, bool &logarithmic, QString &unit) const; - virtual bool getXScaleValue(const View *v, int x, + virtual bool getXScaleValue(const LayerGeometryProvider *v, int x, double &value, QString &unit) const; - virtual bool getYScaleValue(const View *, int y, + virtual bool getYScaleValue(const LayerGeometryProvider *, int y, double &value, QString &unit) const; - virtual bool getYScaleDifference(const View *, int y0, int y1, + virtual bool getYScaleDifference(const LayerGeometryProvider *, int y0, int y1, double &diff, QString &unit) const; - virtual bool isLayerScrollable(const View *) const { return false; } + virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return false; } void setChannel(int); int getChannel() const { return m_channel; } @@ -93,7 +93,7 @@ void setShowPeaks(bool); bool getShowPeaks() const { return m_showPeaks; } - virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; } + virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; } virtual void toXml(QTextStream &stream, QString indent = "", QString extraAttributes = "") const; diff -r e8102ff5573b -r dc2af6616c83 layer/TextLayer.cpp --- a/layer/TextLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/TextLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -102,7 +102,7 @@ } bool -TextLayer::isLayerScrollable(const View *v) const +TextLayer::isLayerScrollable(const LayerGeometryProvider *v) const { QPoint discard; return !v->shouldIlluminateLocalFeatures(this, discard); @@ -110,12 +110,12 @@ TextModel::PointList -TextLayer::getLocalPoints(View *v, int x, int y) const +TextLayer::getLocalPoints(LayerGeometryProvider *v, int x, int y) const { if (!m_model) return TextModel::PointList(); sv_frame_t frame0 = v->getFrameForX(-150); - sv_frame_t frame1 = v->getFrameForX(v->width() + 150); + sv_frame_t frame1 = v->getFrameForX(v->getPaintWidth() + 150); TextModel::PointList points(m_model->getPoints(frame0, frame1)); @@ -139,9 +139,9 @@ (QRect(0, 0, 150, 200), Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label); - if (py + rect.height() > v->height()) { - if (rect.height() > v->height()) py = 0; - else py = v->height() - rect.height() - 1; + if (py + rect.height() > v->getPaintHeight()) { + if (rect.height() > v->getPaintHeight()) py = 0; + else py = v->getPaintHeight() - rect.height() - 1; } if (x >= px && x < px + rect.width() && @@ -154,7 +154,7 @@ } bool -TextLayer::getPointToDrag(View *v, int x, int y, TextModel::Point &p) const +TextLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, TextModel::Point &p) const { if (!m_model) return false; @@ -182,7 +182,7 @@ } QString -TextLayer::getFeatureDescription(View *v, QPoint &pos) const +TextLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const { int x = pos.x(); @@ -220,7 +220,7 @@ //!!! too much overlap with TimeValueLayer/TimeInstantLayer bool -TextLayer::snapToFeatureFrame(View *v, sv_frame_t &frame, +TextLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { @@ -292,21 +292,21 @@ } int -TextLayer::getYForHeight(View *v, double height) const +TextLayer::getYForHeight(LayerGeometryProvider *v, double height) const { - int h = v->height(); + int h = v->getPaintHeight(); return h - int(height * h); } double -TextLayer::getHeightForY(View *v, int y) const +TextLayer::getHeightForY(LayerGeometryProvider *v, int y) const { - int h = v->height(); + int h = v->getPaintHeight(); return double(h - y) / h; } void -TextLayer::paint(View *v, QPainter &paint, QRect rect) const +TextLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) return; @@ -347,7 +347,7 @@ int boxMaxHeight = 200; paint.save(); - paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->height()); + paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->getPaintHeight()); for (TextModel::PointList::const_iterator i = points.begin(); i != points.end(); ++i) { @@ -380,9 +380,9 @@ QRect textRect = QRect(3, 2, boxRect.width(), boxRect.height()); boxRect = QRect(0, 0, boxRect.width() + 6, boxRect.height() + 2); - if (y + boxRect.height() > v->height()) { - if (boxRect.height() > v->height()) y = 0; - else y = v->height() - boxRect.height() - 1; + if (y + boxRect.height() > v->getPaintHeight()) { + if (boxRect.height() > v->getPaintHeight()) y = 0; + else y = v->getPaintHeight() - boxRect.height() - 1; } boxRect = QRect(x, y, boxRect.width(), boxRect.height()); @@ -411,7 +411,7 @@ } void -TextLayer::drawStart(View *v, QMouseEvent *e) +TextLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "TextLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl; @@ -437,7 +437,7 @@ } void -TextLayer::drawDrag(View *v, QMouseEvent *e) +TextLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "TextLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl; @@ -456,13 +456,13 @@ } void -TextLayer::drawEnd(View *v, QMouseEvent *) +TextLayer::drawEnd(LayerGeometryProvider *v, QMouseEvent *) { // SVDEBUG << "TextLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl; if (!m_model || !m_editing) return; bool ok = false; - QString label = QInputDialog::getText(v, tr("Enter label"), + QString label = QInputDialog::getText(v->getView(), tr("Enter label"), tr("Please enter a new label:"), QLineEdit::Normal, "", &ok); @@ -480,7 +480,7 @@ } void -TextLayer::eraseStart(View *v, QMouseEvent *e) +TextLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return; @@ -495,12 +495,12 @@ } void -TextLayer::eraseDrag(View *, QMouseEvent *) +TextLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *) { } void -TextLayer::eraseEnd(View *v, QMouseEvent *e) +TextLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model || !m_editing) return; @@ -521,7 +521,7 @@ } void -TextLayer::editStart(View *v, QMouseEvent *e) +TextLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) { // SVDEBUG << "TextLayer::editStart(" << e->x() << "," << e->y() << ")" << endl; @@ -543,7 +543,7 @@ } void -TextLayer::editDrag(View *v, QMouseEvent *e) +TextLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model || !m_editing) return; @@ -570,7 +570,7 @@ } void -TextLayer::editEnd(View *, QMouseEvent *) +TextLayer::editEnd(LayerGeometryProvider *, QMouseEvent *) { // SVDEBUG << "TextLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl; if (!m_model || !m_editing) return; @@ -598,7 +598,7 @@ } bool -TextLayer::editOpen(View *v, QMouseEvent *e) +TextLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return false; @@ -608,7 +608,7 @@ QString label = text.label; bool ok = false; - label = QInputDialog::getText(v, tr("Enter label"), + label = QInputDialog::getText(v->getView(), tr("Enter label"), tr("Please enter a new label:"), QLineEdit::Normal, label, &ok); if (ok && label != text.label) { @@ -699,7 +699,7 @@ } void -TextLayer::copy(View *v, Selection s, Clipboard &to) +TextLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) { if (!m_model) return; @@ -717,7 +717,7 @@ } bool -TextLayer::paste(View *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) +TextLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */) { if (!m_model) return false; @@ -728,7 +728,7 @@ if (clipboardHasDifferentAlignment(v, from)) { QMessageBox::StandardButton button = - QMessageBox::question(v, tr("Re-align pasted items?"), + QMessageBox::question(v->getView(), tr("Re-align pasted items?"), tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); diff -r e8102ff5573b -r dc2af6616c83 layer/TextLayer.h --- a/layer/TextLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/TextLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -32,35 +32,35 @@ public: TextLayer(); - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; - virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame, + virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual void drawStart(View *v, QMouseEvent *); - virtual void drawDrag(View *v, QMouseEvent *); - virtual void drawEnd(View *v, QMouseEvent *); + virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void eraseStart(View *v, QMouseEvent *); - virtual void eraseDrag(View *v, QMouseEvent *); - virtual void eraseEnd(View *v, QMouseEvent *); + virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void editStart(View *v, QMouseEvent *); - virtual void editDrag(View *v, QMouseEvent *); - virtual void editEnd(View *v, QMouseEvent *); + virtual void editStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *); virtual void moveSelection(Selection s, sv_frame_t newStartFrame); virtual void resizeSelection(Selection s, Selection newSize); virtual void deleteSelection(Selection s); - virtual void copy(View *v, Selection s, Clipboard &to); - virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset, + virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to); + virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool interactive); - virtual bool editOpen(View *, QMouseEvent *); // on double-click + virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *); // on double-click virtual const Model *getModel() const { return m_model; } void setModel(TextModel *model); @@ -74,16 +74,16 @@ int value) const; virtual void setProperty(const PropertyName &, int value); - virtual bool isLayerScrollable(const View *v) const; + virtual bool isLayerScrollable(const LayerGeometryProvider *v) const; virtual bool isLayerEditable() const { return true; } - virtual int getCompletion(View *) const { return m_model->getCompletion(); } + virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); } virtual bool getValueExtents(double &min, double &max, bool &logarithmic, QString &unit) const; - virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; } + virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; } virtual void toXml(QTextStream &stream, QString indent = "", QString extraAttributes = "") const; @@ -91,14 +91,14 @@ void setProperties(const QXmlAttributes &attributes); protected: - int getYForHeight(View *v, double height) const; - double getHeightForY(View *v, int y) const; + int getYForHeight(LayerGeometryProvider *v, double height) const; + double getHeightForY(LayerGeometryProvider *v, int y) const; virtual int getDefaultColourHint(bool dark, bool &impose); - TextModel::PointList getLocalPoints(View *v, int x, int y) const; + TextModel::PointList getLocalPoints(LayerGeometryProvider *v, int x, int y) const; - bool getPointToDrag(View *v, int x, int y, TextModel::Point &) const; + bool getPointToDrag(LayerGeometryProvider *v, int x, int y, TextModel::Point &) const; TextModel *m_model; bool m_editing; diff -r e8102ff5573b -r dc2af6616c83 layer/TimeInstantLayer.cpp --- a/layer/TimeInstantLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/TimeInstantLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -20,7 +20,9 @@ #include "view/View.h" #include "base/Profiler.h" #include "base/Clipboard.h" + #include "ColourDatabase.h" +#include "PaintAssistant.h" #include "data/model/SparseOneDimensionalModel.h" @@ -147,14 +149,14 @@ } bool -TimeInstantLayer::isLayerScrollable(const View *v) const +TimeInstantLayer::isLayerScrollable(const LayerGeometryProvider *v) const { QPoint discard; return !v->shouldIlluminateLocalFeatures(this, discard); } SparseOneDimensionalModel::PointList -TimeInstantLayer::getLocalPoints(View *v, int x) const +TimeInstantLayer::getLocalPoints(LayerGeometryProvider *v, int x) const { // Return a set of points that all have the same frame number, the // nearest to the given x coordinate, and that are within a @@ -213,7 +215,7 @@ } QString -TimeInstantLayer::getFeatureDescription(View *v, QPoint &pos) const +TimeInstantLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const { int x = pos.x(); @@ -249,7 +251,7 @@ } bool -TimeInstantLayer::snapToFeatureFrame(View *v, sv_frame_t &frame, +TimeInstantLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { @@ -321,7 +323,7 @@ } void -TimeInstantLayer::paint(View *v, QPainter &paint, QRect rect) const +TimeInstantLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) return; @@ -403,16 +405,16 @@ } if (p.frame == illuminateFrame) { - paint.setPen(getForegroundQColor(v)); + paint.setPen(getForegroundQColor(v->getView())); } else { paint.setPen(brushColour); } if (m_plotStyle == PlotInstants) { if (iw > 1) { - paint.drawRect(x, 0, iw - 1, v->height() - 1); + paint.drawRect(x, 0, iw - 1, v->getPaintHeight() - 1); } else { - paint.drawLine(x, 0, x, v->height() - 1); + paint.drawLine(x, 0, x, v->getPaintHeight() - 1); } } else { @@ -431,11 +433,11 @@ if (nx >= x) { if (illuminateFrame != p.frame && - (nx < x + 5 || x >= v->width() - 1)) { + (nx < x + 5 || x >= v->getPaintWidth() - 1)) { paint.setPen(Qt::NoPen); } - paint.drawRect(x, -1, nx - x, v->height() + 1); + paint.drawRect(x, -1, nx - x, v->getPaintHeight() + 1); } odd = !odd; @@ -456,7 +458,7 @@ } if (good) { - v->drawVisibleText(paint, x + iw + 2, textY, p.label, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, x + iw + 2, textY, p.label, PaintAssistant::OutlinedText); // paint.drawText(x + iw + 2, textY, p.label); } } @@ -466,7 +468,7 @@ } void -TimeInstantLayer::drawStart(View *v, QMouseEvent *e) +TimeInstantLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) { #ifdef DEBUG_TIME_INSTANT_LAYER cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << endl; @@ -489,7 +491,7 @@ } void -TimeInstantLayer::drawDrag(View *v, QMouseEvent *e) +TimeInstantLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) { #ifdef DEBUG_TIME_INSTANT_LAYER cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << endl; @@ -506,7 +508,7 @@ } void -TimeInstantLayer::drawEnd(View *, QMouseEvent *) +TimeInstantLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *) { #ifdef DEBUG_TIME_INSTANT_LAYER cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << endl; @@ -523,7 +525,7 @@ } void -TimeInstantLayer::eraseStart(View *v, QMouseEvent *e) +TimeInstantLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return; @@ -541,12 +543,12 @@ } void -TimeInstantLayer::eraseDrag(View *, QMouseEvent *) +TimeInstantLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *) { } void -TimeInstantLayer::eraseEnd(View *v, QMouseEvent *e) +TimeInstantLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model || !m_editing) return; @@ -567,7 +569,7 @@ } void -TimeInstantLayer::editStart(View *v, QMouseEvent *e) +TimeInstantLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) { #ifdef DEBUG_TIME_INSTANT_LAYER cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << endl; @@ -589,7 +591,7 @@ } void -TimeInstantLayer::editDrag(View *v, QMouseEvent *e) +TimeInstantLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) { #ifdef DEBUG_TIME_INSTANT_LAYER cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << endl; @@ -612,7 +614,7 @@ } void -TimeInstantLayer::editEnd(View *, QMouseEvent *) +TimeInstantLayer::editEnd(LayerGeometryProvider *, QMouseEvent *) { #ifdef DEBUG_TIME_INSTANT_LAYER cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << endl; @@ -631,7 +633,7 @@ } bool -TimeInstantLayer::editOpen(View *v, QMouseEvent *e) +TimeInstantLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return false; @@ -747,7 +749,7 @@ } void -TimeInstantLayer::copy(View *v, Selection s, Clipboard &to) +TimeInstantLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) { if (!m_model) return; @@ -765,7 +767,7 @@ } bool -TimeInstantLayer::paste(View *v, const Clipboard &from, sv_frame_t frameOffset, bool) +TimeInstantLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool) { if (!m_model) return false; @@ -776,7 +778,7 @@ if (clipboardHasDifferentAlignment(v, from)) { QMessageBox::StandardButton button = - QMessageBox::question(v, tr("Re-align pasted instants?"), + QMessageBox::question(v->getView(), tr("Re-align pasted instants?"), tr("The instants you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); diff -r e8102ff5573b -r dc2af6616c83 layer/TimeInstantLayer.h --- a/layer/TimeInstantLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/TimeInstantLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -33,35 +33,35 @@ TimeInstantLayer(); virtual ~TimeInstantLayer(); - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; virtual QString getLabelPreceding(sv_frame_t) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; - virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame, + virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual void drawStart(View *v, QMouseEvent *); - virtual void drawDrag(View *v, QMouseEvent *); - virtual void drawEnd(View *v, QMouseEvent *); + virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void eraseStart(View *v, QMouseEvent *); - virtual void eraseDrag(View *v, QMouseEvent *); - virtual void eraseEnd(View *v, QMouseEvent *); + virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void editStart(View *v, QMouseEvent *); - virtual void editDrag(View *v, QMouseEvent *); - virtual void editEnd(View *v, QMouseEvent *); + virtual void editStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual bool editOpen(View *, QMouseEvent *); + virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *); virtual void moveSelection(Selection s, sv_frame_t newStartFrame); virtual void resizeSelection(Selection s, Selection newSize); virtual void deleteSelection(Selection s); - virtual void copy(View *v, Selection s, Clipboard &to); - virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset, + virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to); + virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool interactive); virtual const Model *getModel() const { return m_model; } @@ -84,11 +84,11 @@ void setPlotStyle(PlotStyle style); PlotStyle getPlotStyle() const { return m_plotStyle; } - virtual bool isLayerScrollable(const View *v) const; + virtual bool isLayerScrollable(const LayerGeometryProvider *v) const; virtual bool isLayerEditable() const { return true; } - virtual int getCompletion(View *) const { return m_model->getCompletion(); } + virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); } virtual bool needsTextLabelHeight() const { return m_model->hasTextLabels(); } @@ -109,14 +109,14 @@ } } - virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; } + virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; } protected: - SparseOneDimensionalModel::PointList getLocalPoints(View *v, int) const; + SparseOneDimensionalModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const; virtual int getDefaultColourHint(bool dark, bool &impose); - bool clipboardAlignmentDiffers(View *v, const Clipboard &) const; + bool clipboardAlignmentDiffers(LayerGeometryProvider *v, const Clipboard &) const; SparseOneDimensionalModel *m_model; bool m_editing; diff -r e8102ff5573b -r dc2af6616c83 layer/TimeRulerLayer.cpp --- a/layer/TimeRulerLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/TimeRulerLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -19,19 +19,20 @@ #include "data/model/Model.h" #include "base/RealTime.h" +#include "view/View.h" + #include "ColourDatabase.h" -#include "view/View.h" +#include "PaintAssistant.h" #include #include #include +#include //#define DEBUG_TIME_RULER_LAYER 1 - - TimeRulerLayer::TimeRulerLayer() : SingleColourLayer(), m_model(0), @@ -49,7 +50,7 @@ } bool -TimeRulerLayer::snapToFeatureFrame(View *v, sv_frame_t &frame, +TimeRulerLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { if (!m_model) { @@ -141,7 +142,7 @@ } int -TimeRulerLayer::getMajorTickSpacing(View *v, bool &quarterTicks) const +TimeRulerLayer::getMajorTickSpacing(LayerGeometryProvider *v, bool &quarterTicks) const { // return value is in milliseconds @@ -158,7 +159,7 @@ RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate); RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate); - int count = v->width() / minPixelSpacing; + int count = v->getPaintWidth() / minPixelSpacing; if (count < 1) count = 1; RealTime rtGap = (rtEnd - rtStart) / count; @@ -182,6 +183,8 @@ } else { incms = 1; int ms = rtGap.msec(); +// cerr << "rtGap.msec = " << ms << ", rtGap = " << rtGap << ", count = " << count << endl; +// cerr << "startFrame = " << startFrame << ", endFrame = " << endFrame << " rtStart = " << rtStart << ", rtEnd = " << rtEnd << endl; if (ms > 0) { incms *= 10; ms /= 10; } if (ms > 0) { incms *= 10; ms /= 10; } if (ms > 0) { incms *= 5; ms /= 5; } @@ -192,7 +195,7 @@ } void -TimeRulerLayer::paint(View *v, QPainter &paint, QRect rect) const +TimeRulerLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { #ifdef DEBUG_TIME_RULER_LAYER SVDEBUG << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y() @@ -241,6 +244,9 @@ // time < 0 which would cut it in half int minlabel = 1; // ms + // used for a sanity check + sv_frame_t prevframe = 0; + while (1) { // frame is used to determine where to draw the lines, so it @@ -253,10 +259,17 @@ frame /= v->getZoomLevel(); frame *= v->getZoomLevel(); // so frame corresponds to an exact pixel + if (frame == prevframe && prevframe != 0) { + cerr << "ERROR: frame == prevframe (== " << frame + << ") in TimeRulerLayer::paint" << endl; + throw std::logic_error("frame == prevframe in TimeRulerLayer::paint"); + } + prevframe = frame; + int x = v->getXForFrame(frame); #ifdef DEBUG_TIME_RULER_LAYER - SVDEBUG << "Considering frame = " << frame << ", x = " << x << endl; + cerr << "Considering frame = " << frame << ", x = " << x << endl; #endif if (x >= rect.x() + rect.width() + 50) { @@ -287,11 +300,11 @@ } paint.setPen(greyColour); - paint.drawLine(x, 0, x, v->height()); + paint.drawLine(x, 0, x, v->getPaintHeight()); paint.setPen(getBaseQColor()); paint.drawLine(x, 0, x, 5); - paint.drawLine(x, v->height() - 6, x, v->height() - 1); + paint.drawLine(x, v->getPaintHeight() - 6, x, v->getPaintHeight() - 1); int y; switch (m_labelHeight) { @@ -300,20 +313,20 @@ y = 6 + metrics.ascent(); break; case LabelMiddle: - y = v->height() / 2 - metrics.height() / 2 + metrics.ascent(); + y = v->getPaintHeight() / 2 - metrics.height() / 2 + metrics.ascent(); break; case LabelBottom: - y = v->height() - metrics.height() + metrics.ascent() - 6; + y = v->getPaintHeight() - metrics.height() + metrics.ascent() - 6; } if (v->getViewManager() && v->getViewManager()->getOverlayMode() != ViewManager::NoOverlays) { - if (v->getLayer(0) == this) { + if (v->getView()->getLayer(0) == this) { // backmost layer, don't worry about outlining the text paint.drawText(x+2 - tw/2, y, text); } else { - v->drawVisibleText(paint, x+2 - tw/2, y, text, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, x+2 - tw/2, y, text, PaintAssistant::OutlinedText); } } } @@ -344,14 +357,14 @@ if (ticks == 10) { if ((i % 2) == 1) { if (i == 5) { - paint.drawLine(x, 0, x, v->height()); + paint.drawLine(x, 0, x, v->getPaintHeight()); } else sz = 3; } else { sz = 7; } } paint.drawLine(x, 0, x, sz); - paint.drawLine(x, v->height() - sz - 1, x, v->height() - 1); + paint.drawLine(x, v->getPaintHeight() - sz - 1, x, v->getPaintHeight() - 1); } ms += incms; diff -r e8102ff5573b -r dc2af6616c83 layer/TimeRulerLayer.h --- a/layer/TimeRulerLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/TimeRulerLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -32,7 +32,7 @@ public: TimeRulerLayer(); - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; void setModel(Model *); virtual const Model *getModel() const { return m_model; } @@ -41,7 +41,7 @@ void setLabelHeight(LabelHeight h) { m_labelHeight = h; } LabelHeight getLabelHeight() const { return m_labelHeight; } - virtual bool snapToFeatureFrame(View *, sv_frame_t &, int &, SnapType) const; + virtual bool snapToFeatureFrame(LayerGeometryProvider *, sv_frame_t &, int &, SnapType) const; virtual ColourSignificance getLayerColourSignificance() const { return ColourIrrelevant; @@ -53,7 +53,7 @@ virtual QString getLayerPresentationName() const; - virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; } + virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; } virtual void toXml(QTextStream &stream, QString indent = "", QString extraAttributes = "") const; @@ -68,7 +68,7 @@ virtual int getDefaultColourHint(bool dark, bool &impose); - int getMajorTickSpacing(View *, bool &quarterTicks) const; + int getMajorTickSpacing(LayerGeometryProvider *, bool &quarterTicks) const; }; #endif diff -r e8102ff5573b -r dc2af6616c83 layer/TimeValueLayer.cpp --- a/layer/TimeValueLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/TimeValueLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -21,7 +21,6 @@ #include "base/LogRange.h" #include "base/RangeMapper.h" #include "base/Pitch.h" -#include "ColourDatabase.h" #include "view/View.h" #include "data/model/SparseTimeValueModel.h" @@ -31,12 +30,14 @@ #include "widgets/ListInputDialog.h" #include "widgets/TextAbbrev.h" +#include "ColourDatabase.h" #include "ColourMapper.h" #include "PianoScale.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" #include "LinearColourScale.h" #include "LogColourScale.h" +#include "PaintAssistant.h" #include #include @@ -131,7 +132,7 @@ if (name == "Plot Type") return ValueProperty; if (name == "Vertical Scale") return ValueProperty; if (name == "Scale Units") return UnitsProperty; - if (name == "Colour" && m_plotStyle == PlotSegmentation) return ValueProperty; + if (name == "Colour" && m_plotStyle == PlotSegmentation) return ColourMapProperty; if (name == "Draw Segment Division Lines") return ToggleProperty; if (name == "Show Derivative") return ToggleProperty; return SingleColourLayer::getPropertyType(name); @@ -316,7 +317,7 @@ } bool -TimeValueLayer::isLayerScrollable(const View *v) const +TimeValueLayer::isLayerScrollable(const LayerGeometryProvider *v) const { // We don't illuminate sections in the line or curve modes, so // they're always scrollable @@ -530,7 +531,7 @@ } SparseTimeValueModel::PointList -TimeValueLayer::getLocalPoints(View *v, int x) const +TimeValueLayer::getLocalPoints(LayerGeometryProvider *v, int x) const { if (!m_model) return SparseTimeValueModel::PointList(); @@ -587,7 +588,7 @@ } QString -TimeValueLayer::getFeatureDescription(View *v, QPoint &pos) const +TimeValueLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const { int x = pos.x(); @@ -641,7 +642,7 @@ } bool -TimeValueLayer::snapToFeatureFrame(View *v, sv_frame_t &frame, +TimeValueLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { @@ -713,7 +714,7 @@ } bool -TimeValueLayer::snapToSimilarFeature(View *v, sv_frame_t &frame, +TimeValueLayer::snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const { @@ -794,7 +795,7 @@ } void -TimeValueLayer::getScaleExtents(View *v, double &min, double &max, bool &log) const +TimeValueLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const { min = 0.0; max = 0.0; @@ -830,11 +831,11 @@ } int -TimeValueLayer::getYForValue(View *v, double val) const +TimeValueLayer::getYForValue(LayerGeometryProvider *v, double val) const { double min = 0.0, max = 0.0; bool logarithmic = false; - int h = v->height(); + int h = v->getPaintHeight(); getScaleExtents(v, min, max, logarithmic); @@ -851,11 +852,11 @@ } double -TimeValueLayer::getValueForY(View *v, int y) const +TimeValueLayer::getValueForY(LayerGeometryProvider *v, int y) const { double min = 0.0, max = 0.0; bool logarithmic = false; - int h = v->height(); + int h = v->getPaintHeight(); getScaleExtents(v, min, max, logarithmic); @@ -877,7 +878,7 @@ } QColor -TimeValueLayer::getColourForValue(View *v, double val) const +TimeValueLayer::getColourForValue(LayerGeometryProvider *v, double val) const { double min, max; bool log; @@ -908,7 +909,7 @@ } void -TimeValueLayer::paint(View *v, QPainter &paint, QRect rect) const +TimeValueLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) return; @@ -943,8 +944,8 @@ double max = m_model->getValueMaximum(); if (max == min) max = min + 1.0; - int origin = int(nearbyint(v->height() - - (-min * v->height()) / (max - min))); + int origin = int(nearbyint(v->getPaintHeight() - + (-min * v->getPaintHeight()) / (max - min))); QPoint localPos; sv_frame_t illuminateFrame = -1; @@ -978,7 +979,7 @@ textY = v->getTextLabelHeight(this, paint); } else { int originY = getYForValue(v, 0.f); - if (originY > 0 && originY < v->height()) { + if (originY > 0 && originY < v->getPaintHeight()) { paint.save(); paint.setPen(getPartialShades(v)[1]); paint.drawLine(x0, originY, x1, originY); @@ -1183,12 +1184,12 @@ if (!illuminate) { if (!m_drawSegmentDivisions || nx < x + 5 || - x >= v->width() - 1) { + x >= v->getPaintWidth() - 1) { paint.setPen(Qt::NoPen); } } - paint.drawRect(x, -1, nx - x, v->height() + 1); + paint.drawRect(x, -1, nx - x, v->getPaintHeight() + 1); } if (v->shouldShowFeatureLabels()) { @@ -1214,10 +1215,10 @@ if (haveRoom || (!haveNext && (pointCount == 0 || !italic))) { - v->drawVisibleText(paint, x + 5, textY, label, + PaintAssistant::drawVisibleText(v, paint, x + 5, textY, label, italic ? - View::OutlinedItalicText : - View::OutlinedText); + PaintAssistant::OutlinedItalicText : + PaintAssistant::OutlinedText); } } } @@ -1231,7 +1232,7 @@ paint.drawPath(path); } else if ((m_plotStyle == PlotCurve || m_plotStyle == PlotLines) && !path.isEmpty()) { - paint.setRenderHint(QPainter::Antialiasing, pointCount <= v->width()); + paint.setRenderHint(QPainter::Antialiasing, pointCount <= v->getPaintWidth()); paint.drawPath(path); } @@ -1242,7 +1243,7 @@ } int -TimeValueLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const +TimeValueLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const { if (!m_model || shouldAutoAlign()) { return 0; @@ -1262,7 +1263,7 @@ } void -TimeValueLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const +TimeValueLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const { if (!m_model || m_model->getPoints().empty()) return; @@ -1271,7 +1272,7 @@ bool logarithmic; int w = getVerticalScaleWidth(v, false, paint); - int h = v->height(); + int h = v->getPaintHeight(); if (m_plotStyle == PlotSegmentation) { @@ -1314,7 +1315,7 @@ } void -TimeValueLayer::drawStart(View *v, QMouseEvent *e) +TimeValueLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e) { #ifdef DEBUG_TIME_VALUE_LAYER cerr << "TimeValueLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl; @@ -1364,7 +1365,7 @@ } void -TimeValueLayer::drawDrag(View *v, QMouseEvent *e) +TimeValueLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e) { #ifdef DEBUG_TIME_VALUE_LAYER cerr << "TimeValueLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl; @@ -1426,7 +1427,7 @@ } void -TimeValueLayer::drawEnd(View *, QMouseEvent *) +TimeValueLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *) { #ifdef DEBUG_TIME_VALUE_LAYER cerr << "TimeValueLayer::drawEnd" << endl; @@ -1438,7 +1439,7 @@ } void -TimeValueLayer::eraseStart(View *v, QMouseEvent *e) +TimeValueLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return; @@ -1456,12 +1457,12 @@ } void -TimeValueLayer::eraseDrag(View *, QMouseEvent *) +TimeValueLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *) { } void -TimeValueLayer::eraseEnd(View *v, QMouseEvent *e) +TimeValueLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model || !m_editing) return; @@ -1483,7 +1484,7 @@ } void -TimeValueLayer::editStart(View *v, QMouseEvent *e) +TimeValueLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e) { #ifdef DEBUG_TIME_VALUE_LAYER cerr << "TimeValueLayer::editStart(" << e->x() << "," << e->y() << ")" << endl; @@ -1506,7 +1507,7 @@ } void -TimeValueLayer::editDrag(View *v, QMouseEvent *e) +TimeValueLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e) { #ifdef DEBUG_TIME_VALUE_LAYER cerr << "TimeValueLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl; @@ -1532,7 +1533,7 @@ } void -TimeValueLayer::editEnd(View *, QMouseEvent *) +TimeValueLayer::editEnd(LayerGeometryProvider *, QMouseEvent *) { #ifdef DEBUG_TIME_VALUE_LAYER cerr << "TimeValueLayer::editEnd" << endl; @@ -1562,7 +1563,7 @@ } bool -TimeValueLayer::editOpen(View *v, QMouseEvent *e) +TimeValueLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e) { if (!m_model) return false; @@ -1685,7 +1686,7 @@ } void -TimeValueLayer::copy(View *v, Selection s, Clipboard &to) +TimeValueLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to) { if (!m_model) return; @@ -1703,7 +1704,7 @@ } bool -TimeValueLayer::paste(View *v, const Clipboard &from, sv_frame_t /* frameOffset */, +TimeValueLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool interactive) { if (!m_model) return false; @@ -1715,7 +1716,7 @@ if (clipboardHasDifferentAlignment(v, from)) { QMessageBox::StandardButton button = - QMessageBox::question(v, tr("Re-align pasted items?"), + QMessageBox::question(v->getView(), tr("Re-align pasted items?"), tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); diff -r e8102ff5573b -r dc2af6616c83 layer/TimeValueLayer.h --- a/layer/TimeValueLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/TimeValueLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -37,41 +37,41 @@ public: TimeValueLayer(); - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; - virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const; - virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const; + virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const; + virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; virtual QString getLabelPreceding(sv_frame_t) const; - virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame, + virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual bool snapToSimilarFeature(View *v, sv_frame_t &frame, + virtual bool snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap) const; - virtual void drawStart(View *v, QMouseEvent *); - virtual void drawDrag(View *v, QMouseEvent *); - virtual void drawEnd(View *v, QMouseEvent *); + virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void eraseStart(View *v, QMouseEvent *); - virtual void eraseDrag(View *v, QMouseEvent *); - virtual void eraseEnd(View *v, QMouseEvent *); + virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual void editStart(View *v, QMouseEvent *); - virtual void editDrag(View *v, QMouseEvent *); - virtual void editEnd(View *v, QMouseEvent *); + virtual void editStart(LayerGeometryProvider *v, QMouseEvent *); + virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *); + virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *); - virtual bool editOpen(View *v, QMouseEvent *); + virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *); virtual void moveSelection(Selection s, sv_frame_t newStartFrame); virtual void resizeSelection(Selection s, Selection newSize); virtual void deleteSelection(Selection s); - virtual void copy(View *v, Selection s, Clipboard &to); - virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset, + virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to); + virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool interactive); virtual const Model *getModel() const { return m_model; } @@ -120,11 +120,11 @@ void setShowDerivative(bool); bool getShowDerivative() const { return m_derivative; } - virtual bool isLayerScrollable(const View *v) const; + virtual bool isLayerScrollable(const LayerGeometryProvider *v) const; virtual bool isLayerEditable() const { return true; } - virtual int getCompletion(View *) const { return m_model->getCompletion(); } + virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); } virtual bool needsTextLabelHeight() const { return m_plotStyle == PlotSegmentation && m_model->hasTextLabels(); @@ -146,6 +146,7 @@ void setProperties(const QXmlAttributes &attributes); + /// Override from SingleColourLayer virtual ColourSignificance getLayerColourSignificance() const { if (m_plotStyle == PlotSegmentation) { return ColourHasMeaningfulValue; @@ -154,17 +155,26 @@ } } + /// Override from SingleColourLayer + virtual bool hasLightBackground() const { + if (m_plotStyle == PlotSegmentation) { + return true; + } else { + return SingleColourLayer::hasLightBackground(); + } + } + /// VerticalScaleLayer and ColourScaleLayer methods - virtual int getYForValue(View *, double value) const; - virtual double getValueForY(View *, int y) const; + virtual int getYForValue(LayerGeometryProvider *, double value) const; + virtual double getValueForY(LayerGeometryProvider *, int y) const; virtual QString getScaleUnits() const; - virtual QColor getColourForValue(View *v, double value) const; + virtual QColor getColourForValue(LayerGeometryProvider *v, double value) const; protected: - void getScaleExtents(View *, double &min, double &max, bool &log) const; + void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const; bool shouldAutoAlign() const; - SparseTimeValueModel::PointList getLocalPoints(View *v, int) const; + SparseTimeValueModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const; virtual int getDefaultColourHint(bool dark, bool &impose); diff -r e8102ff5573b -r dc2af6616c83 layer/VerticalBinLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/VerticalBinLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,62 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2016 Chris Cannam and QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef VERTICAL_BIN_LAYER_H +#define VERTICAL_BIN_LAYER_H + +#include "SliceableLayer.h" + +/** + * Interface for layers in which the Y axis corresponds to bin number + * rather than scale value. Colour3DPlotLayer is the obvious example. + * Conceptually these are always SliceableLayers as well, and this + * subclasses from SliceableLayer to avoid a big inheritance mess. + */ +class VerticalBinLayer : public SliceableLayer +{ +public: + /** + * Return the y coordinate at which the given bin "starts" + * (i.e. at the bottom of the bin, if the given bin is an integer + * and the vertical scale is the usual way up). Bin number may be + * fractional, to obtain a position part-way through a bin. + */ + virtual double getYForBin(const LayerGeometryProvider *, double bin) const = 0; + + /** + * As getYForBin, but rounding to integer values. + */ + virtual int getIYForBin(const LayerGeometryProvider *v, int bin) const { + return int(round(getYForBin(v, bin))); + } + + /** + * Return the bin number, possibly fractional, at the given y + * coordinate. Note that the whole numbers occur at the positions + * at which the bins "start" (i.e. the bottom of the visible bin, + * if the vertical scale is the usual way up). + */ + virtual double getBinForY(const LayerGeometryProvider *, double y) const = 0; + + /** + * As getBinForY, but rounding to integer values. + */ + virtual int getIBinForY(const LayerGeometryProvider *v, int y) const { + return int(floor(getBinForY(v, y))); + } +}; + +#endif + diff -r e8102ff5573b -r dc2af6616c83 layer/VerticalScaleLayer.h --- a/layer/VerticalScaleLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/VerticalScaleLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -16,11 +16,17 @@ #ifndef VERTICAL_SCALE_LAYER_H #define VERTICAL_SCALE_LAYER_H +/** + * Interface for layers in which the Y axis represents (or can + * sometimes represent, depending on the display mode) the sample + * value. For example, TimeValueLayer uses vertical scale when in + * point mode and so provides this interface. + */ class VerticalScaleLayer { public: - virtual int getYForValue(View *, double value) const = 0; - virtual double getValueForY(View *, int y) const = 0; + virtual int getYForValue(LayerGeometryProvider *, double value) const = 0; + virtual double getValueForY(LayerGeometryProvider *, int y) const = 0; virtual QString getScaleUnits() const = 0; }; diff -r e8102ff5573b -r dc2af6616c83 layer/WaveformLayer.cpp --- a/layer/WaveformLayer.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/WaveformLayer.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -19,7 +19,10 @@ #include "view/View.h" #include "base/Profiler.h" #include "base/RangeMapper.h" +#include "base/Strings.h" + #include "ColourDatabase.h" +#include "PaintAssistant.h" #include #include @@ -326,7 +329,7 @@ } int -WaveformLayer::getCompletion(View *) const +WaveformLayer::getCompletion(LayerGeometryProvider *) const { int completion = 100; if (!m_model || !m_model->isOK()) return completion; @@ -399,7 +402,7 @@ } bool -WaveformLayer::isLayerScrollable(const View *) const +WaveformLayer::isLayerScrollable(const LayerGeometryProvider *) const { return !m_autoNormalize; } @@ -408,7 +411,7 @@ -5, -3, -2, -1, -0.5, 0 }; bool -WaveformLayer::getSourceFramesForX(View *v, int x, int modelZoomLevel, +WaveformLayer::getSourceFramesForX(LayerGeometryProvider *v, int x, int modelZoomLevel, sv_frame_t &f0, sv_frame_t &f1) const { sv_frame_t viewFrame = v->getFrameForX(x); @@ -433,7 +436,7 @@ } float -WaveformLayer::getNormalizeGain(View *v, int channel) const +WaveformLayer::getNormalizeGain(LayerGeometryProvider *v, int channel) const { sv_frame_t startFrame = v->getStartFrame(); sv_frame_t endFrame = v->getEndFrame(); @@ -473,7 +476,7 @@ } void -WaveformLayer::paint(View *v, QPainter &viewPainter, QRect rect) const +WaveformLayer::paint(LayerGeometryProvider *v, QPainter &viewPainter, QRect rect) const { if (!m_model || !m_model->isOK()) { return; @@ -494,8 +497,8 @@ mergingChannels, mixingChannels); if (channels == 0) return; - int w = v->width(); - int h = v->height(); + int w = v->getPaintWidth(); + int h = v->getPaintHeight(); bool ready = m_model->isReady(); QPainter *paint; @@ -559,7 +562,7 @@ y1 = rect.bottom(); if (x0 > 0) --x0; - if (x1 < v->width()) ++x1; + if (x1 < w) ++x1; // Our zoom level may differ from that at which the underlying // model has its blocks. @@ -945,7 +948,7 @@ if (m_aggressive) { - if (ready && rect == v->rect()) { + if (ready && rect == v->getPaintRect()) { m_cacheValid = true; m_cacheZoomLevel = zoomLevel; } @@ -959,7 +962,7 @@ } QString -WaveformLayer::getFeatureDescription(View *v, QPoint &pos) const +WaveformLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const { int x = pos.x(); @@ -1042,7 +1045,7 @@ } int -WaveformLayer::getYForValue(const View *v, double value, int channel) const +WaveformLayer::getYForValue(const LayerGeometryProvider *v, double value, int channel) const { int channels = 0, minChannel = 0, maxChannel = 0; bool mergingChannels = false, mixingChannels = false; @@ -1052,7 +1055,7 @@ if (channels == 0) return 0; if (maxChannel < minChannel || channel < minChannel) return 0; - int h = v->height(); + int h = v->getPaintHeight(); int m = (h / channels) / 2; if ((m_scale == dBScale || m_scale == MeterScale) && @@ -1085,7 +1088,7 @@ } double -WaveformLayer::getValueForY(const View *v, int y, int &channel) const +WaveformLayer::getValueForY(const LayerGeometryProvider *v, int y, int &channel) const { int channels = 0, minChannel = 0, maxChannel = 0; bool mergingChannels = false, mixingChannels = false; @@ -1095,7 +1098,7 @@ if (channels == 0) return 0; if (maxChannel < minChannel) return 0; - int h = v->height(); + int h = v->getPaintHeight(); int m = (h / channels) / 2; if ((m_scale == dBScale || m_scale == MeterScale) && @@ -1131,7 +1134,7 @@ } bool -WaveformLayer::getYScaleValue(const View *v, int y, +WaveformLayer::getYScaleValue(const LayerGeometryProvider *v, int y, double &value, QString &unit) const { int channel; @@ -1157,7 +1160,7 @@ } bool -WaveformLayer::getYScaleDifference(const View *v, int y0, int y1, +WaveformLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1, double &diff, QString &unit) const { int c0, c1; @@ -1195,18 +1198,18 @@ } int -WaveformLayer::getVerticalScaleWidth(View *, bool, QPainter &paint) const +WaveformLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const { if (m_scale == LinearScale) { return paint.fontMetrics().width("0.0") + 13; } else { return std::max(paint.fontMetrics().width(tr("0dB")), - paint.fontMetrics().width(tr("-Inf"))) + 13; + paint.fontMetrics().width(Strings::minus_infinity)) + 13; } } void -WaveformLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const +WaveformLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) { return; @@ -1255,7 +1258,7 @@ text = QString("%1").arg(meterdbs[i]); if (i == n) text = tr("0dB"); if (i == 0) { - text = tr("-Inf"); + text = Strings::minus_infinity; val = 0.0; } break; @@ -1265,7 +1268,7 @@ text = QString("%1").arg(-(10*n) + i * 10); if (i == n) text = tr("0dB"); if (i == 0) { - text = tr("-Inf"); + text = Strings::minus_infinity; val = 0.0; } break; diff -r e8102ff5573b -r dc2af6616c83 layer/WaveformLayer.h --- a/layer/WaveformLayer.h Fri Mar 04 12:23:31 2016 +0000 +++ b/layer/WaveformLayer.h Fri Jan 13 10:29:50 2017 +0000 @@ -38,16 +38,16 @@ return m_model ? m_model->getZoomConstraint() : 0; } virtual const Model *getModel() const { return m_model; } - virtual void paint(View *v, QPainter &paint, QRect rect) const; + virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; - virtual QString getFeatureDescription(View *v, QPoint &) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const; virtual ColourSignificance getLayerColourSignificance() const { return ColourAndBackgroundSignificant; } - virtual int getVerticalScaleWidth(View *v, bool detailed, QPainter &) const; - virtual void paintVerticalScale(View *v, bool detailed, QPainter &paint, QRect rect) const; + virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool detailed, QPainter &) const; + virtual void paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const; void setModel(const RangeSummarisableTimeValueModel *model); @@ -180,17 +180,17 @@ void setAggressiveCacheing(bool); bool getAggressiveCacheing() const { return m_aggressive; } - virtual bool isLayerScrollable(const View *) const; + virtual bool isLayerScrollable(const LayerGeometryProvider *) const; - virtual int getCompletion(View *) const; + virtual int getCompletion(LayerGeometryProvider *) const; virtual bool getValueExtents(double &min, double &max, bool &log, QString &unit) const; - virtual bool getYScaleValue(const View *v, int y, + virtual bool getYScaleValue(const LayerGeometryProvider *v, int y, double &value, QString &unit) const; - virtual bool getYScaleDifference(const View *v, int y0, int y1, + virtual bool getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1, double &diff, QString &unit) const; virtual void toXml(QTextStream &stream, QString indent = "", @@ -213,14 +213,14 @@ int getChannelArrangement(int &min, int &max, bool &merging, bool &mixing) const; - int getYForValue(const View *v, double value, int channel) const; + int getYForValue(const LayerGeometryProvider *v, double value, int channel) const; - double getValueForY(const View *v, int y, int &channel) const; + double getValueForY(const LayerGeometryProvider *v, int y, int &channel) const; - bool getSourceFramesForX(View *v, int x, int modelZoomLevel, + bool getSourceFramesForX(LayerGeometryProvider *v, int x, int modelZoomLevel, sv_frame_t &f0, sv_frame_t &f1) const; - float getNormalizeGain(View *v, int channel) const; + float getNormalizeGain(LayerGeometryProvider *v, int channel) const; virtual void flagBaseColourChanged() { m_cacheValid = false; } diff -r e8102ff5573b -r dc2af6616c83 svgui.pro --- a/svgui.pro Fri Mar 04 12:23:31 2016 +0000 +++ b/svgui.pro Fri Jan 13 10:29:50 2017 +0000 @@ -1,32 +1,14 @@ TEMPLATE = lib +INCLUDEPATH += ../vamp-plugin-sdk + exists(config.pri) { include(config.pri) } -!exists(config.pri) { - - CONFIG += release - DEFINES += NDEBUG BUILD_RELEASE NO_TIMING - - win32-g++ { - INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include - LIBS += -L../sv-dependency-builds/win32-mingw/lib - } - win32-msvc* { - INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include - LIBS += -L../sv-dependency-builds/win32-msvc/lib - } - macx* { - INCLUDEPATH += ../sv-dependency-builds/osx/include - LIBS += -L../sv-dependency-builds/osx/lib - } - - DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_RUBBERBAND HAVE_LIBLO HAVE_MAD HAVE_ID3TAG -} CONFIG += staticlib qt thread warn_on stl rtti exceptions c++11 -QT += network xml gui widgets +QT += network xml gui widgets svg TARGET = svgui @@ -35,149 +17,7 @@ OBJECTS_DIR = o MOC_DIR = o -HEADERS += layer/Colour3DPlotLayer.h \ - layer/ColourDatabase.h \ - layer/ColourMapper.h \ - layer/ColourScaleLayer.h \ - layer/FlexiNoteLayer.h \ - layer/ImageLayer.h \ - layer/ImageRegionFinder.h \ - layer/Layer.h \ - layer/LayerFactory.h \ - layer/LinearNumericalScale.h \ - layer/LogNumericalScale.h \ - layer/LinearColourScale.h \ - layer/LogColourScale.h \ - layer/NoteLayer.h \ - layer/PaintAssistant.h \ - layer/PianoScale.h \ - layer/RegionLayer.h \ - layer/SingleColourLayer.h \ - layer/SliceableLayer.h \ - layer/SliceLayer.h \ - layer/SpectrogramLayer.h \ - layer/SpectrumLayer.h \ - layer/TextLayer.h \ - layer/TimeInstantLayer.h \ - layer/TimeRulerLayer.h \ - layer/TimeValueLayer.h \ - layer/VerticalScaleLayer.h \ - layer/WaveformLayer.h -SOURCES += layer/Colour3DPlotLayer.cpp \ - layer/ColourDatabase.cpp \ - layer/ColourMapper.cpp \ - layer/FlexiNoteLayer.cpp \ - layer/ImageLayer.cpp \ - layer/ImageRegionFinder.cpp \ - layer/Layer.cpp \ - layer/LayerFactory.cpp \ - layer/LinearNumericalScale.cpp \ - layer/LogNumericalScale.cpp \ - layer/LinearColourScale.cpp \ - layer/LogColourScale.cpp \ - layer/NoteLayer.cpp \ - layer/PaintAssistant.cpp \ - layer/PianoScale.cpp \ - layer/RegionLayer.cpp \ - layer/SingleColourLayer.cpp \ - layer/SliceLayer.cpp \ - layer/SpectrogramLayer.cpp \ - layer/SpectrumLayer.cpp \ - layer/TextLayer.cpp \ - layer/TimeInstantLayer.cpp \ - layer/TimeRulerLayer.cpp \ - layer/TimeValueLayer.cpp \ - layer/WaveformLayer.cpp +include(files.pri) -HEADERS += view/Overview.h \ - view/Pane.h \ - view/PaneStack.h \ - view/View.h \ - view/ViewManager.h -SOURCES += view/Overview.cpp \ - view/Pane.cpp \ - view/PaneStack.cpp \ - view/View.cpp \ - view/ViewManager.cpp - -HEADERS += widgets/ActivityLog.h \ - widgets/AudioDial.h \ - widgets/ClickableLabel.h \ - widgets/ColourNameDialog.h \ - widgets/CommandHistory.h \ - widgets/CSVFormatDialog.h \ - widgets/Fader.h \ - widgets/InteractiveFileFinder.h \ - widgets/IconLoader.h \ - widgets/ImageDialog.h \ - widgets/ItemEditDialog.h \ - widgets/KeyReference.h \ - widgets/LabelCounterInputDialog.h \ - widgets/LayerTree.h \ - widgets/LayerTreeDialog.h \ - widgets/LEDButton.h \ - widgets/LevelPanToolButton.h \ - widgets/LevelPanWidget.h \ - widgets/ListInputDialog.h \ - widgets/MIDIFileImportDialog.h \ - widgets/ModelDataTableDialog.h \ - widgets/NotifyingCheckBox.h \ - widgets/NotifyingComboBox.h \ - widgets/NotifyingPushButton.h \ - widgets/NotifyingTabBar.h \ - widgets/Panner.h \ - widgets/PluginParameterBox.h \ - widgets/PluginParameterDialog.h \ - widgets/ProgressDialog.h \ - widgets/PropertyBox.h \ - widgets/PropertyStack.h \ - widgets/RangeInputDialog.h \ - widgets/SelectableLabel.h \ - widgets/SubdividingMenu.h \ - widgets/TextAbbrev.h \ - widgets/Thumbwheel.h \ - widgets/TipDialog.h \ - widgets/TransformFinder.h \ - widgets/UnitConverter.h \ - widgets/WindowShapePreview.h \ - widgets/WindowTypeSelector.h -SOURCES += widgets/ActivityLog.cpp \ - widgets/AudioDial.cpp \ - widgets/ColourNameDialog.cpp \ - widgets/CommandHistory.cpp \ - widgets/CSVFormatDialog.cpp \ - widgets/Fader.cpp \ - widgets/InteractiveFileFinder.cpp \ - widgets/IconLoader.cpp \ - widgets/ImageDialog.cpp \ - widgets/ItemEditDialog.cpp \ - widgets/KeyReference.cpp \ - widgets/LabelCounterInputDialog.cpp \ - widgets/LayerTree.cpp \ - widgets/LayerTreeDialog.cpp \ - widgets/LEDButton.cpp \ - widgets/LevelPanToolButton.cpp \ - widgets/LevelPanWidget.cpp \ - widgets/ListInputDialog.cpp \ - widgets/MIDIFileImportDialog.cpp \ - widgets/ModelDataTableDialog.cpp \ - widgets/NotifyingCheckBox.cpp \ - widgets/NotifyingComboBox.cpp \ - widgets/NotifyingPushButton.cpp \ - widgets/NotifyingTabBar.cpp \ - widgets/Panner.cpp \ - widgets/PluginParameterBox.cpp \ - widgets/PluginParameterDialog.cpp \ - widgets/ProgressDialog.cpp \ - widgets/PropertyBox.cpp \ - widgets/PropertyStack.cpp \ - widgets/RangeInputDialog.cpp \ - widgets/SelectableLabel.cpp \ - widgets/SubdividingMenu.cpp \ - widgets/TextAbbrev.cpp \ - widgets/Thumbwheel.cpp \ - widgets/TipDialog.cpp \ - widgets/TransformFinder.cpp \ - widgets/UnitConverter.cpp \ - widgets/WindowShapePreview.cpp \ - widgets/WindowTypeSelector.cpp +HEADERS = $$(SVGUI_HEADERS) +SOURCES = $$(SVGUI_SOURCES) diff -r e8102ff5573b -r dc2af6616c83 view/AlignmentView.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/AlignmentView.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,200 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2014 Chris Cannam and QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "AlignmentView.h" + +#include + +#include "data/model/SparseOneDimensionalModel.h" + +#include "layer/TimeInstantLayer.h" + +using std::vector; + +AlignmentView::AlignmentView(QWidget *w) : + View(w, false), + m_above(0), + m_below(0) +{ + setObjectName(tr("AlignmentView")); +} + +void +AlignmentView::globalCentreFrameChanged(sv_frame_t f) +{ + View::globalCentreFrameChanged(f); + update(); +} + +void +AlignmentView::viewCentreFrameChanged(View *v, sv_frame_t f) +{ + View::viewCentreFrameChanged(v, f); + if (v == m_above) { + m_centreFrame = f; + update(); + } else if (v == m_below) { + update(); + } +} + +void +AlignmentView::viewManagerPlaybackFrameChanged(sv_frame_t) +{ + update(); +} + +void +AlignmentView::viewAboveZoomLevelChanged(int level, bool) +{ + m_zoomLevel = level; + update(); +} + +void +AlignmentView::viewBelowZoomLevelChanged(int, bool) +{ + update(); +} + +void +AlignmentView::setViewAbove(View *v) +{ + if (m_above) { + disconnect(m_above, 0, this, 0); + } + + m_above = v; + + if (m_above) { + connect(m_above, + SIGNAL(zoomLevelChanged(int, bool)), + this, + SLOT(viewAboveZoomLevelChanged(int, bool))); + } +} + +void +AlignmentView::setViewBelow(View *v) +{ + if (m_below) { + disconnect(m_below, 0, this, 0); + } + + m_below = v; + + if (m_below) { + connect(m_below, + SIGNAL(zoomLevelChanged(int, bool)), + this, + SLOT(viewBelowZoomLevelChanged(int, bool))); + } +} + +void +AlignmentView::paintEvent(QPaintEvent *) +{ + if (m_above == 0 || m_below == 0 || !m_manager) return; + + bool darkPalette = false; + if (m_manager) darkPalette = m_manager->getGlobalDarkBackground(); + + QColor fg, bg; + if (darkPalette) { + fg = Qt::gray; + bg = Qt::black; + } else { + fg = Qt::black; + bg = Qt::gray; + } + + QPainter paint(this); + paint.setPen(QPen(fg, 2)); + paint.setBrush(Qt::NoBrush); + paint.setRenderHint(QPainter::Antialiasing, true); + + paint.fillRect(rect(), bg); + + vector keyFrames = getKeyFrames(); + + foreach (sv_frame_t f, keyFrames) { + int ax = m_above->getXForFrame(f); + sv_frame_t rf = m_above->alignToReference(f); + sv_frame_t bf = m_below->alignFromReference(rf); + int bx = m_below->getXForFrame(bf); + paint.drawLine(ax, 0, bx, height()); + } + + paint.end(); +} + +vector +AlignmentView::getKeyFrames() +{ + if (!m_above) { + return getDefaultKeyFrames(); + } + + SparseOneDimensionalModel *m = 0; + + // get the topmost such + for (int i = 0; i < m_above->getLayerCount(); ++i) { + if (qobject_cast(m_above->getLayer(i))) { + SparseOneDimensionalModel *mm = + qobject_cast + (m_above->getLayer(i)->getModel()); + if (mm) m = mm; + } + } + + if (!m) { + return getDefaultKeyFrames(); + } + + vector keyFrames; + + const SparseOneDimensionalModel::PointList pp = m->getPoints(); + for (SparseOneDimensionalModel::PointList::const_iterator pi = pp.begin(); + pi != pp.end(); ++pi) { + keyFrames.push_back(pi->frame); + } + + return keyFrames; +} + +vector +AlignmentView::getDefaultKeyFrames() +{ + vector keyFrames; + + if (!m_above || !m_manager) return keyFrames; + + sv_samplerate_t rate = m_manager->getMainModelSampleRate(); + if (rate == 0) return keyFrames; + + for (sv_frame_t f = m_above->getModelsStartFrame(); + f <= m_above->getModelsEndFrame(); + f += sv_frame_t(rate * 5 + 0.5)) { + keyFrames.push_back(f); + } + + return keyFrames; +} + + + + + + diff -r e8102ff5573b -r dc2af6616c83 view/AlignmentView.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/AlignmentView.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,50 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2014 Chris Cannam and QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef ALIGNMENT_VIEW_H +#define ALIGNMENT_VIEW_H + +#include "View.h" + +class AlignmentView : public View +{ + Q_OBJECT + +public: + AlignmentView(QWidget *parent = 0); + virtual QString getPropertyContainerIconName() const { return "alignment"; } + + void setViewAbove(View *view); + void setViewBelow(View *view); + +public slots: + virtual void globalCentreFrameChanged(sv_frame_t); + virtual void viewCentreFrameChanged(View *, sv_frame_t); + virtual void viewAboveZoomLevelChanged(int, bool); + virtual void viewBelowZoomLevelChanged(int, bool); + virtual void viewManagerPlaybackFrameChanged(sv_frame_t); + +protected: + virtual void paintEvent(QPaintEvent *e); + virtual bool shouldLabelSelections() const { return false; } + + std::vector getKeyFrames(); + std::vector getDefaultKeyFrames(); + + View *m_above; + View *m_below; +}; + +#endif diff -r e8102ff5573b -r dc2af6616c83 view/Overview.cpp --- a/view/Overview.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/view/Overview.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -211,8 +211,6 @@ paint.setClipRegion(e->region()); paint.setRenderHints(QPainter::Antialiasing); - QRect r(rect()); - // We paint a rounded rect for each distinct set of view extents, // and we colour in the inside and outside of the rect that // corresponds to the current view. (One small caveat -- we don't @@ -245,7 +243,6 @@ int x0 = getXForFrame(f0); int x1 = getXForFrame(f1); - if (x1 <= x0) x1 = x0 + 1; std::pair extent(x0, x1); diff -r e8102ff5573b -r dc2af6616c83 view/Pane.cpp --- a/view/Pane.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/view/Pane.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -22,9 +22,11 @@ #include "ViewManager.h" #include "widgets/CommandHistory.h" #include "widgets/TextAbbrev.h" +#include "widgets/IconLoader.h" #include "base/Preferences.h" #include "layer/WaveformLayer.h" #include "layer/TimeRulerLayer.h" +#include "layer/PaintAssistant.h" // GF: added so we can propagate the mouse move event to the note layer for context handling. #include "layer/LayerFactory.h" @@ -141,8 +143,8 @@ m_hthumb->setObjectName(tr("Horizontal Zoom")); m_hthumb->setCursor(Qt::ArrowCursor); layout->addWidget(m_hthumb, 1, 0, 1, 2); - m_hthumb->setFixedWidth(70); - m_hthumb->setFixedHeight(16); + m_hthumb->setFixedWidth(m_manager->scalePixelSize(70)); + m_hthumb->setFixedHeight(m_manager->scalePixelSize(16)); m_hthumb->setDefaultValue(0); m_hthumb->setSpeed(0.6f); connect(m_hthumb, SIGNAL(valueChanged(int)), this, @@ -153,8 +155,8 @@ m_vpan = new Panner; m_vpan->setCursor(Qt::ArrowCursor); layout->addWidget(m_vpan, 0, 1); - m_vpan->setFixedWidth(12); - m_vpan->setFixedHeight(70); + m_vpan->setFixedWidth(m_manager->scalePixelSize(12)); + m_vpan->setFixedHeight(m_manager->scalePixelSize(70)); m_vpan->setAlpha(80, 130); connect(m_vpan, SIGNAL(rectExtentsChanged(float, float, float, float)), this, SLOT(verticalPannerMoved(float, float, float, float))); @@ -167,8 +169,8 @@ m_vthumb->setObjectName(tr("Vertical Zoom")); m_vthumb->setCursor(Qt::ArrowCursor); layout->addWidget(m_vthumb, 0, 2); - m_vthumb->setFixedWidth(16); - m_vthumb->setFixedHeight(70); + m_vthumb->setFixedWidth(m_manager->scalePixelSize(16)); + m_vthumb->setFixedHeight(m_manager->scalePixelSize(70)); connect(m_vthumb, SIGNAL(valueChanged(int)), this, SLOT(verticalThumbwheelMoved(int))); connect(m_vthumb, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); @@ -182,9 +184,9 @@ m_reset = new NotifyingPushButton; m_reset->setFlat(true); m_reset->setCursor(Qt::ArrowCursor); - m_reset->setFixedHeight(16); - m_reset->setFixedWidth(16); - m_reset->setIcon(QPixmap(":/icons/zoom-reset.png")); + m_reset->setFixedHeight(m_manager->scalePixelSize(16)); + m_reset->setFixedWidth(m_manager->scalePixelSize(16)); + m_reset->setIcon(IconLoader().load("zoom-reset")); m_reset->setToolTip(tr("Reset zoom to default")); layout->addWidget(m_reset, 1, 2); @@ -283,16 +285,19 @@ updateVerticalPanner(); if (m_manager && m_manager->getZoomWheelsEnabled() && - width() > 120 && height() > 100) { + width() > m_manager->scalePixelSize(120) && + height() > m_manager->scalePixelSize(100)) { if (!m_headsUpDisplay->isVisible()) { m_headsUpDisplay->show(); } + int shift = m_manager->scalePixelSize(86); if (haveVThumb) { m_headsUpDisplay->setFixedHeight(m_vthumb->height() + m_hthumb->height()); - m_headsUpDisplay->move(width() - 86, height() - 86); + m_headsUpDisplay->move(width() - shift, height() - shift); } else { m_headsUpDisplay->setFixedHeight(m_hthumb->height()); - m_headsUpDisplay->move(width() - 86, height() - 16); + m_headsUpDisplay->move(width() - shift, + height() - m_manager->scalePixelSize(16)); } } else { m_headsUpDisplay->hide(); @@ -717,6 +722,10 @@ void Pane::drawCentreLine(sv_samplerate_t sampleRate, QPainter &paint, bool omitLine) { + if (omitLine && m_manager->getMainModelSampleRate() == 0) { + return; + } + int fontHeight = paint.fontMetrics().height(); int fontAscent = paint.fontMetrics().ascent(); @@ -773,14 +782,14 @@ int tw = paint.fontMetrics().width(text); int x = width()/2 - 4 - tw; - drawVisibleText(paint, x, y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, x, y, text, PaintAssistant::OutlinedText); } QString text = QString("%1").arg(m_centreFrame); int x = width()/2 + 4; - drawVisibleText(paint, x, y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, x, y, text, PaintAssistant::OutlinedText); } } @@ -862,8 +871,8 @@ return; } - drawVisibleText(paint, m_scaleWidth + 5, - paint.fontMetrics().ascent() + y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, m_scaleWidth + 5, + paint.fontMetrics().ascent() + y, text, PaintAssistant::OutlinedText); paint.restore(); } @@ -901,8 +910,8 @@ return; } - drawVisibleText(paint, m_scaleWidth + 5, - paint.fontMetrics().ascent() + y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, m_scaleWidth + 5, + paint.fontMetrics().ascent() + y, text, PaintAssistant::OutlinedText); paint.restore(); } @@ -915,7 +924,7 @@ int lly = height() - 6; if (m_manager->getZoomWheelsEnabled()) { - lly -= 20; + lly -= m_manager->scalePixelSize(20); } if (r.y() + r.height() < lly - int(m_layerStack.size()) * fontHeight) { @@ -937,7 +946,7 @@ int llx = width() - maxTextWidth - 5; if (m_manager->getZoomWheelsEnabled()) { - llx -= 36; + llx -= m_manager->scalePixelSize(36); } if (r.x() + r.width() >= llx - fontAscent - 3) { @@ -950,9 +959,9 @@ paint.setPen(getForeground()); } - drawVisibleText(paint, llx, + PaintAssistant::drawVisibleText(this, paint, llx, lly - fontHeight + fontAscent, - texts[i], OutlinedText); + texts[i], PaintAssistant::OutlinedText); if (!pixmaps[i].isNull()) { paint.drawPixmap(llx - fontAscent - 3, @@ -1014,10 +1023,10 @@ offsetText = tr("+%1").arg(offsetText); } } - drawVisibleText(paint, p0 + 2, fontAscent + fontHeight + 4, startText, OutlinedText); - drawVisibleText(paint, p1 + 2, fontAscent + fontHeight + 4, endText, OutlinedText); - drawVisibleText(paint, p0 + 2, fontAscent + fontHeight*2 + 4, offsetText, OutlinedText); - drawVisibleText(paint, p1 + 2, fontAscent + fontHeight*2 + 4, offsetText, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p0 + 2, fontAscent + fontHeight + 4, startText, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p1 + 2, fontAscent + fontHeight + 4, endText, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p0 + 2, fontAscent + fontHeight*2 + 4, offsetText, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p1 + 2, fontAscent + fontHeight*2 + 4, offsetText, PaintAssistant::OutlinedText); //!!! duplicating display policy with View::drawSelections @@ -1048,23 +1057,19 @@ sv_samplerate_t modelRate = waveformModel->getSampleRate(); sv_samplerate_t nativeRate = waveformModel->getNativeRate(); sv_samplerate_t playbackRate = m_manager->getPlaybackSampleRate(); - sv_samplerate_t outputRate = m_manager->getOutputSampleRate(); QString srNote = ""; - // Show (R) for waveform models that have been resampled or will - // be resampled on playback, and (X) for waveform models that will - // be played at the wrong rate because their rate differs from the - // current playback rate (which is not necessarily that of the - // main model). - - if (playbackRate != 0) { - if (modelRate == playbackRate) { - if (modelRate != outputRate || modelRate != nativeRate) { - srNote = " " + tr("(R)"); - } - } else { + // Show (R) for waveform models that have been resampled during + // load, and (X) for waveform models that will be played at the + // wrong rate because their rate differs from the current playback + // rate (which is not necessarily that of the main model). + + if (modelRate != nativeRate) { + if (playbackRate != 0 && modelRate != playbackRate) { srNote = " " + tr("(X)"); + } else { + srNote = " " + tr("(R)"); } } @@ -1080,9 +1085,9 @@ if (x < pbw + 5) x = pbw + 5; if (r.x() < x + paint.fontMetrics().width(desc)) { - drawVisibleText(paint, x, + PaintAssistant::drawVisibleText(this, paint, x, height() - fontHeight + fontAscent - 6, - desc, OutlinedText); + desc, PaintAssistant::OutlinedText); } } @@ -1118,7 +1123,7 @@ } QImage * -Pane::toNewImage(sv_frame_t f0, sv_frame_t f1) +Pane::renderPartToNewImage(sv_frame_t f0, sv_frame_t f1) { int x0 = int(f0 / getZoomLevel()); int x1 = int(f1 / getZoomLevel()); @@ -1157,9 +1162,9 @@ } QSize -Pane::getImageSize(sv_frame_t f0, sv_frame_t f1) +Pane::getRenderedPartImageSize(sv_frame_t f0, sv_frame_t f1) { - QSize s = View::getImageSize(f0, f1); + QSize s = View::getRenderedPartImageSize(f0, f1); QImage *image = new QImage(100, 100, QImage::Format_RGB32); QPainter paint(image); @@ -1943,7 +1948,7 @@ true, // can move horiz canTopLayerMoveVertical(), // can move vert canTopLayerMoveVertical() || (m_manager && m_manager->isPlaying()), // resist horiz - !(m_manager && m_manager->isPlaying())); // resist vert + true); // resist vert if (m_dragMode == HorizontalDrag || m_dragMode == FreeDrag) { @@ -2264,8 +2269,10 @@ void Pane::wheelEvent(QWheelEvent *e) { - cerr << "wheelEvent, delta " << e->delta() << ", angleDelta " << e->angleDelta().x() << "," << e->angleDelta().y() << ", pixelDelta " << e->pixelDelta().x() << "," << e->pixelDelta().y() << ", modifiers " << e->modifiers() << endl; - +// cerr << "wheelEvent, delta " << e->delta() << ", angleDelta " << e->angleDelta().x() << "," << e->angleDelta().y() << ", pixelDelta " << e->pixelDelta().x() << "," << e->pixelDelta().y() << ", modifiers " << e->modifiers() << endl; + + e->accept(); // we never want wheel events on the pane to be propagated + int dx = e->angleDelta().x(); int dy = e->angleDelta().y(); @@ -2285,7 +2292,7 @@ } if (e->phase() == Qt::ScrollBegin || - fabs(d) >= 120 || + std::abs(d) >= 120 || (d > 0 && m_pendingWheelAngle < 0) || (d < 0 && m_pendingWheelAngle > 0)) { m_pendingWheelAngle = d; @@ -2312,7 +2319,7 @@ m_pendingWheelAngle = 0; return; } - + while (abs(m_pendingWheelAngle) >= 120) { int sign = (m_pendingWheelAngle < 0 ? -1 : 1); @@ -2331,7 +2338,7 @@ void Pane::wheelVertical(int sign, Qt::KeyboardModifiers mods) { - cerr << "wheelVertical: sign = " << sign << endl; +// cerr << "wheelVertical: sign = " << sign << endl; if (mods & Qt::ShiftModifier) { @@ -2378,7 +2385,7 @@ void Pane::wheelHorizontal(int sign, Qt::KeyboardModifiers mods) { - cerr << "wheelHorizontal: sign = " << sign << endl; +// cerr << "wheelHorizontal: sign = " << sign << endl; // Scroll left or right, rapidly @@ -2388,7 +2395,7 @@ void Pane::wheelHorizontalFine(int pixels, Qt::KeyboardModifiers) { - cerr << "wheelHorizontalFine: pixels = " << pixels << endl; +// cerr << "wheelHorizontalFine: pixels = " << pixels << endl; // Scroll left or right by a fixed number of pixels diff -r e8102ff5573b -r dc2af6616c83 view/Pane.h --- a/view/Pane.h Fri Mar 04 12:23:31 2016 +0000 +++ b/view/Pane.h Fri Jan 13 10:29:50 2017 +0000 @@ -37,28 +37,35 @@ public: Pane(QWidget *parent = 0); - virtual QString getPropertyContainerIconName() const { return "pane"; } + virtual QString getPropertyContainerIconName() const override { return "pane"; } virtual bool shouldIlluminateLocalFeatures(const Layer *layer, - QPoint &pos) const; + QPoint &pos) const override; virtual bool shouldIlluminateLocalSelection(QPoint &pos, bool &closeToLeft, - bool &closeToRight) const; + bool &closeToRight) const override; void setCentreLineVisible(bool visible); bool getCentreLineVisible() const { return m_centreLineVisible; } - virtual sv_frame_t getFirstVisibleFrame() const; + virtual sv_frame_t getFirstVisibleFrame() const override; - virtual int getVerticalScaleWidth() const; + int getVerticalScaleWidth() const; - virtual QImage *toNewImage(sv_frame_t f0, sv_frame_t f1); - virtual QImage *toNewImage() { return View::toNewImage(); } - virtual QSize getImageSize(sv_frame_t f0, sv_frame_t f1); - virtual QSize getImageSize() { return View::getImageSize(); } + virtual QImage *renderToNewImage() override { + return View::renderToNewImage(); + } + + virtual QImage *renderPartToNewImage(sv_frame_t f0, sv_frame_t f1) override; + + virtual QSize getRenderedImageSize() override { + return View::getRenderedImageSize(); + } + + virtual QSize getRenderedPartImageSize(sv_frame_t f0, sv_frame_t f1) override; virtual void toXml(QTextStream &stream, QString indent = "", - QString extraAttributes = "") const; + QString extraAttributes = "") const override; static void registerShortcuts(KeyReference &kr); @@ -77,20 +84,22 @@ void regionOutlined(QRect rect); public slots: - virtual void toolModeChanged(); - virtual void zoomWheelsEnabledChanged(); - virtual void viewZoomLevelChanged(View *v, int z, bool locked); - virtual void modelAlignmentCompletionChanged(); + // view slots + virtual void toolModeChanged() override; + virtual void zoomWheelsEnabledChanged() override; + virtual void viewZoomLevelChanged(View *v, int z, bool locked) override; + virtual void modelAlignmentCompletionChanged() override; + // local slots, not overrides virtual void horizontalThumbwheelMoved(int value); virtual void verticalThumbwheelMoved(int value); virtual void verticalZoomChanged(); virtual void verticalPannerMoved(float x, float y, float w, float h); virtual void editVerticalPannerExtents(); - virtual void layerParametersChanged(); + virtual void layerParametersChanged() override; - virtual void propertyContainerSelected(View *, PropertyContainer *pc); + virtual void propertyContainerSelected(View *, PropertyContainer *pc) override; void zoomToRegion(QRect r); @@ -101,17 +110,17 @@ void playbackScheduleTimerElapsed(); protected: - virtual void paintEvent(QPaintEvent *e); - virtual void mousePressEvent(QMouseEvent *e); - virtual void mouseReleaseEvent(QMouseEvent *e); - virtual void mouseMoveEvent(QMouseEvent *e); - virtual void mouseDoubleClickEvent(QMouseEvent *e); - virtual void enterEvent(QEvent *e); - virtual void leaveEvent(QEvent *e); - virtual void wheelEvent(QWheelEvent *e); - virtual void resizeEvent(QResizeEvent *e); - virtual void dragEnterEvent(QDragEnterEvent *e); - virtual void dropEvent(QDropEvent *e); + virtual void paintEvent(QPaintEvent *e) override; + virtual void mousePressEvent(QMouseEvent *e) override; + virtual void mouseReleaseEvent(QMouseEvent *e) override; + virtual void mouseMoveEvent(QMouseEvent *e) override; + virtual void mouseDoubleClickEvent(QMouseEvent *e) override; + virtual void enterEvent(QEvent *e) override; + virtual void leaveEvent(QEvent *e) override; + virtual void wheelEvent(QWheelEvent *e) override; + virtual void resizeEvent(QResizeEvent *e) override; + virtual void dragEnterEvent(QDragEnterEvent *e) override; + virtual void dropEvent(QDropEvent *e) override; void wheelVertical(int sign, Qt::KeyboardModifiers); void wheelHorizontal(int sign, Qt::KeyboardModifiers); @@ -127,7 +136,7 @@ void drawEditingSelection(QPainter &); void drawAlignmentStatus(QRect, QPainter &, const Model *, bool down); - virtual bool render(QPainter &paint, int x0, sv_frame_t f0, sv_frame_t f1); + virtual bool render(QPainter &paint, int x0, sv_frame_t f0, sv_frame_t f1) override; Selection getSelectionAt(int x, bool &closeToLeft, bool &closeToRight) const; diff -r e8102ff5573b -r dc2af6616c83 view/PaneStack.cpp --- a/view/PaneStack.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/view/PaneStack.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -21,6 +21,7 @@ #include "widgets/ClickableLabel.h" #include "layer/Layer.h" #include "ViewManager.h" +#include "AlignmentView.h" #include #include @@ -40,6 +41,7 @@ QFrame(parent), m_currentPane(0), m_showAccessories(true), + m_showAlignmentViews(false), m_splitter(new QSplitter), m_propertyStackStack(new QStackedWidget), m_viewManager(viewManager), @@ -67,6 +69,15 @@ m_showAccessories = show; } +void +PaneStack::setShowAlignmentViews(bool show) +{ + m_showAlignmentViews = show; + foreach (const PaneRec &r, m_panes) { + r.alignmentView->setVisible(m_showAlignmentViews); + } +} + Pane * PaneStack::addPane(bool suppressPropertyBox) { @@ -112,6 +123,12 @@ layout->addWidget(pane, 0, 1, 2, 1); layout->setColumnStretch(1, 20); + AlignmentView *av = new AlignmentView(frame); + av->setFixedHeight(40);//!!! + av->setVisible(m_showAlignmentViews); + av->setViewManager(m_viewManager); + layout->addWidget(av, 2, 1); + QWidget *properties = 0; if (suppressPropertyBox) { properties = new QFrame(); @@ -139,6 +156,7 @@ rec.currentIndicator = currentIndicator; rec.frame = frame; rec.layout = layout; + rec.alignmentView = av; m_panes.push_back(rec); frame->setLayout(layout); @@ -167,11 +185,34 @@ } showOrHidePaneAccessories(); + relinkAlignmentViews(); return pane; } void +PaneStack::relinkAlignmentViews() +{ + for (int i = 0; i < (int)m_panes.size(); ++i) { + m_panes[i].alignmentView->setViewAbove(m_panes[i].pane); + if (i + 1 < (int)m_panes.size()) { + m_panes[i].alignmentView->setViewBelow(m_panes[i+1].pane); + } else { + m_panes[i].alignmentView->setViewBelow(0); + } + } +} + +void +PaneStack::unlinkAlignmentViews() +{ + for (int i = 0; i < (int)m_panes.size(); ++i) { + m_panes[i].alignmentView->setViewAbove(0); + m_panes[i].alignmentView->setViewBelow(0); + } +} + +void PaneStack::setPropertyStackMinWidth(int mw) { for (std::vector::iterator i = m_panes.begin(); @@ -279,6 +320,7 @@ } emit paneAboutToBeDeleted(pane); + unlinkAlignmentViews(); cerr << "PaneStack::deletePane: about to delete parent " << pane->parent() << " of pane " << pane << endl; @@ -303,6 +345,7 @@ } showOrHidePaneAccessories(); + relinkAlignmentViews(); emit paneDeleted(); } @@ -362,6 +405,8 @@ ++i; } + relinkAlignmentViews(); + cerr << "WARNING: PaneStack::hidePane(" << pane << "): Pane not found in visible panes" << endl; } @@ -386,6 +431,8 @@ ++i; } + relinkAlignmentViews(); + cerr << "WARNING: PaneStack::showPane(" << pane << "): Pane not found in hidden panes" << endl; } diff -r e8102ff5573b -r dc2af6616c83 view/PaneStack.h --- a/view/PaneStack.h Fri Mar 04 12:23:31 2016 +0000 +++ b/view/PaneStack.h Fri Jan 13 10:29:50 2017 +0000 @@ -34,6 +34,7 @@ class ViewManager; class PropertyContainer; class PropertyStack; +class AlignmentView; class PaneStack : public QFrame { @@ -73,6 +74,8 @@ void setShowPaneAccessories(bool show); // current indicator, close button + void setShowAlignmentViews(bool show); + void sizePanesEqually(); signals: @@ -114,18 +117,20 @@ struct PaneRec { - Pane *pane; - QWidget *propertyStack; - QPushButton *xButton; - QLabel *currentIndicator; - QFrame *frame; - QGridLayout *layout; + Pane *pane; + QWidget *propertyStack; + QPushButton *xButton; + QLabel *currentIndicator; + QFrame *frame; + QGridLayout *layout; + AlignmentView *alignmentView; }; std::vector m_panes; std::vector m_hiddenPanes; bool m_showAccessories; + bool m_showAlignmentViews; QSplitter *m_splitter; QStackedWidget *m_propertyStackStack; @@ -136,6 +141,9 @@ void showOrHidePaneAccessories(); + void unlinkAlignmentViews(); + void relinkAlignmentViews(); + LayoutStyle m_layoutStyle; }; diff -r e8102ff5573b -r dc2af6616c83 view/View.cpp --- a/view/View.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/view/View.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -20,9 +20,12 @@ #include "base/Profiler.h" #include "base/Pitch.h" #include "base/Preferences.h" +#include "ViewProxy.h" #include "layer/TimeRulerLayer.h" #include "layer/SingleColourLayer.h" +#include "layer/PaintAssistant.h" + #include "data/model/PowerOfSqrtTwoZoomConstraint.h" #include "data/model/RangeSummarisableTimeValueModel.h" @@ -37,19 +40,19 @@ #include #include #include +#include +#include #include #include #include -#include - //#define DEBUG_VIEW 1 //#define DEBUG_VIEW_WIDGET_PAINT 1 - View::View(QWidget *w, bool showProgress) : QFrame(w), + m_id(getNextId()), m_centreFrame(0), m_zoomLevel(1024), m_followPan(true), @@ -59,6 +62,7 @@ m_playPointerFrame(0), m_showProgress(showProgress), m_cache(0), + m_buffer(0), m_cacheCentreFrame(0), m_cacheZoomLevel(1024), m_selectionCached(false), @@ -76,6 +80,8 @@ m_deleting = true; delete m_propertyContainer; + delete m_cache; + delete m_buffer; } PropertyContainer::PropertyList @@ -362,15 +368,17 @@ sv_frame_t View::getFrameForX(int x) const { - int z = m_zoomLevel; + sv_frame_t z = m_zoomLevel; // nb not just int, or multiplication may overflow sv_frame_t frame = m_centreFrame - (width()/2) * z; + frame = (frame / z) * z; // this is start frame + frame = frame + x * z; + #ifdef DEBUG_VIEW_WIDGET_PAINT - SVDEBUG << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl; + cerr << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl; #endif - frame = (frame / z) * z; // this is start frame - return frame + x * z; + return frame; } double @@ -408,12 +416,12 @@ } double -View::getFrequencyForY(int y, +View::getFrequencyForY(double y, double minf, double maxf, bool logarithmic) const { - int h = height(); + double h = height(); if (logarithmic) { @@ -448,9 +456,30 @@ return m_zoomLevel; } +int +View::effectiveDevicePixelRatio() const +{ +#ifdef Q_OS_MAC + int dpratio = devicePixelRatio(); + if (dpratio > 1) { + QSettings settings; + settings.beginGroup("Preferences"); + if (!settings.value("scaledHiDpi", true).toBool()) { + dpratio = 1; + } + settings.endGroup(); + } + return dpratio; +#else + return 1; +#endif +} + void View::setZoomLevel(int z) { + int dpratio = effectiveDevicePixelRatio(); + if (z < dpratio) return; if (z < 1) z = 1; if (m_zoomLevel != int(z)) { m_zoomLevel = z; @@ -784,56 +813,6 @@ } void -View::drawVisibleText(QPainter &paint, int x, int y, QString text, TextStyle style) const -{ - if (style == OutlinedText || style == OutlinedItalicText) { - - paint.save(); - - if (style == OutlinedItalicText) { - QFont f(paint.font()); - f.setItalic(true); - paint.setFont(f); - } - - QColor penColour, surroundColour, boxColour; - - penColour = getForeground(); - surroundColour = getBackground(); - boxColour = surroundColour; - boxColour.setAlpha(127); - - paint.setPen(Qt::NoPen); - paint.setBrush(boxColour); - - QRect r = paint.fontMetrics().boundingRect(text); - r.translate(QPoint(x, y)); -// cerr << "drawVisibleText: r = " << r.x() << "," < aligned frame = " << af << endl; + cerr << " -> aligned frame = " << f << endl; #endif movePlayPointer(f); @@ -1653,11 +1632,27 @@ void View::setPaintFont(QPainter &paint) { + int scaleFactor = 1; + int dpratio = effectiveDevicePixelRatio(); + if (dpratio > 1) { + QPaintDevice *dev = paint.device(); + if (dynamic_cast(dev) || dynamic_cast(dev)) { + scaleFactor = dpratio; + } + } + QFont font(paint.font()); - font.setPointSize(Preferences::getInstance()->getViewFontSize()); + font.setPointSize(Preferences::getInstance()->getViewFontSize() + * scaleFactor); paint.setFont(font); } +QRect +View::getPaintRect() const +{ + return rect(); +} + void View::paintEvent(QPaintEvent *e) { @@ -1694,6 +1689,8 @@ QRect nonCacheRect(cacheRect); + int dpratio = effectiveDevicePixelRatio(); + // If not all layers are scrollable, but some of the back layers // are, we should store only those in the cache. @@ -1705,25 +1702,34 @@ // If all the non-scrollable layers are non-opaque, then we draw // the selection rectangle behind them and cache it. If any are - // opaque, however, we can't cache. + // opaque, however, or if our device-pixel ratio is not 1 (so we + // need to paint direct to the widget), then we can't cache. // - if (!selectionCacheable) { - selectionCacheable = true; - for (LayerList::const_iterator i = nonScrollables.begin(); - i != nonScrollables.end(); ++i) { - if ((*i)->isLayerOpaque()) { - selectionCacheable = false; - break; - } - } - } - - if (selectionCacheable) { - QPoint localPos; - bool closeToLeft, closeToRight; - if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) { - selectionCacheable = false; - } + if (dpratio == 1) { + + if (!selectionCacheable) { + selectionCacheable = true; + for (LayerList::const_iterator i = nonScrollables.begin(); + i != nonScrollables.end(); ++i) { + if ((*i)->isLayerOpaque()) { + selectionCacheable = false; + break; + } + } + } + + if (selectionCacheable) { + QPoint localPos; + bool closeToLeft, closeToRight; + if (shouldIlluminateLocalSelection + (localPos, closeToLeft, closeToRight)) { + selectionCacheable = false; + } + } + + } else { + + selectionCacheable = false; } #ifdef DEBUG_VIEW_WIDGET_PAINT @@ -1741,6 +1747,14 @@ m_selectionCached = false; } + QSize scaledCacheSize(scaledSize(size(), dpratio)); + QRect scaledCacheRect(scaledRect(cacheRect, dpratio)); + + if (!m_buffer || scaledCacheSize != m_buffer->size()) { + delete m_buffer; + m_buffer = new QPixmap(scaledCacheSize); + } + if (!scrollables.empty()) { #ifdef DEBUG_VIEW_WIDGET_PAINT @@ -1750,8 +1764,7 @@ if (!m_cache || m_cacheZoomLevel != m_zoomLevel || - width() != m_cache->width() || - height() != m_cache->height()) { + scaledCacheSize != m_cache->size()) { // cache is not valid @@ -1763,7 +1776,7 @@ #endif } else { delete m_cache; - m_cache = new QPixmap(width(), height()); + m_cache = new QPixmap(scaledCacheSize); #ifdef DEBUG_VIEW_WIDGET_PAINT cerr << "View(" << this << ")::paintEvent: recreated cache" << endl; #endif @@ -1778,24 +1791,10 @@ getXForFrame(m_centreFrame); if (dx > -width() && dx < width()) { -#ifdef PIXMAP_COPY_TO_SELF - // This is not normally defined. Copying a pixmap to - // itself doesn't work properly on Windows, Mac, or - // X11 with the raster backend (it only works when - // moving in one direction and then presumably only by - // accident). It does actually seem to be fine on X11 - // with the native backend, but we prefer not to use - // that anyway - paint.begin(m_cache); - paint.drawPixmap(dx, 0, *m_cache); - paint.end(); -#else static QPixmap *tmpPixmap = 0; - if (!tmpPixmap || - tmpPixmap->width() != width() || - tmpPixmap->height() != height()) { + if (!tmpPixmap || tmpPixmap->size() != scaledCacheSize) { delete tmpPixmap; - tmpPixmap = new QPixmap(width(), height()); + tmpPixmap = new QPixmap(scaledCacheSize); } paint.begin(tmpPixmap); paint.drawPixmap(0, 0, *m_cache); @@ -1803,7 +1802,6 @@ paint.begin(m_cache); paint.drawPixmap(dx, 0, *tmpPixmap); paint.end(); -#endif if (dx < 0) { cacheRect = QRect(width() + dx, 0, -dx, height()); } else { @@ -1824,8 +1822,8 @@ #ifdef DEBUG_VIEW_WIDGET_PAINT cerr << "View(" << this << ")::paintEvent: cache is good" << endl; #endif - paint.begin(this); - paint.drawPixmap(cacheRect, *m_cache, cacheRect); + paint.begin(m_buffer); + paint.drawPixmap(scaledCacheRect, *m_cache, scaledCacheRect); paint.end(); QFrame::paintEvent(e); paintedCacheRect = true; @@ -1841,16 +1839,26 @@ // Scrollable (cacheable) items first + ViewProxy proxy(this, dpratio); + if (!paintedCacheRect) { - if (repaintCache) paint.begin(m_cache); - else paint.begin(this); + QRect rectToPaint; + + if (repaintCache) { + paint.begin(m_cache); + rectToPaint = scaledCacheRect; + } else { + paint.begin(m_buffer); + rectToPaint = scaledCacheRect; + } + setPaintFont(paint); - paint.setClipRect(cacheRect); + paint.setClipRect(rectToPaint); paint.setPen(getBackground()); paint.setBrush(getBackground()); - paint.drawRect(cacheRect); + paint.drawRect(rectToPaint); paint.setPen(getForeground()); paint.setBrush(Qt::NoBrush); @@ -1858,7 +1866,10 @@ for (LayerList::iterator i = scrollables.begin(); i != scrollables.end(); ++i) { paint.setRenderHint(QPainter::Antialiasing, false); paint.save(); - (*i)->paint(this, paint, cacheRect); +#ifdef DEBUG_VIEW_WIDGET_PAINT + cerr << "Painting scrollable layer " << *i << " using proxy with repaintCache = " << repaintCache << ", dpratio = " << dpratio << ", rectToPaint = " << rectToPaint.x() << "," << rectToPaint.y() << " " << rectToPaint.width() << "x" << rectToPaint.height() << endl; +#endif + (*i)->paint(&proxy, paint, rectToPaint); paint.restore(); } @@ -1871,8 +1882,9 @@ if (repaintCache) { cacheRect |= (e ? e->rect() : rect()); - paint.begin(this); - paint.drawPixmap(cacheRect, *m_cache, cacheRect); + scaledCacheRect = scaledRect(cacheRect, dpratio); + paint.begin(m_buffer); + paint.drawPixmap(scaledCacheRect, *m_cache, scaledCacheRect); paint.end(); } } @@ -1883,13 +1895,15 @@ nonCacheRect |= cacheRect; - paint.begin(this); - paint.setClipRect(nonCacheRect); + QRect scaledNonCacheRect = scaledRect(nonCacheRect, dpratio); + + paint.begin(m_buffer); + paint.setClipRect(scaledNonCacheRect); setPaintFont(paint); if (scrollables.empty()) { paint.setPen(getBackground()); paint.setBrush(getBackground()); - paint.drawRect(nonCacheRect); + paint.drawRect(scaledNonCacheRect); } paint.setPen(getForeground()); @@ -1897,10 +1911,18 @@ for (LayerList::iterator i = nonScrollables.begin(); i != nonScrollables.end(); ++i) { // Profiler profiler2("View::paintEvent non-cacheable"); - (*i)->paint(this, paint, nonCacheRect); +#ifdef DEBUG_VIEW_WIDGET_PAINT + cerr << "Painting non-scrollable layer " << *i << " without proxy with repaintCache = " << repaintCache << ", dpratio = " << dpratio << ", rectToPaint = " << nonCacheRect.x() << "," << nonCacheRect.y() << " " << nonCacheRect.width() << "x" << nonCacheRect.height() << endl; +#endif + (*i)->paint(&proxy, paint, scaledNonCacheRect); } paint.end(); + + paint.begin(this); + QRect finalPaintRect = e ? e->rect() : rect(); + paint.drawPixmap(finalPaintRect, *m_buffer, scaledRect(finalPaintRect, dpratio)); + paint.end(); paint.begin(this); setPaintFont(paint); @@ -1925,7 +1947,7 @@ showPlayPointer = false; } } - + if (showPlayPointer) { paint.begin(this); @@ -2081,11 +2103,15 @@ dx = p1 - 2 - dw; } - paint.drawText(sx, sy, startText); - paint.drawText(ex, ey, endText); - paint.drawText(dx, dy, durationText); + PaintAssistant::drawVisibleText(this, paint, sx, sy, startText, + PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, ex, ey, endText, + PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, dx, dy, durationText, + PaintAssistant::OutlinedText); if (durationBothEnds) { - paint.drawText(sx, dy, durationText); + PaintAssistant::drawVisibleText(this, paint, sx, dy, durationText, + PaintAssistant::OutlinedText); } } } @@ -2300,32 +2326,32 @@ } if (axs != "") { - drawVisibleText(paint, axx, axy, axs, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, axx, axy, axs, PaintAssistant::OutlinedText); axy += fontHeight; } if (ays != "") { - drawVisibleText(paint, axx, axy, ays, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, axx, axy, ays, PaintAssistant::OutlinedText); axy += fontHeight; } if (bxs != "") { - drawVisibleText(paint, bxx, bxy, bxs, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, bxx, bxy, bxs, PaintAssistant::OutlinedText); bxy += fontHeight; } if (bys != "") { - drawVisibleText(paint, bxx, bxy, bys, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, bxx, bxy, bys, PaintAssistant::OutlinedText); bxy += fontHeight; } if (dxs != "") { - drawVisibleText(paint, dxx, dxy, dxs, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, dxx, dxy, dxs, PaintAssistant::OutlinedText); dxy += fontHeight; } if (dys != "") { - drawVisibleText(paint, dxx, dxy, dys, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, dxx, dxy, dys, PaintAssistant::OutlinedText); dxy += fontHeight; } @@ -2412,23 +2438,23 @@ for (LayerList::iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) { - if(!((*i)->isLayerDormant(this))){ - - paint.setRenderHint(QPainter::Antialiasing, false); - - paint.save(); - paint.translate(xorigin + x, 0); - - cerr << "Centre frame now: " << m_centreFrame << " drawing to " << chunk.x() + x + xorigin << ", " << chunk.width() << endl; - - (*i)->setSynchronousPainting(true); - - (*i)->paint(this, paint, chunk); - - (*i)->setSynchronousPainting(false); - - paint.restore(); - } + if (!((*i)->isLayerDormant(this))){ + + paint.setRenderHint(QPainter::Antialiasing, false); + + paint.save(); + paint.translate(xorigin + x, 0); + + cerr << "Centre frame now: " << m_centreFrame << " drawing to " << chunk.x() + x + xorigin << ", " << chunk.width() << endl; + + (*i)->setSynchronousPainting(true); + + (*i)->paint(this, paint, chunk); + + (*i)->setSynchronousPainting(false); + + paint.restore(); + } } } @@ -2438,16 +2464,16 @@ } QImage * -View::toNewImage() +View::renderToNewImage() { sv_frame_t f0 = getModelsStartFrame(); sv_frame_t f1 = getModelsEndFrame(); - return toNewImage(f0, f1); + return renderPartToNewImage(f0, f1); } QImage * -View::toNewImage(sv_frame_t f0, sv_frame_t f1) +View::renderPartToNewImage(sv_frame_t f0, sv_frame_t f1) { int x0 = int(f0 / getZoomLevel()); int x1 = int(f1 / getZoomLevel()); @@ -2466,16 +2492,16 @@ } QSize -View::getImageSize() +View::getRenderedImageSize() { sv_frame_t f0 = getModelsStartFrame(); sv_frame_t f1 = getModelsEndFrame(); - return getImageSize(f0, f1); + return getRenderedPartImageSize(f0, f1); } QSize -View::getImageSize(sv_frame_t f0, sv_frame_t f1) +View::getRenderedPartImageSize(sv_frame_t f0, sv_frame_t f1) { int x0 = int(f0 / getZoomLevel()); int x1 = int(f1 / getZoomLevel()); @@ -2483,6 +2509,35 @@ return QSize(x1 - x0, height()); } +bool +View::renderToSvgFile(QString filename) +{ + sv_frame_t f0 = getModelsStartFrame(); + sv_frame_t f1 = getModelsEndFrame(); + + return renderPartToSvgFile(filename, f0, f1); +} + +bool +View::renderPartToSvgFile(QString filename, sv_frame_t f0, sv_frame_t f1) +{ + int x0 = int(f0 / getZoomLevel()); + int x1 = int(f1 / getZoomLevel()); + + QSvgGenerator generator; + generator.setFileName(filename); + generator.setSize(QSize(x1 - x0, height())); + generator.setViewBox(QRect(0, 0, x1 - x0, height())); + generator.setTitle(tr("Exported image from %1") + .arg(QApplication::applicationName())); + + QPainter paint; + paint.begin(&generator); + bool result = render(paint, 0, f0, f1); + paint.end(); + return result; +} + void View::toXml(QTextStream &stream, QString indent, QString extraAttributes) const diff -r e8102ff5573b -r dc2af6616c83 view/View.h --- a/view/View.h Fri Mar 04 12:23:31 2016 +0000 +++ b/view/View.h Fri Jan 13 10:29:50 2017 +0000 @@ -19,6 +19,8 @@ #include #include +#include "layer/LayerGeometryProvider.h" + #include "base/ZoomConstraint.h" #include "base/PropertyContainer.h" #include "ViewManager.h" @@ -49,7 +51,8 @@ */ class View : public QFrame, - public XmlExportable + public XmlExportable, + public LayerGeometryProvider { Q_OBJECT @@ -61,6 +64,12 @@ virtual ~View(); /** + * Retrieve the id of this object. Views have their own unique + * ids, but ViewProxy objects share the id of their View. + */ + int getId() const { return m_id; } + + /** * Retrieve the first visible sample frame on the widget. * This is a calculated value based on the centre-frame, widget * width and zoom level. The result may be negative. @@ -105,6 +114,20 @@ sv_frame_t getFrameForX(int x) const; /** + * Return the closest pixel x-coordinate corresponding to a given + * view x-coordinate. Default is no scaling, ViewProxy handles + * scaling case. + */ + int getXForViewX(int viewx) const { return viewx; } + + /** + * Return the closest view x-coordinate corresponding to a given + * pixel x-coordinate. Default is no scaling, ViewProxy handles + * scaling case. + */ + int getViewXForX(int x) const { return x; } + + /** * Return the pixel y-coordinate corresponding to a given * frequency, if the frequency range is as specified. This does * not imply any policy about layer frequency ranges, but it might @@ -121,8 +144,8 @@ * * Not thread-safe in logarithmic mode. Call only from GUI thread. */ - double getFrequencyForY(int y, double minFreq, double maxFreq, - bool logarithmic) const; + double getFrequencyForY(double y, double minFreq, double maxFreq, + bool logarithmic) const; /** * Return the zoom level, i.e. the number of frames per pixel @@ -243,15 +266,6 @@ virtual QColor getForeground() const; virtual QColor getBackground() const; - enum TextStyle { - BoxedText, - OutlinedText, - OutlinedItalicText - }; - - virtual void drawVisibleText(QPainter &p, int x, int y, - QString text, TextStyle style) const; - virtual void drawMeasurementRect(QPainter &p, const Layer *, QRect rect, bool focus) const; @@ -294,12 +308,42 @@ virtual const PropertyContainer *getPropertyContainer(int i) const; virtual PropertyContainer *getPropertyContainer(int i); - // Render the contents on a wide canvas - virtual QImage *toNewImage(sv_frame_t f0, sv_frame_t f1); - virtual QImage *toNewImage(); - virtual QSize getImageSize(sv_frame_t f0, sv_frame_t f1); - virtual QSize getImageSize(); + /** + * Render the view contents to a new QImage (which may be wider + * than the visible View). + */ + virtual QImage *renderToNewImage(); + /** + * Render the view contents between the given frame extents to a + * new QImage (which may be wider than the visible View). + */ + virtual QImage *renderPartToNewImage(sv_frame_t f0, sv_frame_t f1); + + /** + * Calculate and return the size of image that will be generated + * by renderToNewImage(). + */ + virtual QSize getRenderedImageSize(); + + /** + * Calculate and return the size of image that will be generated + * by renderPartToNewImage(f0, f1). + */ + virtual QSize getRenderedPartImageSize(sv_frame_t f0, sv_frame_t f1); + + /** + * Render the view contents to a new SVG file. + */ + virtual bool renderToSvgFile(QString filename); + + /** + * Render the view contents between the given frame extents to a + * new SVG file. + */ + virtual bool renderPartToSvgFile(QString filename, + sv_frame_t f0, sv_frame_t f1); + virtual int getTextLabelHeight(const Layer *layer, QPainter &) const; virtual bool getValueExtents(QString unit, double &min, double &max, @@ -315,6 +359,18 @@ sv_frame_t getModelsStartFrame() const; sv_frame_t getModelsEndFrame() const; + /** + * To be called from a layer, to obtain the extent of the surface + * that the layer is currently painting to. This may be the extent + * of the view (if 1x display scaling is in effect) or of a larger + * cached pixmap (if greater display scaling is in effect). + */ + QRect getPaintRect() const; + + QSize getPaintSize() const { return getPaintRect().size(); } + int getPaintWidth() const { return getPaintRect().width(); } + int getPaintHeight() const { return getPaintRect().height(); } + typedef std::set ModelSet; ModelSet getModels(); @@ -324,6 +380,11 @@ sv_frame_t alignToReference(sv_frame_t) const; sv_frame_t getAlignedPlaybackFrame() const; + void updatePaintRect(QRect r) { update(r); } + + View *getView() { return this; } + const View *getView() const { return this; } + signals: void propertyContainerAdded(PropertyContainer *pc); void propertyContainerRemoved(PropertyContainer *pc); @@ -339,7 +400,7 @@ bool globalScroll, PlaybackFollowMode followMode); - void zoomLevelChanged(int, bool); + void zoomLevelChanged(int level, bool locked); void contextHelpChanged(const QString &); @@ -372,11 +433,22 @@ protected: View(QWidget *, bool showProgress); + + int m_id; + virtual void paintEvent(QPaintEvent *e); virtual void drawSelections(QPainter &); virtual bool shouldLabelSelections() const { return true; } virtual bool render(QPainter &paint, int x0, sv_frame_t f0, sv_frame_t f1); virtual void setPaintFont(QPainter &paint); + + QSize scaledSize(const QSize &s, int factor) { + return QSize(s.width() * factor, s.height() * factor); + } + QRect scaledRect(const QRect &r, int factor) { + return QRect(r.x() * factor, r.y() * factor, + r.width() * factor, r.height() * factor); + } typedef std::vector LayerList; @@ -406,6 +478,8 @@ void checkProgress(void *object); int getProgressBarWidth() const; // if visible + int effectiveDevicePixelRatio() const; + sv_frame_t m_centreFrame; int m_zoomLevel; bool m_followPan; @@ -416,7 +490,8 @@ bool m_lightBackground; bool m_showProgress; - QPixmap *m_cache; + QPixmap *m_cache; // I own this + QPixmap *m_buffer; // I own this sv_frame_t m_cacheCentreFrame; int m_cacheZoomLevel; bool m_selectionCached; diff -r e8102ff5573b -r dc2af6616c83 view/ViewManager.cpp --- a/view/ViewManager.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/view/ViewManager.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -15,6 +15,7 @@ #include "ViewManager.h" #include "base/AudioPlaySource.h" +#include "base/AudioRecordTarget.h" #include "base/RealTime.h" #include "data/model/Model.h" #include "widgets/CommandHistory.h" @@ -30,6 +31,7 @@ ViewManager::ViewManager() : m_playSource(0), + m_recordTarget(0), m_globalCentreFrame(0), m_globalZoom(1024), m_playbackFrame(0), @@ -157,8 +159,20 @@ sv_frame_t ViewManager::getPlaybackFrame() const { - if (m_playSource && m_playSource->isPlaying()) { + if (isRecording()) { + m_playbackFrame = m_recordTarget->getRecordDuration(); +#ifdef DEBUG_VIEW_MANAGER + cout << "ViewManager::getPlaybackFrame(recording) -> " << m_playbackFrame << endl; +#endif + } else if (isPlaying()) { m_playbackFrame = m_playSource->getCurrentPlayingFrame(); +#ifdef DEBUG_VIEW_MANAGER + cout << "ViewManager::getPlaybackFrame(playing) -> " << m_playbackFrame << endl; +#endif + } else { +#ifdef DEBUG_VIEW_MANAGER + cout << "ViewManager::getPlaybackFrame(not playing) -> " << m_playbackFrame << endl; +#endif } return m_playbackFrame; } @@ -166,11 +180,14 @@ void ViewManager::setPlaybackFrame(sv_frame_t f) { +#ifdef DEBUG_VIEW_MANAGER + cerr << "ViewManager::setPlaybackFrame(" << f << ")" << endl; +#endif if (f < 0) f = 0; if (m_playbackFrame != f) { m_playbackFrame = f; emit playbackFrameChanged(f); - if (m_playSource && m_playSource->isPlaying()) { + if (isPlaying()) { m_playSource->play(f); } } @@ -489,10 +506,10 @@ } sv_samplerate_t -ViewManager::getOutputSampleRate() const +ViewManager::getDeviceSampleRate() const { if (m_playSource) { - return m_playSource->getTargetSampleRate(); + return m_playSource->getDeviceSampleRate(); } return 0; } @@ -507,6 +524,15 @@ } void +ViewManager::setAudioRecordTarget(AudioRecordTarget *target) +{ + if (!m_recordTarget) { + QTimer::singleShot(100, this, SLOT(checkPlayStatus())); + } + m_recordTarget = target; +} + +void ViewManager::playStatusChanged(bool /* playing */) { #ifdef DEBUG_VIEW_MANAGER @@ -516,14 +542,44 @@ } void +ViewManager::recordStatusChanged(bool /* recording */) +{ +#ifdef DEBUG_VIEW_MANAGER + cerr << "ViewManager::recordStatusChanged" << endl; +#endif + checkPlayStatus(); +} + +void ViewManager::checkPlayStatus() { - if (m_playSource && m_playSource->isPlaying()) { + if (isRecording()) { + + float left = 0, right = 0; + if (m_recordTarget->getInputLevels(left, right)) { + if (left != m_lastLeft || right != m_lastRight) { + emit monitoringLevelsChanged(left, right); + m_lastLeft = left; + m_lastRight = right; + } + } + + m_playbackFrame = m_recordTarget->getRecordDuration(); + +#ifdef DEBUG_VIEW_MANAGER + cerr << "ViewManager::checkPlayStatus: Recording, frame " << m_playbackFrame << ", levels " << m_lastLeft << "," << m_lastRight << endl; +#endif + + emit playbackFrameChanged(m_playbackFrame); + + QTimer::singleShot(500, this, SLOT(checkPlayStatus())); + + } else if (isPlaying()) { float left = 0, right = 0; if (m_playSource->getOutputLevels(left, right)) { if (left != m_lastLeft || right != m_lastRight) { - emit outputLevelsChanged(left, right); + emit monitoringLevelsChanged(left, right); m_lastLeft = left; m_lastRight = right; } @@ -542,13 +598,13 @@ } else { if (m_lastLeft != 0.0 || m_lastRight != 0.0) { - emit outputLevelsChanged(0.0, 0.0); + emit monitoringLevelsChanged(0.0, 0.0); m_lastLeft = 0.0; m_lastRight = 0.0; } #ifdef DEBUG_VIEW_MANAGER - cerr << "ViewManager::checkPlayStatus: Not playing" << endl; + cerr << "ViewManager::checkPlayStatus: Not playing or recording" << endl; #endif } } @@ -559,6 +615,12 @@ return m_playSource && m_playSource->isPlaying(); } +bool +ViewManager::isRecording() const +{ + return m_recordTarget && m_recordTarget->isRecording(); +} + void ViewManager::viewCentreFrameChanged(sv_frame_t f, bool locked, PlaybackFollowMode mode) @@ -597,14 +659,22 @@ cerr << "ViewManager::seek(" << f << ")" << endl; #endif - if (m_playSource && m_playSource->isPlaying()) { + if (isRecording()) { + // ignore +#ifdef DEBUG_VIEW_MANAGER + cerr << "ViewManager::seek: Ignoring during recording" << endl; +#endif + return; + } + + if (isPlaying()) { sv_frame_t playFrame = m_playSource->getCurrentPlayingFrame(); sv_frame_t diff = std::max(f, playFrame) - std::min(f, playFrame); if (diff > 20000) { m_playbackFrame = f; m_playSource->play(f); #ifdef DEBUG_VIEW_MANAGER - cerr << "ViewManager::considerSeek: reseeking from " << playFrame << " to " << f << endl; + cerr << "ViewManager::seek: reseeking from " << playFrame << " to " << f << endl; #endif emit playbackFrameChanged(f); } @@ -741,3 +811,4 @@ if (pixels != 0 && scaled == 0) scaled = 1; return scaled; } + diff -r e8102ff5573b -r dc2af6616c83 view/ViewManager.h --- a/view/ViewManager.h Fri Mar 04 12:23:31 2016 +0000 +++ b/view/ViewManager.h Fri Jan 13 10:29:50 2017 +0000 @@ -29,6 +29,7 @@ #include "base/BaseTypes.h" class AudioPlaySource; +class AudioRecordTarget; class Model; enum PlaybackFollowMode { @@ -80,8 +81,10 @@ virtual ~ViewManager(); void setAudioPlaySource(AudioPlaySource *source); + void setAudioRecordTarget(AudioRecordTarget *target); bool isPlaying() const; + bool isRecording() const; sv_frame_t getGlobalCentreFrame() const; // the set method is a slot int getGlobalZoom() const; @@ -172,9 +175,9 @@ /** * The sample rate of the audio output device. If the playback * sample rate differs from this, everything will be resampled at - * the output stage. + * the output stage (but not before). */ - sv_samplerate_t getOutputSampleRate() const; + sv_samplerate_t getDeviceSampleRate() const; /** * The sample rate of the current main model. This may in theory @@ -254,8 +257,8 @@ /** Emitted when the playback frame changes. */ void playbackFrameChanged(sv_frame_t frame); - /** Emitted when the output levels change. Values in range 0.0 -> 1.0. */ - void outputLevelsChanged(float left, float right); + /** Emitted when the output or record levels change. Values in range 0.0 -> 1.0. */ + void monitoringLevelsChanged(float left, float right); /** Emitted whenever the selection has changed. */ void selectionChanged(); @@ -305,6 +308,7 @@ void setGlobalCentreFrame(sv_frame_t); void setPlaybackFrame(sv_frame_t); void playStatusChanged(bool playing); + void recordStatusChanged(bool recording); protected slots: void checkPlayStatus(); @@ -313,6 +317,8 @@ protected: AudioPlaySource *m_playSource; + AudioRecordTarget *m_recordTarget; + sv_frame_t m_globalCentreFrame; int m_globalZoom; mutable sv_frame_t m_playbackFrame; diff -r e8102ff5573b -r dc2af6616c83 view/ViewProxy.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/ViewProxy.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,147 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef VIEW_PROXY_H +#define VIEW_PROXY_H + +#include "layer/LayerGeometryProvider.h" + +class ViewProxy : public LayerGeometryProvider +{ +public: + ViewProxy(View *view, int scaleFactor) : + m_view(view), m_scaleFactor(scaleFactor) { } + + virtual int getId() const { + return m_view->getId(); + } + virtual sv_frame_t getStartFrame() const { + return m_view->getStartFrame(); + } + virtual sv_frame_t getCentreFrame() const { + return m_view->getCentreFrame(); + } + virtual sv_frame_t getEndFrame() const { + return m_view->getEndFrame(); + } + virtual int getXForFrame(sv_frame_t frame) const { + //!!! not actually correct, if frame lies between view's pixels + return m_scaleFactor * m_view->getXForFrame(frame); + } + virtual sv_frame_t getFrameForX(int x) const { + sv_frame_t f0 = m_view->getFrameForX(x / m_scaleFactor); + if (m_scaleFactor == 1) return f0; + sv_frame_t f1 = m_view->getFrameForX((x / m_scaleFactor) + 1); + return f0 + ((f1 - f0) * (x % m_scaleFactor)) / m_scaleFactor; + } + virtual int getXForViewX(int viewx) const { + return viewx * m_scaleFactor; + } + virtual int getViewXForX(int x) const { + return x / m_scaleFactor; + } + virtual sv_frame_t getModelsStartFrame() const { + return m_view->getModelsStartFrame(); + } + virtual sv_frame_t getModelsEndFrame() const { + return m_view->getModelsEndFrame(); + } + virtual double getYForFrequency(double frequency, + double minFreq, double maxFreq, + bool logarithmic) const { + return m_scaleFactor * + m_view->getYForFrequency(frequency, minFreq, maxFreq, logarithmic); + } + virtual double getFrequencyForY(double y, double minFreq, double maxFreq, + bool logarithmic) const { + return m_view->getFrequencyForY + (y / m_scaleFactor, minFreq, maxFreq, logarithmic); + } + virtual int getTextLabelHeight(const Layer *layer, QPainter &paint) const { + return m_scaleFactor * m_view->getTextLabelHeight(layer, paint); + } + virtual bool getValueExtents(QString unit, double &min, double &max, + bool &log) const { + return m_view->getValueExtents(unit, min, max, log); + } + virtual int getZoomLevel() const { + int z = m_view->getZoomLevel(); +// cerr << "getZoomLevel: from " << z << " to "; + z = z / m_scaleFactor; +// cerr << z << endl; + if (z < 1) z = 1; + return z; + } + virtual QRect getPaintRect() const { + QRect r = m_view->getPaintRect(); + return QRect(r.x() * m_scaleFactor, + r.y() * m_scaleFactor, + r.width() * m_scaleFactor, + r.height() * m_scaleFactor); + } + virtual QSize getPaintSize() const { + return getPaintRect().size(); + } + virtual int getPaintWidth() const { + return getPaintRect().width(); + } + virtual int getPaintHeight() const { + return getPaintRect().height(); + } + virtual bool hasLightBackground() const { + return m_view->hasLightBackground(); + } + virtual QColor getForeground() const { + return m_view->getForeground(); + } + virtual QColor getBackground() const { + return m_view->getBackground(); + } + virtual ViewManager *getViewManager() const { + return m_view->getViewManager(); + } + + virtual bool shouldIlluminateLocalFeatures(const Layer *layer, + QPoint &point) const { + QPoint p; + bool should = m_view->shouldIlluminateLocalFeatures(layer, p); + point = QPoint(p.x() * m_scaleFactor, p.y() * m_scaleFactor); + return should; + } + + virtual bool shouldShowFeatureLabels() const { + return m_view->shouldShowFeatureLabels(); + } + + virtual void drawMeasurementRect(QPainter &p, const Layer *layer, + QRect rect, bool focus) const { + m_view->drawMeasurementRect(p, layer, rect, focus); + } + + virtual void updatePaintRect(QRect r) { + m_view->update(r.x() / m_scaleFactor, + r.y() / m_scaleFactor, + r.width() / m_scaleFactor, + r.height() / m_scaleFactor); + } + + virtual View *getView() { return m_view; } + virtual const View *getView() const { return m_view; } + +private: + View *m_view; + int m_scaleFactor; +}; + +#endif diff -r e8102ff5573b -r dc2af6616c83 widgets/AudioDial.cpp --- a/widgets/AudioDial.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/AudioDial.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -409,13 +409,27 @@ if (m_showTooltip) { QString name = objectName(); - QString unit = ""; + QString label; + if (m_rangeMapper) { + label = m_rangeMapper->getLabel(value); + } QString text; - if (m_rangeMapper) unit = m_rangeMapper->getUnit(); - if (name != "") { - text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit); + if (label != "") { + if (name != "") { + text = tr("%1: %2").arg(name).arg(label); + } else { + text = label; + } } else { - text = tr("%2%3").arg(m_mappedValue).arg(unit); + QString unit = ""; + if (m_rangeMapper) { + unit = m_rangeMapper->getUnit(); + } + if (name != "") { + text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit); + } else { + text = tr("%2%3").arg(m_mappedValue).arg(unit); + } } setToolTip(text); } diff -r e8102ff5573b -r dc2af6616c83 widgets/ColourComboBox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/ColourComboBox.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,101 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007-2016 QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "ColourComboBox.h" + +#include "ColourNameDialog.h" + +#include "layer/ColourDatabase.h" + +#include "base/Debug.h" + +#include +#include + +#include + +using namespace std; + +ColourComboBox::ColourComboBox(bool withAddNewColourEntry, QWidget *parent) : + NotifyingComboBox(parent), + m_withAddNewColourEntry(withAddNewColourEntry) +{ + setEditable(false); + rebuild(); + + connect(this, SIGNAL(activated(int)), this, SLOT(comboActivated(int))); + connect(ColourDatabase::getInstance(), SIGNAL(colourDatabaseChanged()), + this, SLOT(rebuild())); + + if (count() < 20 && count() > maxVisibleItems()) { + setMaxVisibleItems(count()); + } +} + +void +ColourComboBox::comboActivated(int index) +{ + if (!m_withAddNewColourEntry || + index < int(ColourDatabase::getInstance()->getColourCount())) { + emit colourChanged(index); + return; + } + + QColor newColour = QColorDialog::getColor(); + if (!newColour.isValid()) return; + + ColourNameDialog dialog(tr("Name New Colour"), + tr("Enter a name for the new colour:"), + newColour, newColour.name(), this); + dialog.showDarkBackgroundCheckbox(tr("Prefer black background for this colour")); + if (dialog.exec() == QDialog::Accepted) { + //!!! command + ColourDatabase *db = ColourDatabase::getInstance(); + int index = db->addColour(newColour, dialog.getColourName()); + db->setUseDarkBackground(index, dialog.isDarkBackgroundChecked()); + // addColour will have called back on rebuild(), and the new + // colour will be at the index previously occupied by Add New + // Colour, which is our current index + emit colourChanged(currentIndex()); + } +} + +void +ColourComboBox::rebuild() +{ + blockSignals(true); + + int ix = currentIndex(); + + clear(); + + int size = (QFontMetrics(QFont()).height() * 2) / 3; + if (size < 12) size = 12; + + ColourDatabase *db = ColourDatabase::getInstance(); + for (int i = 0; i < db->getColourCount(); ++i) { + QString name = db->getColourName(i); + addItem(db->getExamplePixmap(i, QSize(size, size)), name); + } + + if (m_withAddNewColourEntry) { + addItem(tr("Add New Colour...")); + } + + setCurrentIndex(ix); + + blockSignals(false); +} + diff -r e8102ff5573b -r dc2af6616c83 widgets/ColourComboBox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/ColourComboBox.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,44 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007-2016 QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_COLOUR_COMBO_BOX_H +#define SV_COLOUR_COMBO_BOX_H + +#include "NotifyingComboBox.h" + +/** + * Colour-picker combo box with swatches, optionally including "Add + * New Colour..." entry to invoke a QColorDialog/ColourNameDialog + */ +class ColourComboBox : public NotifyingComboBox +{ + Q_OBJECT + +public: + ColourComboBox(bool withAddNewColourEntry, QWidget *parent = 0); + +signals: + void colourChanged(int colourIndex); + +private slots: + void rebuild(); + void comboActivated(int); + +private: + bool m_withAddNewColourEntry; +}; + +#endif + diff -r e8102ff5573b -r dc2af6616c83 widgets/ColourMapComboBox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/ColourMapComboBox.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,74 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007-2016 QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "ColourMapComboBox.h" + +#include "layer/ColourMapper.h" + +#include "base/Debug.h" + +#include + +#include + +using namespace std; + +ColourMapComboBox::ColourMapComboBox(bool includeSwatches, QWidget *parent) : + NotifyingComboBox(parent), + m_includeSwatches(includeSwatches) +{ + setEditable(false); + rebuild(); + + connect(this, SIGNAL(activated(int)), this, SLOT(comboActivated(int))); + + if (count() < 20 && count() > maxVisibleItems()) { + setMaxVisibleItems(count()); + } +} + +void +ColourMapComboBox::comboActivated(int index) +{ + emit colourMapChanged(index); +} + +void +ColourMapComboBox::rebuild() +{ + blockSignals(true); + + int ix = currentIndex(); + + clear(); + + int size = (QFontMetrics(QFont()).height() * 2) / 3; + if (size < 12) size = 12; + + for (int i = 0; i < ColourMapper::getColourMapCount(); ++i) { + QString name = ColourMapper::getColourMapName(i); + if (m_includeSwatches) { + ColourMapper mapper(i, 0.0, 1.0); + addItem(mapper.getExamplePixmap(QSize(size * 2, size)), name); + } else { + addItem(name); + } + } + + setCurrentIndex(ix); + + blockSignals(false); +} + diff -r e8102ff5573b -r dc2af6616c83 widgets/ColourMapComboBox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/ColourMapComboBox.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,43 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007-2016 QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_COLOURMAP_COMBO_BOX_H +#define SV_COLOURMAP_COMBO_BOX_H + +#include "NotifyingComboBox.h" + +/** + * Colour map picker combo box with optional swatches + */ +class ColourMapComboBox : public NotifyingComboBox +{ + Q_OBJECT + +public: + ColourMapComboBox(bool includeSwatches, QWidget *parent = 0); + +signals: + void colourMapChanged(int index); + +private slots: + void rebuild(); + void comboActivated(int); + +private: + bool m_includeSwatches; +}; + +#endif + diff -r e8102ff5573b -r dc2af6616c83 widgets/CommandHistory.cpp --- a/widgets/CommandHistory.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/CommandHistory.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -26,6 +26,8 @@ #include "base/Command.h" +#include "IconLoader.h" + #include #include #include @@ -53,12 +55,16 @@ m_bundleTimer(0), m_bundleTimeout(3000) { - m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); + IconLoader loader; + QIcon undoIcon(loader.load("undo")); + QIcon redoIcon(loader.load("redo")); + + m_undoAction = new QAction(undoIcon, ("&Undo"), this); m_undoAction->setShortcut(tr("Ctrl+Z")); m_undoAction->setStatusTip(tr("Undo the last editing operation")); connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo())); - m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); + m_undoMenuAction = new QAction(undoIcon, tr("&Undo"), this); connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo())); m_undoMenu = new QMenu(tr("&Undo")); @@ -66,12 +72,12 @@ connect(m_undoMenu, SIGNAL(triggered(QAction *)), this, SLOT(undoActivated(QAction*))); - m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); + m_redoAction = new QAction(redoIcon, tr("Re&do"), this); m_redoAction->setShortcut(tr("Ctrl+Shift+Z")); m_redoAction->setStatusTip(tr("Redo the last operation that was undone")); connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo())); - m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); + m_redoMenuAction = new QAction(redoIcon, tr("Re&do"), this); connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo())); m_redoMenu = new QMenu(tr("Re&do")); diff -r e8102ff5573b -r dc2af6616c83 widgets/IconLoader.cpp --- a/widgets/IconLoader.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/IconLoader.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -19,8 +19,17 @@ #include #include #include +#include +#include -static const char *autoInvertExceptions[] = { +#include +#include + +#include "base/Debug.h" + +using namespace std; + +static set autoInvertExceptions { // These are the icons that look OK in their default colours, even // in a colour scheme with a black background. (They may also be // icons that would look worse if we tried to auto-invert them.) @@ -29,16 +38,13 @@ // supply inverted versions -- the loader will load xx_inverse.png // in preference to xx.png if a dark background is found.) "fileclose", - "filenew-22", "filenew", - "fileopen-22", "fileopen", "fileopenaudio", "fileopensession", - "filesave-22", "filesave", - "filesaveas-22", "filesaveas", + "filesaveas-sv", "help", "editcut", "editcopy", @@ -51,42 +57,121 @@ "zoom" }; +static vector sizes { 0, 16, 22, 24, 32, 48, 64, 128 }; + QIcon IconLoader::load(QString name) { - QPixmap pmap(loadPixmap(name)); - if (pmap.isNull()) return QIcon(); - else return QIcon(pmap); + QIcon icon; + for (int sz: sizes) { + QPixmap pmap(loadPixmap(name, sz)); + if (!pmap.isNull()) icon.addPixmap(pmap); + } + return icon; +} + +bool +IconLoader::shouldInvert() const +{ + QColor bg = QApplication::palette().window().color(); + bool darkBackground = (bg.red() + bg.green() + bg.blue() <= 384); + return darkBackground; +} + +bool +IconLoader::shouldAutoInvert(QString name) const +{ + if (shouldInvert()) { + return (autoInvertExceptions.find(name) == autoInvertExceptions.end()); + } else { + return false; + } } QPixmap -IconLoader::loadPixmap(QString name) +IconLoader::loadPixmap(QString name, int size) { - QColor bg = QApplication::palette().window().color(); - if (bg.red() + bg.green() + bg.blue() > 384) { // light background - QPixmap pmap(QString(":icons/%1").arg(name)); - if (pmap.isNull()) { - pmap = QPixmap(QString(":icons/%1.png").arg(name)); - } - return pmap; + bool invert = shouldInvert(); + + QString scalableName, nonScalableName; + QPixmap pmap; + + nonScalableName = makeNonScalableFilename(name, size, invert); + pmap = QPixmap(nonScalableName); + if (!pmap.isNull()) return pmap; + + if (size > 0) { + scalableName = makeScalableFilename(name, invert); + pmap = loadScalable(scalableName, size); + if (!pmap.isNull()) return pmap; } - QPixmap pmap(QString(":icons/%1").arg(name)); - if (pmap.isNull()) { - pmap = QPixmap(QString(":icons/%1_inverse.png").arg(name)); - if (pmap.isNull()) { - pmap = QPixmap(QString(":icons/%1.png").arg(name)); - } - } - if (pmap.isNull()) return pmap; + if (invert && shouldAutoInvert(name)) { - for (int i = 0; i < int(sizeof(autoInvertExceptions)/ - sizeof(autoInvertExceptions[0])); ++i) { - if (autoInvertExceptions[i] == name) { - return pmap; + nonScalableName = makeNonScalableFilename(name, size, false); + pmap = QPixmap(nonScalableName); + if (!pmap.isNull()) return invertPixmap(pmap); + + if (size > 0) { + scalableName = makeScalableFilename(name, false); + pmap = loadScalable(scalableName, size); + if (!pmap.isNull()) return invertPixmap(pmap); } } + return QPixmap(); +} + +QPixmap +IconLoader::loadScalable(QString name, int size) +{ + if (!QFile(name).exists()) { +// cerr << "loadScalable: no such file as: \"" << name << "\"" << endl; + return QPixmap(); + } + QPixmap pmap(size, size); + pmap.fill(Qt::transparent); + QSvgRenderer renderer(name); + QPainter painter; + painter.begin(&pmap); +// cerr << "calling renderer for " << name << " at size " << size << "..." << endl; + renderer.render(&painter); +// cerr << "renderer completed" << endl; + painter.end(); + return pmap; +} + +QString +IconLoader::makeNonScalableFilename(QString name, int size, bool invert) +{ + if (invert) { + if (size == 0) { + return QString(":icons/%1_inverse.png").arg(name); + } else { + return QString(":icons/%1-%2_inverse.png").arg(name).arg(size); + } + } else { + if (size == 0) { + return QString(":icons/%1.png").arg(name); + } else { + return QString(":icons/%1-%2.png").arg(name).arg(size); + } + } +} + +QString +IconLoader::makeScalableFilename(QString name, bool invert) +{ + if (invert) { + return QString(":icons/scalable/%1_inverse.svg").arg(name); + } else { + return QString(":icons/scalable/%1.svg").arg(name); + } +} + +QPixmap +IconLoader::invertPixmap(QPixmap pmap) +{ // No suitable inverted icon found for black background; try to // auto-invert the default one diff -r e8102ff5573b -r dc2af6616c83 widgets/IconLoader.h --- a/widgets/IconLoader.h Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/IconLoader.h Fri Jan 13 10:29:50 2017 +0000 @@ -25,7 +25,15 @@ virtual ~IconLoader() { } QIcon load(QString name); - QPixmap loadPixmap(QString name); + +private: + bool shouldInvert() const; + bool shouldAutoInvert(QString) const; + QPixmap loadPixmap(QString, int); + QPixmap loadScalable(QString, int); + QPixmap invertPixmap(QPixmap); + QString makeScalableFilename(QString, bool); + QString makeNonScalableFilename(QString, int, bool); }; #endif diff -r e8102ff5573b -r dc2af6616c83 widgets/InteractiveFileFinder.cpp --- a/widgets/InteractiveFileFinder.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/InteractiveFileFinder.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -37,7 +37,6 @@ m_lastLocatedLocation(""), m_parent(0) { - SVDEBUG << "Registering interactive file finder" << endl; FileFinder::registerFileFinder(this); } @@ -135,6 +134,11 @@ } break; + case SVGFile: + settingsKeyStub = "svg"; + filter = tr("Scalable Vector Graphics files (*.svg)\nAll files (*.*)"); + break; + case CSVFile: settingsKeyStub = "layer"; filter = tr("Comma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nText files (*.txt)\nAll files (*.*)"); @@ -283,6 +287,12 @@ filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)"); break; + case SVGFile: + settingsKeyStub = "savesvg"; + title = tr("Select a file to export to"); + filter = tr("Scalable Vector Graphics files (*.svg)\nAll files (*.*)"); + break; + case CSVFile: settingsKeyStub = "savelayer"; title = tr("Select a file to export to"); @@ -331,6 +341,8 @@ defaultSuffix = "wav"; } else if (type == ImageFile) { defaultSuffix = "png"; + } else if (type == SVGFile) { + defaultSuffix = "svg"; } else if (type == CSVFile) { defaultSuffix = "csv"; } @@ -451,6 +463,10 @@ settingsKeyStub = "image"; break; + case SVGFile: + settingsKeyStub = "svg"; + break; + case CSVFile: settingsKeyStub = "layer"; break; diff -r e8102ff5573b -r dc2af6616c83 widgets/LEDButton.cpp --- a/widgets/LEDButton.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/LEDButton.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -23,6 +23,7 @@ #include "LEDButton.h" +#include "WidgetScale.h" #include #include @@ -38,8 +39,6 @@ int dark_factor; QColor offcolor; - QPixmap *off_map; - QPixmap *on_map; }; @@ -51,8 +50,6 @@ d = new LEDButton::LEDButtonPrivate; d->dark_factor = 300; d->offcolor = col.dark(300); - d->off_map = 0; - d->on_map = 0; setColor(col); } @@ -65,8 +62,6 @@ d = new LEDButton::LEDButtonPrivate; d->dark_factor = 300; d->offcolor = col.dark(300); - d->off_map = 0; - d->on_map = 0; setColor(col); } @@ -78,16 +73,12 @@ d = new LEDButton::LEDButtonPrivate; d->dark_factor = 300; d->offcolor = col.dark(300); - d->off_map = 0; - d->on_map = 0; setColor(col); } LEDButton::~LEDButton() { - delete d->off_map; - delete d->on_map; delete d; } @@ -135,40 +126,7 @@ if (width < 0) width = 0; - QPixmap *tmpMap = 0; - - if (led_state) { - if (d->on_map) { - if (d->on_map->size() == size()) { - paint.begin(this); - paint.drawPixmap(0, 0, *d->on_map); - paint.end(); - return; - } else { - delete d->on_map; - d->on_map = 0; - } - } - } else { - if (d->off_map) { - if (d->off_map->size() == size()) { - paint.begin(this); - paint.drawPixmap(0, 0, *d->off_map); - paint.end(); - return; - } else { - delete d->off_map; - d->off_map = 0; - } - } - } - - int scale = 1; - width *= scale; - - tmpMap = new QPixmap(width, width); - tmpMap->fill(palette().background().color()); - paint.begin(tmpMap); + paint.begin(this); paint.setRenderHint(QPainter::Antialiasing, true); @@ -182,14 +140,14 @@ paint.setBrush(brush); // Draws a "flat" LED with the given color: - paint.drawEllipse( scale, scale, width - scale*2, width - scale*2 ); + paint.drawEllipse( 1, 1, width - 2, width - 2 ); // Draw the bright light spot of the LED now, using modified "old" // painter routine taken from KDEUI´s LEDButton widget: // Setting the new width of the pen is essential to avoid "pixelized" // shadow like it can be observed with the old LED code - pen.setWidth( 2 * scale ); + pen.setWidth( 2 ); // shrink the light on the LED to a size about 2/3 of the complete LED int pos = width/5 + 1; @@ -217,12 +175,13 @@ pos++; light_width--; } + paint.drawPoint(pos, pos); + // Drawing of bright spot finished, now draw a thin border // around the LED which resembles a shadow with light coming // from the upper left. -// pen.setWidth( 2 * scale + 1 ); // ### shouldn't this value be smaller for smaller LEDs? - pen.setWidth(2 * scale); + pen.setWidth(2); brush.setStyle(Qt::NoBrush); paint.setBrush(brush); // This avoids filling of the ellipse @@ -235,36 +194,13 @@ for (int arc = 120; arc < 2880; arc += 240) { pen.setColor(color); paint.setPen(pen); - int w = width - pen.width()/2 - scale + 1; + int w = width - pen.width()/2; paint.drawArc(pen.width()/2 + 1, pen.width()/2 + 1, w - 2, w - 2, angle + arc, 240); paint.drawArc(pen.width()/2 + 1, pen.width()/2 + 1, w - 2, w - 2, angle - arc, 240); color = color.dark(110); //FIXME: this should somehow use the contrast value } // end for ( angle = 720; angle < 6480; angle += 160 ) paint.end(); - // - // painting done - - QPixmap *&dest = led_state ? d->on_map : d->off_map; - - if (scale > 1) { - - QImage i = tmpMap->toImage(); - width /= scale; - delete tmpMap; - dest = new QPixmap(QPixmap::fromImage - (i.scaled(width, width, - Qt::KeepAspectRatio, - Qt::SmoothTransformation))); - - } else { - - dest = tmpMap; - } - - paint.begin(this); - paint.drawPixmap(0, 0, *dest); - paint.end(); } bool @@ -303,10 +239,6 @@ if(led_color!=col) { led_color = col; d->offcolor = col.dark(d->dark_factor); - delete d->on_map; - d->on_map = 0; - delete d->off_map; - d->off_map = 0; update(); } } @@ -348,12 +280,12 @@ QSize LEDButton::sizeHint() const { - return QSize(17, 17); + return WidgetScale::scaleQSize(QSize(17, 17)); } QSize LEDButton::minimumSizeHint() const { - return QSize(17, 17); + return WidgetScale::scaleQSize(QSize(17, 17)); } diff -r e8102ff5573b -r dc2af6616c83 widgets/LEDButton.h --- a/widgets/LEDButton.h Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/LEDButton.h Fri Jan 13 10:29:50 2017 +0000 @@ -25,8 +25,8 @@ sunken variant. This version also implements a simple button API. */ -#ifndef _LED_BUTTON_H_ -#define _LED_BUTTON_H_ +#ifndef SV_LED_BUTTON_H +#define SV_LED_BUTTON_H #include #include "base/Debug.h" diff -r e8102ff5573b -r dc2af6616c83 widgets/LevelPanToolButton.cpp --- a/widgets/LevelPanToolButton.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/LevelPanToolButton.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -49,6 +49,7 @@ setPopupMode(InstantPopup); setMenu(menu); + setToolTip(tr("Click to adjust level and pan")); setImageSize(m_pixels); setBigImageSize(m_pixelsBig); @@ -58,6 +59,12 @@ { } +void +LevelPanToolButton::wheelEvent(QWheelEvent *e) +{ + m_lpw->wheelEvent(e); +} + float LevelPanToolButton::getLevel() const { @@ -110,6 +117,13 @@ } void +LevelPanToolButton::setMonitoringLevels(float left, float right) +{ + m_lpw->setMonitoringLevels(left, right); + update(); +} + +void LevelPanToolButton::setIncludeMute(bool include) { m_lpw->setIncludeMute(include); @@ -170,4 +184,18 @@ m_lpw->renderTo(this, QRectF(margin, margin, m_pixels, m_pixels), false); } +void +LevelPanToolButton::enterEvent(QEvent *e) +{ + QToolButton::enterEvent(e); + emit mouseEntered(); +} +void +LevelPanToolButton::leaveEvent(QEvent *e) +{ + QToolButton::enterEvent(e); + emit mouseLeft(); +} + + diff -r e8102ff5573b -r dc2af6616c83 widgets/LevelPanToolButton.h --- a/widgets/LevelPanToolButton.h Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/LevelPanToolButton.h Fri Jan 13 10:29:50 2017 +0000 @@ -47,6 +47,9 @@ /// Set pan in the range [-1,1] -- will be rounded void setPan(float); + /// Set left and right peak monitoring levels in the range [0,1] + void setMonitoringLevels(float, float); + /// Specify whether the level range should include muting or not void setIncludeMute(bool); @@ -56,12 +59,18 @@ void levelChanged(float); void panChanged(float); + void mouseEntered(); + void mouseLeft(); + private slots: void selfLevelChanged(float); void selfClicked(); protected: - void paintEvent(QPaintEvent *); + virtual void paintEvent(QPaintEvent *); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + virtual void wheelEvent(QWheelEvent *e); LevelPanWidget *m_lpw; int m_pixels; diff -r e8102ff5573b -r dc2af6616c83 widgets/LevelPanWidget.cpp --- a/widgets/LevelPanWidget.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/LevelPanWidget.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -21,6 +21,8 @@ #include "layer/ColourMapper.h" #include "base/AudioLevel.h" +#include "WidgetScale.h" + #include #include #include @@ -35,9 +37,14 @@ QWidget(parent), m_level(maxLevel), m_pan(0), + m_monitorLeft(-1), + m_monitorRight(-1), m_editable(true), m_includeMute(true) { + setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan")); + setLevel(1.0); + setPan(0.0); } LevelPanWidget::~LevelPanWidget() @@ -47,22 +54,7 @@ QSize LevelPanWidget::sizeHint() const { - static double ratio = 0.0; - if (ratio == 0.0) { - double baseEm; -#ifdef Q_OS_MAC - baseEm = 17.0; -#else - baseEm = 15.0; -#endif - double em = QFontMetrics(QFont()).height(); - ratio = em / baseEm; - } - - int pixels = 40; - int scaled = int(pixels * ratio + 0.5); - if (pixels != 0 && scaled == 0) scaled = 1; - return QSize(scaled, scaled); + return WidgetScale::scaleQSize(QSize(40, 40)); } static int @@ -91,18 +83,36 @@ else return -20.; } +int +LevelPanWidget::audioLevelToLevel(float audioLevel, bool withMute) +{ + int level; + if (withMute) { + level = AudioLevel::multiplier_to_fader + (audioLevel, maxLevel, AudioLevel::ShortFader); + } else { + level = db_to_level(AudioLevel::multiplier_to_dB(audioLevel)); + } + if (level < 0) level = 0; + if (level > maxLevel) level = maxLevel; + return level; +} + +float +LevelPanWidget::levelToAudioLevel(int level, bool withMute) +{ + if (withMute) { + return float(AudioLevel::fader_to_multiplier + (level, maxLevel, AudioLevel::ShortFader)); + } else { + return float(AudioLevel::dB_to_multiplier(level_to_db(level))); + } +} + void LevelPanWidget::setLevel(float flevel) { - int level; - if (m_includeMute) { - level = AudioLevel::multiplier_to_fader - (flevel, maxLevel, AudioLevel::ShortFader); - } else { - level = db_to_level(AudioLevel::multiplier_to_dB(flevel)); - } - if (level < 0) level = 0; - if (level > maxLevel) level = maxLevel; + int level = audioLevelToLevel(flevel, m_includeMute); if (level != m_level) { m_level = level; float convertsTo = getLevel(); @@ -116,20 +126,46 @@ float LevelPanWidget::getLevel() const { - if (m_includeMute) { - return float(AudioLevel::fader_to_multiplier - (m_level, maxLevel, AudioLevel::ShortFader)); - } else { - return float(AudioLevel::dB_to_multiplier(level_to_db(m_level))); + float flevel = levelToAudioLevel(m_level, m_includeMute); + return flevel; +} + +int +LevelPanWidget::audioPanToPan(float audioPan) +{ + int pan = int(round(audioPan * maxPan)); + if (pan < -maxPan) pan = -maxPan; + if (pan > maxPan) pan = maxPan; + return pan; +} + +float +LevelPanWidget::panToAudioPan(int pan) +{ + return float(pan) / float(maxPan); +} + +void +LevelPanWidget::setPan(float fpan) +{ + int pan = audioPanToPan(fpan); + if (pan != m_pan) { + m_pan = pan; + update(); } } +float +LevelPanWidget::getPan() const +{ + return panToAudioPan(m_pan); +} + void -LevelPanWidget::setPan(float pan) +LevelPanWidget::setMonitoringLevels(float left, float right) { - m_pan = int(round(pan * maxPan)); - if (m_pan < -maxPan) m_pan = -maxPan; - if (m_pan > maxPan) m_pan = maxPan; + m_monitorLeft = left; + m_monitorRight = right; update(); } @@ -160,23 +196,15 @@ update(); } -float -LevelPanWidget::getPan() const -{ - return float(m_pan) / float(maxPan); -} - void LevelPanWidget::emitLevelChanged() { - cerr << "emitting levelChanged(" << getLevel() << ")" << endl; emit levelChanged(getLevel()); } void LevelPanWidget::emitPanChanged() { - cerr << "emitting panChanged(" << getPan() << ")" << endl; emit panChanged(getPan()); } @@ -338,11 +366,33 @@ pen.setWidthF(cellLightSize(rect).width() + thin); pen.setCapStyle(Qt::RoundCap); paint.setPen(pen); + paint.setBrush(Qt::NoBrush); for (int pan = -maxPan; pan <= maxPan; ++pan) { paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan)); } + if (m_monitorLeft > 0.f || m_monitorRight > 0.f) { + paint.setPen(Qt::NoPen); + for (int pan = -maxPan; pan <= maxPan; ++pan) { + float audioPan = panToAudioPan(pan); + float audioLevel; + if (audioPan < 0.f) { + audioLevel = m_monitorLeft + m_monitorRight * (1.f + audioPan); + } else { + audioLevel = m_monitorRight + m_monitorLeft * (1.f - audioPan); + } + int levelHere = audioLevelToLevel(audioLevel, false); + for (int level = 0; level <= levelHere; ++level) { + paint.setBrush(level_to_colour(level)); + QRectF clr = cellLightRect(rect, level, pan); + paint.drawEllipse(clr); + } + } + paint.setPen(pen); + paint.setBrush(Qt::NoBrush); + } + if (isEnabled()) { pen.setColor(Qt::black); } else { @@ -384,5 +434,17 @@ renderTo(this, rect(), m_editable); } +void +LevelPanWidget::enterEvent(QEvent *e) +{ + QWidget::enterEvent(e); + emit mouseEntered(); +} +void +LevelPanWidget::leaveEvent(QEvent *e) +{ + QWidget::enterEvent(e); + emit mouseLeft(); +} diff -r e8102ff5573b -r dc2af6616c83 widgets/LevelPanWidget.h --- a/widgets/LevelPanWidget.h Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/LevelPanWidget.h Fri Jan 13 10:29:50 2017 +0000 @@ -29,7 +29,8 @@ LevelPanWidget(QWidget *parent = 0); ~LevelPanWidget(); - /// Return level as a gain value in the range [0,1] + /// Return level as a gain value. The basic level range is [0,1] but the + /// gain scale may go up to 4.0 float getLevel() const; /// Return pan as a value in the range [-1,1] @@ -47,37 +48,56 @@ QSize sizeHint() const; public slots: - /// Set level in the range [0,1] -- will be rounded + /// Set level. The basic level range is [0,1] but the scale may go + /// higher. The value will be rounded. void setLevel(float); - /// Set pan in the range [-1,1] -- will be rounded + /// Set pan in the range [-1,1]. The value will be rounded void setPan(float); + /// Set left and right peak monitoring levels in the range [0,1] + void setMonitoringLevels(float, float); + /// Specify whether the widget is editable or read-only (default editable) void setEditable(bool); /// Specify whether the level range should include muting or not void setIncludeMute(bool); + // public so it can be called from LevelPanToolButton (ew) + virtual void wheelEvent(QWheelEvent *ev); + signals: - void levelChanged(float); - void panChanged(float); + void levelChanged(float); // range [0,1] + void panChanged(float); // range [-1,1] + void mouseEntered(); + void mouseLeft(); + protected: virtual void mousePressEvent(QMouseEvent *ev); virtual void mouseMoveEvent(QMouseEvent *ev); virtual void mouseReleaseEvent(QMouseEvent *ev); - virtual void wheelEvent(QWheelEvent *ev); virtual void paintEvent(QPaintEvent *ev); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); void emitLevelChanged(); void emitPanChanged(); int m_level; int m_pan; + float m_monitorLeft; + float m_monitorRight; bool m_editable; bool m_includeMute; + static int audioLevelToLevel(float audioLevel, bool withMute); + static float levelToAudioLevel(int level, bool withMute); + + static int audioPanToPan(float audioPan); + static float panToAudioPan(int pan); + QSizeF cellSize(QRectF) const; QPointF cellCentre(QRectF, int level, int pan) const; QSizeF cellLightSize(QRectF) const; diff -r e8102ff5573b -r dc2af6616c83 widgets/ModelDataTableDialog.cpp --- a/widgets/ModelDataTableDialog.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/ModelDataTableDialog.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -61,7 +61,7 @@ toolbar = addToolBar(tr("Edit Toolbar")); - action = new QAction(il.load("datainsert"), tr("Insert New Item"), this); + action = new QAction(il.load("draw"), tr("Insert New Item"), this); action->setShortcut(tr("Insert")); action->setStatusTip(tr("Insert a new item")); connect(action, SIGNAL(triggered()), this, SLOT(insertRow())); diff -r e8102ff5573b -r dc2af6616c83 widgets/NotifyingComboBox.h --- a/widgets/NotifyingComboBox.h Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/NotifyingComboBox.h Fri Jan 13 10:29:50 2017 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _NOTIFYING_COMBO_BOX_H_ -#define _NOTIFYING_COMBO_BOX_H_ +#ifndef SV_NOTIFYING_COMBO_BOX_H +#define SV_NOTIFYING_COMBO_BOX_H #include @@ -26,8 +26,8 @@ class NotifyingComboBox : public QComboBox { Q_OBJECT + public: - NotifyingComboBox(QWidget *parent = 0) : QComboBox(parent) { } diff -r e8102ff5573b -r dc2af6616c83 widgets/NotifyingPushButton.h --- a/widgets/NotifyingPushButton.h Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/NotifyingPushButton.h Fri Jan 13 10:29:50 2017 +0000 @@ -13,21 +13,22 @@ COPYING included with this distribution for more information. */ -#ifndef _NOTIFYING_PUSH_BUTTON_H_ -#define _NOTIFYING_PUSH_BUTTON_H_ +#ifndef SV_NOTIFYING_PUSH_BUTTON_H +#define SV_NOTIFYING_PUSH_BUTTON_H #include /** - * Very trivial enhancement to QPushButton to make it emit signals when - * the mouse enters and leaves (for context help). + * Very trivial enhancement to QPushButton to make it emit signals + * when the mouse enters and leaves (for context help). See also + * NotifyingToolButton */ class NotifyingPushButton : public QPushButton { Q_OBJECT + public: - NotifyingPushButton(QWidget *parent = 0) : QPushButton(parent) { } diff -r e8102ff5573b -r dc2af6616c83 widgets/NotifyingToolButton.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/NotifyingToolButton.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,35 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "NotifyingToolButton.h" + +NotifyingToolButton::~NotifyingToolButton() +{ +} + +void +NotifyingToolButton::enterEvent(QEvent *e) +{ + QToolButton::enterEvent(e); + emit mouseEntered(); +} + +void +NotifyingToolButton::leaveEvent(QEvent *e) +{ + QToolButton::enterEvent(e); + emit mouseLeft(); +} + diff -r e8102ff5573b -r dc2af6616c83 widgets/NotifyingToolButton.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/NotifyingToolButton.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,47 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2007 QMUL. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_NOTIFYING_TOOL_BUTTON_H +#define SV_NOTIFYING_TOOL_BUTTON_H + +#include + +/** + * Very trivial enhancement to QToolButton to make it emit signals + * when the mouse enters and leaves (for context help). See also + * NotifyingPushButton + */ + +class NotifyingToolButton : public QToolButton +{ + Q_OBJECT + +public: + NotifyingToolButton(QWidget *parent = 0) : + QToolButton(parent) { } + + virtual ~NotifyingToolButton(); + +signals: + void mouseEntered(); + void mouseLeft(); + +protected: + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); +}; + +#endif + diff -r e8102ff5573b -r dc2af6616c83 widgets/PropertyBox.cpp --- a/widgets/PropertyBox.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/PropertyBox.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -20,23 +20,28 @@ #include "base/PlayParameters.h" #include "base/PlayParameterRepository.h" #include "layer/Layer.h" -#include "layer/ColourDatabase.h" #include "base/UnitDatabase.h" #include "base/RangeMapper.h" #include "AudioDial.h" #include "LEDButton.h" #include "IconLoader.h" +#include "LevelPanWidget.h" +#include "LevelPanToolButton.h" +#include "WidgetScale.h" #include "NotifyingCheckBox.h" #include "NotifyingComboBox.h" #include "NotifyingPushButton.h" -#include "ColourNameDialog.h" +#include "NotifyingToolButton.h" +#include "ColourComboBox.h" +#include "ColourMapComboBox.h" #include #include #include #include +#include #include #include #include @@ -63,6 +68,12 @@ m_mainBox = new QVBoxLayout; setLayout(m_mainBox); +#ifdef Q_OS_MAC + QMargins mm = m_mainBox->contentsMargins(); + QMargins mmhalf(mm.left()/2, mm.top()/3, mm.right()/2, mm.bottom()/3); + m_mainBox->setContentsMargins(mmhalf); +#endif + // m_nameWidget = new QLabel; // m_mainBox->addWidget(m_nameWidget); // m_nameWidget->setText(container->objectName()); @@ -97,9 +108,6 @@ connect(UnitDatabase::getInstance(), SIGNAL(unitDatabaseChanged()), this, SLOT(unitDatabaseChanged())); - connect(ColourDatabase::getInstance(), SIGNAL(colourDatabaseChanged()), - this, SLOT(colourDatabaseChanged())); - #ifdef DEBUG_PROPERTY_BOX cerr << "PropertyBox[" << this << "]::PropertyBox returning" << endl; #endif @@ -141,7 +149,7 @@ m_viewPlayFrame->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); m_mainBox->addWidget(m_viewPlayFrame); - QHBoxLayout *layout = new QHBoxLayout; + QGridLayout *layout = new QGridLayout; m_viewPlayFrame->setLayout(layout); layout->setMargin(layout->margin() / 2); @@ -150,32 +158,18 @@ SVDEBUG << "PropertyBox::populateViewPlayFrame: container " << m_container << " (name " << m_container->getPropertyContainerName() << ") params " << params << endl; #endif - if (layer) { - QLabel *showLabel = new QLabel(tr("Show")); - layout->addWidget(showLabel); - layout->setAlignment(showLabel, Qt::AlignVCenter); + QSize buttonSize = WidgetScale::scaleQSize(QSize(26, 26)); + int col = 0; - m_showButton = new LEDButton(Qt::blue); - layout->addWidget(m_showButton); - connect(m_showButton, SIGNAL(stateChanged(bool)), - this, SIGNAL(showLayer(bool))); - connect(m_showButton, SIGNAL(mouseEntered()), - this, SLOT(mouseEnteredWidget())); - connect(m_showButton, SIGNAL(mouseLeft()), - this, SLOT(mouseLeftWidget())); - layout->setAlignment(m_showButton, Qt::AlignVCenter); - } - if (params) { - - QLabel *playLabel = new QLabel(tr("Play")); - layout->addWidget(playLabel); - layout->setAlignment(playLabel, Qt::AlignVCenter); - - m_playButton = new LEDButton(Qt::darkGreen); - m_playButton->setState(!params->isPlayMuted()); - layout->addWidget(m_playButton); - connect(m_playButton, SIGNAL(stateChanged(bool)), + + m_playButton = new NotifyingToolButton; + m_playButton->setCheckable(true); + m_playButton->setIcon(IconLoader().load("speaker")); + m_playButton->setToolTip(tr("Click to toggle playback")); + m_playButton->setChecked(!params->isPlayMuted()); + m_playButton->setFixedSize(buttonSize); + connect(m_playButton, SIGNAL(toggled(bool)), this, SLOT(playAudibleButtonChanged(bool))); connect(m_playButton, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); @@ -183,76 +177,56 @@ this, SLOT(mouseLeftWidget())); connect(params, SIGNAL(playAudibleChanged(bool)), this, SLOT(playAudibleChanged(bool))); - layout->setAlignment(m_playButton, Qt::AlignVCenter); - layout->insertStretch(-1, 10); + LevelPanToolButton *levelPan = new LevelPanToolButton; + levelPan->setFixedSize(buttonSize); + levelPan->setImageSize((buttonSize.height() * 3) / 4); + layout->addWidget(levelPan, 0, col++, Qt::AlignCenter); + connect(levelPan, SIGNAL(levelChanged(float)), + this, SLOT(playGainControlChanged(float))); + connect(levelPan, SIGNAL(panChanged(float)), + this, SLOT(playPanControlChanged(float))); + connect(params, SIGNAL(playGainChanged(float)), + levelPan, SLOT(setLevel(float))); + connect(params, SIGNAL(playPanChanged(float)), + levelPan, SLOT(setPan(float))); + connect(levelPan, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(levelPan, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); + + layout->addWidget(m_playButton, 0, col++, Qt::AlignCenter); if (params->getPlayClipId() != "") { - QPushButton *playParamButton = - new QPushButton(QIcon(":icons/faders.png"), ""); - playParamButton->setFixedWidth(24); - playParamButton->setFixedHeight(24); - layout->addWidget(playParamButton); + QToolButton *playParamButton = new QToolButton; + playParamButton->setObjectName("playParamButton"); + playParamButton->setIcon(IconLoader().load("faders")); + playParamButton->setFixedSize(buttonSize); + layout->addWidget(playParamButton, 0, col++, Qt::AlignCenter); connect(playParamButton, SIGNAL(clicked()), this, SLOT(editPlayParameters())); + connect(playParamButton, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(playParamButton, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); } + } - AudioDial *gainDial = new AudioDial; - layout->addWidget(gainDial); - gainDial->setMeterColor(Qt::darkRed); - gainDial->setMinimum(-50); - gainDial->setMaximum(50); - gainDial->setPageStep(1); - gainDial->setFixedWidth(24); - gainDial->setFixedHeight(24); - gainDial->setNotchesVisible(false); - gainDial->setDefaultValue(0); - gainDial->setObjectName(tr("Playback Gain")); - gainDial->setRangeMapper(new LinearRangeMapper - (-50, 50, -25, 25, tr("dB"))); - gainDial->setShowToolTip(true); - connect(gainDial, SIGNAL(valueChanged(int)), - this, SLOT(playGainDialChanged(int))); - connect(params, SIGNAL(playGainChanged(float)), - this, SLOT(playGainChanged(float))); - connect(this, SIGNAL(changePlayGainDial(int)), - gainDial, SLOT(setValue(int))); - connect(gainDial, SIGNAL(mouseEntered()), + layout->setColumnStretch(col++, 10); + + if (layer) { + + QLabel *showLabel = new QLabel(tr("Show")); + layout->addWidget(showLabel, 0, col++, Qt::AlignVCenter | Qt::AlignRight); + + m_showButton = new LEDButton(palette().highlight().color()); + layout->addWidget(m_showButton, 0, col++, Qt::AlignVCenter | Qt::AlignLeft); + connect(m_showButton, SIGNAL(stateChanged(bool)), + this, SIGNAL(showLayer(bool))); + connect(m_showButton, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); - connect(gainDial, SIGNAL(mouseLeft()), + connect(m_showButton, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); - playGainChanged(params->getPlayGain()); - layout->setAlignment(gainDial, Qt::AlignVCenter); - - AudioDial *panDial = new AudioDial; - layout->addWidget(panDial); - panDial->setMeterColor(Qt::darkGreen); - panDial->setMinimum(-50); - panDial->setMaximum(50); - panDial->setPageStep(1); - panDial->setFixedWidth(24); - panDial->setFixedHeight(24); - panDial->setNotchesVisible(false); - panDial->setToolTip(tr("Playback Pan / Balance")); - panDial->setDefaultValue(0); - panDial->setObjectName(tr("Playback Pan / Balance")); - panDial->setShowToolTip(true); - connect(panDial, SIGNAL(valueChanged(int)), - this, SLOT(playPanDialChanged(int))); - connect(params, SIGNAL(playPanChanged(float)), - this, SLOT(playPanChanged(float))); - connect(this, SIGNAL(changePlayPanDial(int)), - panDial, SLOT(setValue(int))); - connect(panDial, SIGNAL(mouseEntered()), - this, SLOT(mouseEnteredWidget())); - connect(panDial, SIGNAL(mouseLeft()), - this, SLOT(mouseLeftWidget())); - playPanChanged(params->getPlayPan()); - layout->setAlignment(panDial, Qt::AlignVCenter); - - } else { - - layout->insertStretch(-1, 10); } } @@ -282,39 +256,51 @@ << groupName << "\"" << endl; #endif - bool inGroup = (groupName != QString()); - + QString groupLabel = groupName; + if (groupName == QString()) { + groupName = "ungrouped: " + name; // not tr(), this is internal id + groupLabel = propertyLabel; + } + if (!have) { - if (inGroup) { - if (m_groupLayouts.find(groupName) == m_groupLayouts.end()) { -#ifdef DEBUG_PROPERTY_BOX - cerr << "PropertyBox: adding label \"" << groupName << "\" and frame for group for \"" << name << "\"" << endl; + if (m_groupLayouts.find(groupName) == m_groupLayouts.end()) { + QWidget *labelWidget = new QLabel(groupLabel, m_mainWidget); + m_layout->addWidget(labelWidget, row, 0); + QWidget *frame = new QWidget(m_mainWidget); + frame->setMinimumSize(WidgetScale::scaleQSize(QSize(1, 24))); + m_groupLayouts[groupName] = new QGridLayout; +#ifdef Q_OS_MAC + // Seems to be plenty of whitespace already + m_groupLayouts[groupName]->setContentsMargins(0, 0, 0, 0); +#else + // Need a bit of padding on the left + m_groupLayouts[groupName]->setContentsMargins + (WidgetScale::scalePixelSize(10), 0, 0, 0); #endif - m_layout->addWidget(new QLabel(groupName, m_mainWidget), row, 0); - QFrame *frame = new QFrame(m_mainWidget); - m_layout->addWidget(frame, row, 1, 1, 2); - m_groupLayouts[groupName] = new QGridLayout; - m_groupLayouts[groupName]->setMargin(0); - frame->setLayout(m_groupLayouts[groupName]); - } - } else { -#ifdef DEBUG_PROPERTY_BOX - cerr << "PropertyBox: adding label \"" << propertyLabel << "\"" << endl; -#endif - m_layout->addWidget(new QLabel(propertyLabel, m_mainWidget), row, 0); - } + frame->setLayout(m_groupLayouts[groupName]); + m_layout->addWidget(frame, row, 1, 1, 2); + m_layout->setColumnStretch(1, 10); + } } + QGridLayout *groupLayout = m_groupLayouts[groupName]; + +#ifdef DEBUG_PROPERTY_BOX + cerr << "groupName becomes \"" << groupName << "\", groupLabel = \"" + << groupLabel << "\", groupLayout = " << groupLayout << endl; +#endif + + assert(groupLayout); + + QWidget *existing = m_propertyControllers[name]; + switch (type) { case PropertyContainer::ToggleProperty: { - QAbstractButton *button = 0; + QAbstractButton *button; - if (have) { - button = dynamic_cast(m_propertyControllers[name]); - assert(button); - } else { + if (!(button = qobject_cast(existing))) { #ifdef DEBUG_PROPERTY_BOX cerr << "PropertyBox: creating new checkbox" << endl; #endif @@ -324,7 +310,7 @@ QIcon icon(IconLoader().load(iconName)); button->setIcon(icon); button->setObjectName(name); - button->setFixedSize(QSize(18, 18)); + button->setFixedSize(WidgetScale::scaleQSize(QSize(18, 18))); } else { button = new NotifyingCheckBox(); button->setObjectName(name); @@ -335,13 +321,15 @@ this, SLOT(mouseEnteredWidget())); connect(button, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); - if (inGroup) { - button->setToolTip(propertyLabel); - m_groupLayouts[groupName]->addWidget - (button, 0, m_groupLayouts[groupName]->columnCount()); - } else { - m_layout->addWidget(button, row, 1, 1, 2); - } + button->setToolTip(propertyLabel); + + if (existing) { + groupLayout->replaceWidget(existing, button); + delete existing; + } else { + groupLayout->addWidget(button, 0, groupLayout->columnCount()); + } + m_propertyControllers[name] = button; } @@ -357,9 +345,7 @@ { AudioDial *dial; - if (have) { - dial = dynamic_cast(m_propertyControllers[name]); - assert(dial); + if ((dial = qobject_cast(existing))) { if (rangeChanged) { dial->blockSignals(true); dial->setMinimum(min); @@ -367,8 +353,7 @@ dial->setRangeMapper(m_container->getNewPropertyRangeMapper(name)); dial->blockSignals(false); } - - } else { + } else { #ifdef DEBUG_PROPERTY_BOX cerr << "PropertyBox: creating new dial" << endl; #endif @@ -378,8 +363,10 @@ dial->setMaximum(max); dial->setPageStep(1); dial->setNotchesVisible((max - min) <= 12); + // important to set the range mapper before the default, + // because the range mapper is used to map the default + dial->setRangeMapper(m_container->getNewPropertyRangeMapper(name)); dial->setDefaultValue(deflt); - dial->setRangeMapper(m_container->getNewPropertyRangeMapper(name)); dial->setShowToolTip(true); connect(dial, SIGNAL(valueChanged(int)), this, SLOT(propertyControllerChanged(int))); @@ -388,21 +375,15 @@ connect(dial, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); - if (inGroup) { - dial->setFixedWidth(24); - dial->setFixedHeight(24); - m_groupLayouts[groupName]->addWidget - (dial, 0, m_groupLayouts[groupName]->columnCount()); - } else { - dial->setFixedWidth(32); - dial->setFixedHeight(32); - m_layout->addWidget(dial, row, 1); - QLabel *label = new QLabel(m_mainWidget); - connect(dial, SIGNAL(valueChanged(int)), - label, SLOT(setNum(int))); - label->setNum(value); - m_layout->addWidget(label, row, 2); - } + dial->setFixedWidth(WidgetScale::scalePixelSize(24)); + dial->setFixedHeight(WidgetScale::scalePixelSize(24)); + + if (existing) { + groupLayout->replaceWidget(existing, dial); + delete existing; + } else { + groupLayout->addWidget(dial, 0, groupLayout->columnCount()); + } m_propertyControllers[name] = dial; } @@ -415,20 +396,94 @@ break; } + case PropertyContainer::ColourProperty: + { + ColourComboBox *cb; + + if (!(cb = qobject_cast(existing))) { + +#ifdef DEBUG_PROPERTY_BOX + cerr << "PropertyBox: creating new colour combobox" << endl; +#endif + cb = new ColourComboBox(true); + cb->setObjectName(name); + + connect(cb, SIGNAL(colourChanged(int)), + this, SLOT(propertyControllerChanged(int))); + connect(cb, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(cb, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); + + cb->setToolTip(propertyLabel); + + if (existing) { + groupLayout->replaceWidget(existing, cb); + delete existing; + } else { + groupLayout->addWidget(cb, 0, groupLayout->columnCount()); + } + + m_propertyControllers[name] = cb; + } + + if (cb->currentIndex() != value) { + cb->blockSignals(true); + cb->setCurrentIndex(value); + cb->blockSignals(false); + } + + break; + } + + case PropertyContainer::ColourMapProperty: + { + ColourMapComboBox *cb; + + if (!(cb = qobject_cast(existing))) { +#ifdef DEBUG_PROPERTY_BOX + cerr << "PropertyBox: creating new colourmap combobox" << endl; +#endif + cb = new ColourMapComboBox(false); + cb->setObjectName(name); + + connect(cb, SIGNAL(colourMapChanged(int)), + this, SLOT(propertyControllerChanged(int))); + connect(cb, SIGNAL(mouseEntered()), + this, SLOT(mouseEnteredWidget())); + connect(cb, SIGNAL(mouseLeft()), + this, SLOT(mouseLeftWidget())); + + cb->setToolTip(propertyLabel); + + if (existing) { + groupLayout->replaceWidget(existing, cb); + delete existing; + } else { + groupLayout->addWidget(cb, 0, groupLayout->columnCount()); + } + + m_propertyControllers[name] = cb; + } + + if (cb->currentIndex() != value) { + cb->blockSignals(true); + cb->setCurrentIndex(value); + cb->blockSignals(false); + } + + break; + } + case PropertyContainer::ValueProperty: case PropertyContainer::UnitsProperty: - case PropertyContainer::ColourProperty: { NotifyingComboBox *cb; - if (have) { - cb = dynamic_cast(m_propertyControllers[name]); - assert(cb); - } else { + if (!(cb = qobject_cast(existing))) { #ifdef DEBUG_PROPERTY_BOX cerr << "PropertyBox: creating new combobox" << endl; #endif - cb = new NotifyingComboBox(); cb->setObjectName(name); cb->setDuplicatesEnabled(false); @@ -443,10 +498,19 @@ if (type == PropertyContainer::ValueProperty) { for (int i = min; i <= max; ++i) { - cb->addItem(m_container->getPropertyValueLabel(name, i)); + + QString label = m_container->getPropertyValueLabel(name, i); + QString iname = m_container->getPropertyValueIconName(name, i); + + if (iname != "") { + QIcon icon(IconLoader().load(iname)); + cb->addItem(icon, label); + } else { + cb->addItem(label); + } } - } else if (type == PropertyContainer::UnitsProperty) { + } else { // PropertyContainer::UnitsProperty QStringList units = UnitDatabase::getInstance()->getKnownUnits(); for (int i = 0; i < units.size(); ++i) { @@ -454,23 +518,6 @@ } cb->setEditable(true); - - } else { // ColourProperty - - //!!! should be a proper colour combobox class that - // manages its own Add New Colour entry... - - ColourDatabase *db = ColourDatabase::getInstance(); - for (int i = 0; i < db->getColourCount(); ++i) { - QString name = db->getColourName(i); - cb->addItem(db->getExamplePixmap(i, QSize(12, 12)), name); - } - cb->addItem(tr("Add New Colour...")); - } - - cb->blockSignals(false); - if (cb->count() < 20 && cb->count() > cb->maxVisibleItems()) { - cb->setMaxVisibleItems(cb->count()); } } @@ -482,19 +529,16 @@ connect(cb, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); - if (inGroup) { - cb->setToolTip(propertyLabel); - m_groupLayouts[groupName]->addWidget - (cb, 0, m_groupLayouts[groupName]->columnCount()); - } else { - m_layout->addWidget(cb, row, 1, 1, 2); - } + cb->setToolTip(propertyLabel); + groupLayout->addWidget(cb, 0, groupLayout->columnCount()); m_propertyControllers[name] = cb; - } + } else if (existing != cb) { + groupLayout->replaceWidget(existing, cb); + delete existing; + } cb->blockSignals(true); - if (type == PropertyContainer::ValueProperty || - type == PropertyContainer::ColourProperty) { + if (type == PropertyContainer::ValueProperty) { if (cb->currentIndex() != value) { cb->setCurrentIndex(value); } @@ -511,11 +555,6 @@ } cb->blockSignals(false); -#ifdef Q_OS_MAC - // Crashes on startup without this, for some reason - cb->setMinimumSize(QSize(10, 10)); -#endif - break; } @@ -583,22 +622,6 @@ } void -PropertyBox::colourDatabaseChanged() -{ - blockSignals(true); - - PropertyContainer::PropertyList properties = m_container->getProperties(); - for (size_t i = 0; i < properties.size(); ++i) { - if (m_container->getPropertyType(properties[i]) == - PropertyContainer::ColourProperty) { - updatePropertyEditor(properties[i], true); - } - } - - blockSignals(false); -} - -void PropertyBox::propertyControllerChanged(bool on) { propertyControllerChanged(on ? 1 : 0); @@ -620,24 +643,13 @@ if (type == PropertyContainer::UnitsProperty) { - NotifyingComboBox *cb = dynamic_cast(obj); + NotifyingComboBox *cb = qobject_cast(obj); if (cb) { QString unit = cb->currentText(); c = m_container->getSetPropertyCommand (name, UnitDatabase::getInstance()->getUnitId(unit)); } - } else if (type == PropertyContainer::ColourProperty) { - - if (value == int(ColourDatabase::getInstance()->getColourCount())) { - addNewColour(); - if (value == int(ColourDatabase::getInstance()->getColourCount())) { - propertyContainerPropertyChanged(m_container); - return; - } - } - c = m_container->getSetPropertyCommand(name, value); - } else if (type != PropertyContainer::InvalidProperty) { c = m_container->getSetPropertyCommand(name, value); @@ -649,27 +661,9 @@ } void -PropertyBox::addNewColour() -{ - QColor newColour = QColorDialog::getColor(); - if (!newColour.isValid()) return; - - ColourNameDialog dialog(tr("Name New Colour"), - tr("Enter a name for the new colour:"), - newColour, newColour.name(), this); - dialog.showDarkBackgroundCheckbox(tr("Prefer black background for this colour")); - if (dialog.exec() == QDialog::Accepted) { - //!!! command - ColourDatabase *db = ColourDatabase::getInstance(); - int index = db->addColour(newColour, dialog.getColourName()); - db->setUseDarkBackground(index, dialog.isDarkBackgroundChecked()); - } -} - -void PropertyBox::playAudibleChanged(bool audible) { - m_playButton->setState(audible); + m_playButton->setChecked(audible); } void @@ -685,26 +679,15 @@ CommandHistory::getInstance()->addCommand(command, true, true); } } - -void -PropertyBox::playGainChanged(float gain) -{ - int dialValue = int(lrint(log10(gain) * 20.0)); - if (dialValue < -50) dialValue = -50; - if (dialValue > 50) dialValue = 50; - emit changePlayGainDial(dialValue); -} void -PropertyBox::playGainDialChanged(int dialValue) +PropertyBox::playGainControlChanged(float gain) { QObject *obj = sender(); PlayParameters *params = m_container->getPlayParameters(); if (!params) return; - float gain = float(pow(10, float(dialValue) / 20.0)); - if (params->getPlayGain() != gain) { PlayParameterRepository::EditCommand *command = new PlayParameterRepository::EditCommand(params); @@ -714,28 +697,15 @@ updateContextHelp(obj); } - -void -PropertyBox::playPanChanged(float pan) -{ - int dialValue = int(lrint(pan * 50.0)); - if (dialValue < -50) dialValue = -50; - if (dialValue > 50) dialValue = 50; - emit changePlayPanDial(dialValue); -} void -PropertyBox::playPanDialChanged(int dialValue) +PropertyBox::playPanControlChanged(float pan) { QObject *obj = sender(); PlayParameters *params = m_container->getPlayParameters(); if (!params) return; - float pan = float(dialValue) / 50.f; - if (pan < -1.f) pan = -1.f; - if (pan > 1.f) pan = 1.f; - if (params->getPlayPan() != pan) { PlayParameterRepository::EditCommand *command = new PlayParameterRepository::EditCommand(params); @@ -820,17 +790,34 @@ void PropertyBox::updateContextHelp(QObject *o) { - QWidget *w = dynamic_cast(o); + QWidget *w = qobject_cast(o); if (!w) return; if (!m_container) return; QString cname = m_container->getPropertyContainerName(); if (cname == "") return; + LevelPanToolButton *lp = qobject_cast(w); + if (lp) { + emit contextHelpChanged(tr("Adjust playback level and pan of %1").arg(cname)); + return; + } + QString wname = w->objectName(); + if (wname == "playParamButton") { + PlayParameters *params = m_container->getPlayParameters(); + if (params) { + emit contextHelpChanged + (tr("Change sound used for playback (currently \"%1\")") + .arg(params->getPlayClipId())); + return; + } + } + QString extraText; - AudioDial *dial = dynamic_cast(w); + + AudioDial *dial = qobject_cast(w); if (dial) { double mv = dial->mappedValue(); QString unit = ""; @@ -848,7 +835,7 @@ emit contextHelpChanged(tr("Toggle Playback of %1").arg(cname)); } else if (wname == "") { return; - } else if (dynamic_cast(w)) { + } else if (qobject_cast(w)) { emit contextHelpChanged(tr("Toggle %1 property of %2") .arg(wname).arg(cname)); } else { diff -r e8102ff5573b -r dc2af6616c83 widgets/PropertyBox.h --- a/widgets/PropertyBox.h Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/PropertyBox.h Fri Jan 13 10:29:50 2017 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _PROPERTY_BOX_H_ -#define _PROPERTY_BOX_H_ +#ifndef SV_PROPERTY_BOX_H +#define SV_PROPERTY_BOX_H #include "base/PropertyContainer.h" @@ -27,6 +27,8 @@ class QVBoxLayout; class QLabel; class LEDButton; +class QToolButton; +class NotifyingPushButton; class PropertyBox : public QFrame { @@ -39,8 +41,6 @@ PropertyContainer *getContainer() { return m_container; } signals: - void changePlayGainDial(int); - void changePlayPanDial(int); void showLayer(bool); void contextHelpChanged(const QString &); @@ -56,15 +56,12 @@ void playAudibleChanged(bool); void playAudibleButtonChanged(bool); - void playGainChanged(float); - void playGainDialChanged(int); - void playPanChanged(float); - void playPanDialChanged(int); + void playGainControlChanged(float); + void playPanControlChanged(float); void populateViewPlayFrame(); void unitDatabaseChanged(); - void colourDatabaseChanged(); void editPlayParameters(); @@ -75,7 +72,6 @@ void updatePropertyEditor(PropertyContainer::PropertyName, bool rangeChanged = false); void updateContextHelp(QObject *o); - void addNewColour(); QLabel *m_nameWidget; QWidget *m_mainWidget; @@ -84,7 +80,7 @@ QFrame *m_viewPlayFrame; QVBoxLayout *m_mainBox; LEDButton *m_showButton; - LEDButton *m_playButton; + QToolButton *m_playButton; std::map m_groupLayouts; std::map m_propertyControllers; }; diff -r e8102ff5573b -r dc2af6616c83 widgets/PropertyStack.cpp --- a/widgets/PropertyStack.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/PropertyStack.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -25,6 +25,8 @@ #include "widgets/CommandHistory.h" #include "layer/ShowLayerCommand.h" +#include "WidgetScale.h" + #include #include @@ -45,12 +47,10 @@ setTabBar(bar); -#if (QT_VERSION >= 0x0402) setElideMode(Qt::ElideNone); tabBar()->setUsesScrollButtons(true); - tabBar()->setIconSize(QSize(16, 16)); -#endif - + tabBar()->setIconSize(WidgetScale::scaleQSize(QSize(16, 16))); + repopulate(); connect(this, SIGNAL(currentChanged(int)), diff -r e8102ff5573b -r dc2af6616c83 widgets/Thumbwheel.cpp --- a/widgets/Thumbwheel.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/Thumbwheel.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -445,35 +445,36 @@ if (!m_cache.isNull()) { QPainter paint(this); - paint.drawImage(0, 0, m_cache); + paint.drawImage(rect(), m_cache, m_cache.rect()); return; } Profiler profiler2("Thumbwheel::paintEvent (no cache)"); - m_cache = QImage(size(), QImage::Format_ARGB32); + QSize imageSize = size() * devicePixelRatio(); + m_cache = QImage(imageSize, QImage::Format_ARGB32); m_cache.fill(Qt::transparent); - int bw = 3; + int w = m_cache.width(); + int h = m_cache.height(); + int bw = 3; // border width QRect subclip; if (m_orientation == Qt::Horizontal) { - subclip = QRect(bw, bw+1, width() - bw*2, height() - bw*2 - 2); + subclip = QRect(bw, bw+1, w - bw*2, h - bw*2 - 2); } else { - subclip = QRect(bw+1, bw, width() - bw*2 - 2, height() - bw*2); + subclip = QRect(bw+1, bw, w - bw*2 - 2, h - bw*2); } QPainter paint(&m_cache); - paint.setClipRect(rect()); + paint.setClipRect(m_cache.rect()); paint.fillRect(subclip, palette().background().color()); paint.setRenderHint(QPainter::Antialiasing, true); - double w = width(); double w0 = 0.5; double w1 = w - 0.5; - double h = height(); double h0 = 0.5; double h1 = h - 0.5; @@ -508,13 +509,13 @@ // cerr << "value = " << m_value << ", min = " << m_min << ", max = " << m_max << ", rotation = " << rotation << endl; - w = (m_orientation == Qt::Horizontal ? width() : height()) - bw*2; + int ww = (m_orientation == Qt::Horizontal ? w : h) - bw*2; // wheel width // total number of notches on the entire wheel int notches = 25; // radius of the wheel including invisible part - int radius = int(w / 2 + 2); + int radius = int(ww / 2 + 2); for (int i = 0; i < notches; ++i) { @@ -525,13 +526,13 @@ double depth = cos((a0 + a2) / 2); if (depth < 0) continue; - double x0 = radius * sin(a0) + w/2; - double x1 = radius * sin(a1) + w/2; - double x2 = radius * sin(a2) + w/2; - if (x2 < 0 || x0 > w) continue; + double x0 = radius * sin(a0) + ww/2; + double x1 = radius * sin(a1) + ww/2; + double x2 = radius * sin(a2) + ww/2; + if (x2 < 0 || x0 > ww) continue; if (x0 < 0) x0 = 0; - if (x2 > w) x2 = w; + if (x2 > ww) x2 = ww; x0 += bw; x1 += bw; @@ -557,10 +558,10 @@ } if (m_orientation == Qt::Horizontal) { - paint.drawRect(QRectF(x1, height() - (height() - bw*2) * prop - bw, - x2 - x1, height() * prop)); + paint.drawRect(QRectF(x1, h - (h - bw*2) * prop - bw, + x2 - x1, h * prop)); } else { - paint.drawRect(QRectF(bw, x1, (width() - bw*2) * prop, x2 - x1)); + paint.drawRect(QRectF(bw, x1, (w - bw*2) * prop, x2 - x1)); } } @@ -568,14 +569,14 @@ paint.setBrush(palette().background().color()); if (m_orientation == Qt::Horizontal) { - paint.drawRect(QRectF(x0, bw, x1 - x0, height() - bw*2)); + paint.drawRect(QRectF(x0, bw, x1 - x0, h - bw*2)); } else { - paint.drawRect(QRectF(bw, x0, width() - bw*2, x1 - x0)); + paint.drawRect(QRectF(bw, x0, w - bw*2, x1 - x0)); } } QPainter paint2(this); - paint2.drawImage(0, 0, m_cache); + paint2.drawImage(rect(), m_cache, m_cache.rect()); } QSize diff -r e8102ff5573b -r dc2af6616c83 widgets/UnitConverter.cpp --- a/widgets/UnitConverter.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/UnitConverter.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -325,7 +325,7 @@ int note, octave; Pitch::getNoteAndOctaveForPitch(pitch, note, octave); - cerr << "pitch " << pitch << " note " << note << " octave " << octave << " cents " << cents << endl; +// cerr << "pitch " << pitch << " note " << note << " octave " << octave << " cents " << cents << endl; setTo(m_midi, pitch); setTo(m_cents, cents); @@ -387,7 +387,7 @@ double samples = m_samples->value(); double rate = getSampleRate(); - cerr << samples << " samples at rate " << rate << endl; +// cerr << samples << " samples at rate " << rate << endl; double sec = samples / rate; double hz = rate / samples; diff -r e8102ff5573b -r dc2af6616c83 widgets/WidgetScale.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/WidgetScale.h Fri Jan 13 10:29:50 2017 +0000 @@ -0,0 +1,59 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_WIDGET_SCALE_H +#define SV_WIDGET_SCALE_H + +#include +#include + +#include "base/Debug.h" + +class WidgetScale +{ +public: + /** + * Take a "design pixel" size and scale it for the actual + * display. This is relevant to hi-dpi systems that do not do + * pixel doubling (i.e. Windows and Linux rather than OS/X). + */ + static int scalePixelSize(int pixels) { + + static double ratio = 0.0; + if (ratio == 0.0) { + double baseEm; +#ifdef Q_OS_MAC + baseEm = 17.0; +#else + baseEm = 15.0; +#endif + double em = QFontMetrics(QFont()).height(); + ratio = em / baseEm; + SVDEBUG << "WidgetScale::scalePixelSize: baseEm = " << baseEm + << ", platform default font height = " << em + << ", resulting scale factor = " << ratio << endl; + } + + int scaled = int(pixels * ratio + 0.5); + if (pixels != 0 && scaled == 0) scaled = 1; + return scaled; + } + + static QSize scaleQSize(QSize size) { + return QSize(scalePixelSize(size.width()), + scalePixelSize(size.height())); + } +}; + +#endif diff -r e8102ff5573b -r dc2af6616c83 widgets/WindowShapePreview.cpp --- a/widgets/WindowShapePreview.cpp Fri Mar 04 12:23:31 2016 +0000 +++ b/widgets/WindowShapePreview.cpp Fri Jan 13 10:29:50 2017 +0000 @@ -22,13 +22,14 @@ #include #include -#include "data/fft/FFTapi.h" +#include +#include +#include #include -#ifndef __GNUC__ -#include -#endif +using namespace std; + WindowShapePreview::WindowShapePreview(QWidget *parent) : QFrame(parent), @@ -50,12 +51,17 @@ void WindowShapePreview::updateLabels() { - int step = 24; - float peak = 48; - int w = step * 4, h = 64; + float scaleRatio = float(QFontMetrics(font()).height()) / 14.f; + if (scaleRatio < 1.f) scaleRatio = 1.f; + + int step = int(24 * scaleRatio); + float peak = float(48 * scaleRatio); + + int w = step * 4, h = int((peak * 4) / 3); + WindowType type = m_windowType; Window windower = Window(type, step * 2); - + QPixmap timeLabel(w, h + 1); timeLabel.fill(Qt::white); QPainter timePainter(&timeLabel); @@ -71,12 +77,7 @@ path = QPainterPath(); -#ifdef __GNUC__ - float acc[w]; -#else - float *acc = (float *)alloca(w * sizeof(float)); -#endif - + float *acc = new float[w]; for (int i = 0; i < w; ++i) acc[i] = 0.f; for (int j = 0; j < 3; ++j) { for (int i = 0; i < step * 2; ++i) { @@ -88,6 +89,7 @@ if (i == 0) path.moveTo(i, y); else path.lineTo(i, y); } + delete[] acc; timePainter.drawPath(path); timePainter.setRenderHint(QPainter::Antialiasing, false); @@ -112,7 +114,7 @@ timePainter.drawPath(path); QFont font; - font.setPixelSize(10); + font.setPixelSize(int(10 * scaleRatio)); font.setItalic(true); timePainter.setFont(font); QString label = tr("V / time"); @@ -121,33 +123,32 @@ m_windowTimeExampleLabel->setPixmap(timeLabel); - int fw = 100; - - QPixmap freqLabel(fw, h + 1); + QPixmap freqLabel(w, h + 1); freqLabel.fill(Qt::white); QPainter freqPainter(&freqLabel); path = QPainterPath(); int fftsize = 512; - float *input = (float *)fftf_malloc(fftsize * sizeof(float)); - fftf_complex *output = - (fftf_complex *)fftf_malloc(fftsize * sizeof(fftf_complex)); - fftf_plan plan = fftf_plan_dft_r2c_1d(fftsize, input, output, - FFTW_ESTIMATE); + breakfastquay::FFT fft(fftsize); + + vector input(fftsize); + vector> output(fftsize/2 + 1); + for (int i = 0; i < fftsize; ++i) input[i] = 0.f; for (int i = 0; i < step * 2; ++i) { input[fftsize/2 - step + i] = windower.getValue(i); } - - fftf_execute(plan); - fftf_destroy_plan(plan); + + fft.forwardInterleaved(input.data(), reinterpret_cast(output.data())); float maxdb = 0.f; float mindb = 0.f; bool first = true; for (int i = 0; i < fftsize/2; ++i) { - float power = output[i][0] * output[i][0] + output[i][1] * output[i][1]; + float power = + output[i].real() * output[i].real() + + output[i].imag() * output[i].imag(); float db = mindb; if (power > 0) { db = 20.f * log10f(power); @@ -168,7 +169,7 @@ // float ly = h - ((-80.f + -mindb) / maxval) * peak + 1; path.moveTo(0, float(h) - peak + 1); - path.lineTo(fw, float(h) - peak + 1); + path.lineTo(w, float(h) - peak + 1); freqPainter.setPen(Qt::gray); freqPainter.setRenderHint(QPainter::Antialiasing, true); @@ -180,27 +181,26 @@ // cerr << "maxdb = " << maxdb << ", mindb = " << mindb << ", maxval = " <setPixmap(freqLabel);