summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2024-03-09 00:11:38 +0100
committerCarl Hetherington <cth@carlh.net>2024-03-09 00:11:38 +0100
commit80313b07095814f0178be70bc0651c1e052decea (patch)
tree5ea002e29340ee342344affce277aa0cd941d7db
parent82f87c7711fb664b06b04d44792ed3820b3d1e01 (diff)
parent04b5957318df591f56e0a5d39720df143dc8230d (diff)
Merge branch 'main' into v2.17.x
-rw-r--r--cscript29
-rw-r--r--src/lib/dcp_content.cc8
-rw-r--r--src/lib/email.cc (renamed from src/lib/emailer.cc)31
-rw-r--r--src/lib/email.h (renamed from src/lib/emailer.h)7
-rw-r--r--src/lib/kdm_cli.cc4
-rw-r--r--src/lib/kdm_with_metadata.cc19
-rw-r--r--src/lib/send_notification_email_job.cc4
-rw-r--r--src/lib/send_problem_report_job.cc6
-rw-r--r--src/lib/util.cc29
-rw-r--r--src/lib/util.h2
-rw-r--r--src/lib/wscript2
-rw-r--r--src/tools/dcpomatic.cc6
-rw-r--r--src/wx/audio_panel.cc1
-rw-r--r--src/wx/dkdm_dialog.cc2
-rw-r--r--src/wx/dolby_doremi_certificate_panel.cc10
-rw-r--r--src/wx/full_config_dialog.cc6
-rw-r--r--src/wx/kdm_dialog.cc2
-rw-r--r--src/wx/timeline.cc45
-rw-r--r--src/wx/timeline.h3
-rw-r--r--test/util_test.cc11
20 files changed, 179 insertions, 48 deletions
diff --git a/cscript b/cscript
index af1db69a6..cee888420 100644
--- a/cscript
+++ b/cscript
@@ -38,6 +38,9 @@ for v in ['22.04']:
for v in ['23.04', '23.10']:
deb_build_depends[v] = copy.deepcopy(deb_build_depends_base)
deb_build_depends[v].extend(['libssh-dev', 'python3.11'])
+for v in ['24.04']:
+ deb_build_depends[v] = copy.deepcopy(deb_build_depends_base)
+ deb_build_depends[v].extend(['libssh-dev', 'python3.12'])
for v in ['9', '10']:
deb_build_depends[v] = copy.deepcopy(deb_build_depends_base)
deb_build_depends[v].extend(['libssh-gcrypt-dev', 'python'])
@@ -185,6 +188,30 @@ deb_depends['23.10'].extend(['libboost-filesystem1.74.0',
'libwxgtk3.2-1',
'libwxgtk-gl3.2-1'])
+def debs(boost, icu, x264):
+ output = copy.deepcopy(deb_depends_base)
+ output.extend(['libboost-filesystem' + boost,
+ 'libboost-thread' + boost,
+ 'libboost-regex' + boost,
+ 'libboost-date-time' + boost,
+ 'libcairomm-1.0-1v5',
+ 'libpangomm-1.4-1v5',
+ 'libxml++2.6-2v5',
+ 'libzip4',
+ 'libicu' + icu,
+ 'libnettle8',
+ 'libssh-4',
+ 'libx264-' + x264,
+ 'libcurl4',
+ 'libpulse0',
+ 'libxerces-c3.2',
+ 'libnanomsg5',
+ 'libwxgtk3.2-1',
+ 'libwxgtk-gl3.2-1'])
+ return output
+
+deb_depends['24.04'] = debs(boost='1.83.0', icu='74', x264='164')
+
deb_depends['9'] = copy.deepcopy(deb_depends_base)
deb_depends['9'].extend(['libboost-filesystem1.62.0',
'libboost-thread1.62.0',
@@ -731,7 +758,7 @@ def package_debian(target, cpu, version, options):
target.set('CDIST_CONFIGURE', '"' + configure_options(target, options, for_package=True) + '"')
target.set('CDIST_PACKAGE', f'dcpomatic{suffix}')
- target.set('CDIST_WX_VERSION', "3.2" if target.version in ("23.04", "23.10") else "3.1")
+ target.set('CDIST_WX_VERSION', "3.2" if target.version in ("23.04", "23.10", "24.04") else "3.1")
if not target.debug:
target.set('CDIST_DEBUG_PACKAGE_FLAG', '--no-ddebs')
diff --git a/src/lib/dcp_content.cc b/src/lib/dcp_content.cc
index 378ba1882..c459a9ece 100644
--- a/src/lib/dcp_content.cc
+++ b/src/lib/dcp_content.cc
@@ -723,6 +723,14 @@ DCPContent::can_reference_audio (shared_ptr<const Film> film, string& why_not) c
return false;
}
+ if (audio && audio->stream()) {
+ auto const channels = audio->stream()->channels();
+ if (channels != film->audio_channels()) {
+ why_not = String::compose(_("it has a different number of audio channels than the project; set the project to have %1 channels."), channels);
+ return false;
+ }
+ }
+
auto part = [](shared_ptr<const Content> c) {
return static_cast<bool>(c->audio) && !c->audio->mapping().mapped_output_channels().empty();
};
diff --git a/src/lib/emailer.cc b/src/lib/email.cc
index f580e3c56..8557b40e0 100644
--- a/src/lib/emailer.cc
+++ b/src/lib/email.cc
@@ -21,7 +21,7 @@
#include "compose.hpp"
#include "config.h"
-#include "emailer.h"
+#include "email.h"
#include "exceptions.h"
#include <curl/curl.h>
#include <boost/algorithm/string.hpp>
@@ -39,7 +39,7 @@ using std::vector;
using dcp::ArrayData;
-Emailer::Emailer(string from, vector<string> to, string subject, string body)
+Email::Email(string from, vector<string> to, string subject, string body)
: _from (from)
, _to (to)
, _subject (subject)
@@ -51,7 +51,7 @@ Emailer::Emailer(string from, vector<string> to, string subject, string body)
string
-Emailer::fix (string s) const
+Email::fix(string s) const
{
boost::algorithm::replace_all (s, "\n", "\r\n");
boost::algorithm::replace_all (s, "\0", " ");
@@ -60,24 +60,24 @@ Emailer::fix (string s) const
void
-Emailer::add_cc (string cc)
+Email::add_cc(string cc)
{
_cc.push_back (cc);
}
void
-Emailer::add_bcc (string bcc)
+Email::add_bcc(string bcc)
{
_bcc.push_back (bcc);
}
void
-Emailer::add_attachment (boost::filesystem::path file, string name, string mime_type)
+Email::add_attachment(boost::filesystem::path file, string name, string mime_type)
{
Attachment a;
- a.file = file;
+ a.file = dcp::ArrayData(file);
a.name = name;
a.mime_type = mime_type;
_attachments.push_back (a);
@@ -87,19 +87,19 @@ Emailer::add_attachment (boost::filesystem::path file, string name, string mime_
static size_t
curl_data_shim (void* ptr, size_t size, size_t nmemb, void* userp)
{
- return reinterpret_cast<Emailer*>(userp)->get_data (ptr, size, nmemb);
+ return reinterpret_cast<Email*>(userp)->get_data (ptr, size, nmemb);
}
static int
curl_debug_shim (CURL* curl, curl_infotype type, char* data, size_t size, void* userp)
{
- return reinterpret_cast<Emailer*>(userp)->debug (curl, type, data, size);
+ return reinterpret_cast<Email*>(userp)->debug (curl, type, data, size);
}
size_t
-Emailer::get_data (void* ptr, size_t size, size_t nmemb)
+Email::get_data(void* ptr, size_t size, size_t nmemb)
{
size_t const t = min (_email.length() - _offset, size * nmemb);
memcpy (ptr, _email.substr (_offset, t).c_str(), t);
@@ -109,7 +109,7 @@ Emailer::get_data (void* ptr, size_t size, size_t nmemb)
void
-Emailer::send (string server, int port, EmailProtocol protocol, string user, string password)
+Email::send(string server, int port, EmailProtocol protocol, string user, string password)
{
char date_buffer[128];
time_t now = time (0);
@@ -171,8 +171,7 @@ Emailer::send (string server, int port, EmailProtocol protocol, string user, str
}
bio = BIO_push (b64, bio);
- ArrayData data (i.file);
- BIO_write (bio, data.data(), data.size());
+ BIO_write(bio, i.file.data(), i.file.size());
(void) BIO_flush (bio);
char* out;
@@ -247,7 +246,7 @@ Emailer::send (string server, int port, EmailProtocol protocol, string user, str
string
-Emailer::address_list(vector<string> addresses)
+Email::address_list(vector<string> addresses)
{
string o;
for (auto i: addresses) {
@@ -259,7 +258,7 @@ Emailer::address_list(vector<string> addresses)
int
-Emailer::debug (CURL *, curl_infotype type, char* data, size_t size)
+Email::debug(CURL *, curl_infotype type, char* data, size_t size)
{
if (type == CURLINFO_TEXT) {
_notes += string (data, size);
@@ -273,7 +272,7 @@ Emailer::debug (CURL *, curl_infotype type, char* data, size_t size)
string
-Emailer::encode_rfc1342 (string subject)
+Email::encode_rfc1342(string subject)
{
auto b64 = BIO_new(BIO_f_base64());
if (!b64) {
diff --git a/src/lib/emailer.h b/src/lib/email.h
index 78942ad1e..36398bfd8 100644
--- a/src/lib/emailer.h
+++ b/src/lib/email.h
@@ -23,13 +23,14 @@
#include <boost/scoped_array.hpp>
-class Emailer
+class Email
{
public:
- Emailer(std::string from, std::vector<std::string> to, std::string subject, std::string body);
+ Email(std::string from, std::vector<std::string> to, std::string subject, std::string body);
void add_cc (std::string cc);
void add_bcc (std::string bcc);
+ /** Add attachment, copying the contents of the file into memory */
void add_attachment (boost::filesystem::path file, std::string name, std::string mime_type);
void send (std::string server, int port, EmailProtocol protocol, std::string user = "", std::string password = "");
@@ -61,7 +62,7 @@ private:
std::vector<std::string> _bcc;
struct Attachment {
- boost::filesystem::path file;
+ dcp::ArrayData file;
std::string name;
std::string mime_type;
};
diff --git a/src/lib/kdm_cli.cc b/src/lib/kdm_cli.cc
index 4e3f9ccb7..e4fabe1a6 100644
--- a/src/lib/kdm_cli.cc
+++ b/src/lib/kdm_cli.cc
@@ -27,7 +27,7 @@
#include "cinema.h"
#include "config.h"
#include "dkdm_wrapper.h"
-#include "emailer.h"
+#include "email.h"
#include "exceptions.h"
#include "film.h"
#include "kdm_with_metadata.h"
@@ -609,7 +609,7 @@ try
if (list_cinemas) {
auto cinemas = Config::instance()->cinemas ();
for (auto i: cinemas) {
- out (String::compose("%1 (%2)", i->name, Emailer::address_list (i->emails)));
+ out (String::compose("%1 (%2)", i->name, Email::address_list(i->emails)));
}
return {};
}
diff --git a/src/lib/kdm_with_metadata.cc b/src/lib/kdm_with_metadata.cc
index 10054f01e..f7ff84435 100644
--- a/src/lib/kdm_with_metadata.cc
+++ b/src/lib/kdm_with_metadata.cc
@@ -23,7 +23,7 @@
#include "config.h"
#include "cross.h"
#include "dcpomatic_log.h"
-#include "emailer.h"
+#include "email.h"
#include "kdm_with_metadata.h"
#include "screen.h"
#include "util.h"
@@ -238,14 +238,13 @@ send_emails (
auto subject = substitute_variables(config->kdm_subject());
auto body = substitute_variables(config->kdm_email());
- string screens;
+ vector<string> screens;
for (auto kdm: kdms_for_cinema) {
- auto screen_name = kdm->get('s');
- if (screen_name) {
- screens += *screen_name + ", ";
+ if (auto screen_name = kdm->get('s')) {
+ screens.push_back(*screen_name);
}
}
- boost::algorithm::replace_all (body, "$SCREENS", screens.substr (0, screens.length() - 2));
+ boost::algorithm::replace_all(body, "$SCREENS", screen_names_to_string(screens));
auto emails = first->emails();
std::copy(extra_addresses.begin(), extra_addresses.end(), std::back_inserter(emails));
@@ -253,7 +252,7 @@ send_emails (
continue;
}
- Emailer email (config->kdm_from(), { emails.front() }, subject, body);
+ Email email(config->kdm_from(), { emails.front() }, subject, body);
/* Use CC for the second and subsequent email addresses, so we seem less spammy (#2310) */
for (auto cc = std::next(emails.begin()); cc != emails.end(); ++cc) {
@@ -268,8 +267,9 @@ send_emails (
}
email.add_attachment (zip_file, container_name_format.get(first->name_values(), ".zip"), "application/zip");
+ dcp::filesystem::remove(zip_file);
- auto log_details = [](Emailer& email) {
+ auto log_details = [](Email& email) {
dcpomatic_log->log("Email content follows", LogEntry::TYPE_DEBUG_EMAIL);
dcpomatic_log->log(email.email(), LogEntry::TYPE_DEBUG_EMAIL);
dcpomatic_log->log("Email session follows", LogEntry::TYPE_DEBUG_EMAIL);
@@ -279,13 +279,10 @@ send_emails (
try {
email.send (config->mail_server(), config->mail_port(), config->mail_protocol(), config->mail_user(), config->mail_password());
} catch (...) {
- dcp::filesystem::remove(zip_file);
log_details (email);
throw;
}
log_details (email);
-
- dcp::filesystem::remove(zip_file);
}
}
diff --git a/src/lib/send_notification_email_job.cc b/src/lib/send_notification_email_job.cc
index f40e8fefa..a2f3016f9 100644
--- a/src/lib/send_notification_email_job.cc
+++ b/src/lib/send_notification_email_job.cc
@@ -22,7 +22,7 @@
#include "send_notification_email_job.h"
#include "exceptions.h"
#include "config.h"
-#include "emailer.h"
+#include "email.h"
#include "compose.hpp"
#include "i18n.h"
@@ -71,7 +71,7 @@ SendNotificationEmailJob::run ()
}
set_progress_unknown ();
- Emailer email (config->notification_from(), { config->notification_to() }, config->notification_subject(), _body);
+ Email email(config->notification_from(), { config->notification_to() }, config->notification_subject(), _body);
for (auto i: config->notification_cc()) {
email.add_cc (i);
}
diff --git a/src/lib/send_problem_report_job.cc b/src/lib/send_problem_report_job.cc
index 34822b156..9569aca3b 100644
--- a/src/lib/send_problem_report_job.cc
+++ b/src/lib/send_problem_report_job.cc
@@ -26,7 +26,7 @@
#include "film.h"
#include "log.h"
#include "version.h"
-#include "emailer.h"
+#include "email.h"
#include "environment_info.h"
#include <libxml++/libxml++.h>
@@ -108,8 +108,8 @@ SendProblemReportJob::run ()
body += "---<8----\n";
}
- Emailer emailer (_from, {"carl@dcpomatic.com"}, "DCP-o-matic problem report", body);
- emailer.send ("main.carlh.net", 2525, EmailProtocol::STARTTLS);
+ Email email(_from, {"carl@dcpomatic.com"}, "DCP-o-matic problem report", body);
+ email.send("main.carlh.net", 2525, EmailProtocol::STARTTLS);
set_progress (1);
set_state (FINISHED_OK);
diff --git a/src/lib/util.cc b/src/lib/util.cc
index 01a7f0248..ef15b90e5 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -1120,7 +1120,6 @@ word_wrap(string input, int columns)
}
-
#ifdef DCPOMATIC_GROK
void
setup_grok_library_path()
@@ -1147,3 +1146,31 @@ setup_grok_library_path()
setenv("LD_LIBRARY_PATH", new_path.c_str(), 1);
}
#endif
+
+string
+screen_names_to_string(vector<string> names)
+{
+ if (names.empty()) {
+ return {};
+ }
+
+ auto number = [](string const& s) {
+ return s.find_first_not_of("0123456789") == string::npos;
+ };
+
+ if (std::find_if(names.begin(), names.end(), [number](string const& s) { return !number(s); }) == names.end()) {
+ std::sort(names.begin(), names.end(), [](string const& a, string const& b) {
+ return dcp::raw_convert<int>(a) < dcp::raw_convert<int>(b);
+ });
+ } else {
+ std::sort(names.begin(), names.end());
+ }
+
+ string result;
+ for (auto const& name: names) {
+ result += name + ", ";
+ }
+
+ return result.substr(0, result.length() - 2);
+}
+
diff --git a/src/lib/util.h b/src/lib/util.h
index b85cf0a33..4f64369d3 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -114,4 +114,6 @@ number_attribute(cxml::ConstNodePtr node, std::string name1, std::string name2)
return *value;
}
+extern std::string screen_names_to_string(std::vector<std::string> names);
+
#endif
diff --git a/src/lib/wscript b/src/lib/wscript
index 5bae3e0d1..878b503a8 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -87,7 +87,7 @@ sources = """
dkdm_recipient.cc
dkdm_wrapper.cc
dolby_cp750.cc
- emailer.cc
+ email.cc
empty.cc
encoder.cc
encode_server.cc
diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc
index f65037c2f..d516c6f5b 100644
--- a/src/tools/dcpomatic.cc
+++ b/src/tools/dcpomatic.cc
@@ -72,7 +72,7 @@
#include "lib/dcpomatic_log.h"
#include "lib/dcpomatic_socket.h"
#include "lib/dkdm_wrapper.h"
-#include "lib/emailer.h"
+#include "lib/email.h"
#include "lib/encode_server_finder.h"
#include "lib/exceptions.h"
#include "lib/ffmpeg_encoder.h"
@@ -1146,9 +1146,9 @@ private:
error_dialog (this, _("You must enter a valid email address when sending translations, "
"otherwise the DCP-o-matic maintainers cannot credit you or contact you with questions."));
} else {
- Emailer emailer(dialog.email(), { "carl@dcpomatic.com" }, "DCP-o-matic translations", body);
+ Email email(dialog.email(), { "carl@dcpomatic.com" }, "DCP-o-matic translations", body);
try {
- emailer.send ("main.carlh.net", 2525, EmailProtocol::STARTTLS);
+ email.send("main.carlh.net", 2525, EmailProtocol::STARTTLS);
} catch (NetworkError& e) {
error_dialog (this, _("Could not send translations"), std_to_wx(e.what()));
}
diff --git a/src/wx/audio_panel.cc b/src/wx/audio_panel.cc
index d7deeec41..34923fb02 100644
--- a/src/wx/audio_panel.cc
+++ b/src/wx/audio_panel.cc
@@ -196,6 +196,7 @@ AudioPanel::film_changed (FilmProperty property)
case FilmProperty::AUDIO_PROCESSOR:
_mapping->set_output_channels (_parent->film()->audio_output_names ());
setup_peak ();
+ setup_sensitivity();
break;
case FilmProperty::VIDEO_FRAME_RATE:
setup_description ();
diff --git a/src/wx/dkdm_dialog.cc b/src/wx/dkdm_dialog.cc
index 8da3090c5..82c2e3598 100644
--- a/src/wx/dkdm_dialog.cc
+++ b/src/wx/dkdm_dialog.cc
@@ -178,7 +178,7 @@ DKDMDialog::make_clicked ()
return;
}
- auto result = _output->make (kdms, film->name(), bind(&DKDMDialog::confirm_overwrite, this, _1));
+ auto result = _output->make(kdms, film->dcp_name(), bind(&DKDMDialog::confirm_overwrite, this, _1));
if (result.first) {
JobManager::instance()->add (result.first);
}
diff --git a/src/wx/dolby_doremi_certificate_panel.cc b/src/wx/dolby_doremi_certificate_panel.cc
index d290535c2..4d8845402 100644
--- a/src/wx/dolby_doremi_certificate_panel.cc
+++ b/src/wx/dolby_doremi_certificate_panel.cc
@@ -117,11 +117,21 @@ static void
try_ims(vector<Location>& locations, string prefix, string serial)
{
locations.push_back({
+ String::compose("%1%2xxx/Dolby-IMS1000-%3.dcicerts.zip", prefix, serial.substr(0, 3), serial),
+ String::compose("Dolby-IMS1000-%1.cert.sha256.pem", serial)
+ });
+
+ locations.push_back({
String::compose("%1%2xxx/Dolby-IMS2000-%3.dcicerts.zip", prefix, serial.substr(0, 3), serial),
String::compose("Dolby-IMS2000-%1.cert.sha256.pem", serial)
});
locations.push_back({
+ String::compose("%1%2xxx/cert_Dolby-IMS3000-%3-SMPTE.zip", prefix, serial.substr(0, 3), serial),
+ String::compose("cert_Dolby-IMS3000-%1-SMPTE.pem", serial)
+ });
+
+ locations.push_back({
String::compose("%1%2xxx/ims-%3.dcicerts.zip", prefix, serial.substr(0, 3), serial),
String::compose("ims-%1.cert.sha256.pem", serial)
});
diff --git a/src/wx/full_config_dialog.cc b/src/wx/full_config_dialog.cc
index 66dbae63e..e3ea91224 100644
--- a/src/wx/full_config_dialog.cc
+++ b/src/wx/full_config_dialog.cc
@@ -52,7 +52,7 @@
#include "lib/config.h"
#include "lib/cross.h"
#include "lib/dcp_content_type.h"
-#include "lib/emailer.h"
+#include "lib/email.h"
#include "lib/exceptions.h"
#include "lib/filter.h"
#include "lib/log.h"
@@ -983,7 +983,7 @@ private:
return;
}
- Emailer emailer(
+ Email email(
wx_to_std(dialog.from()),
{ wx_to_std(dialog.to()) },
wx_to_std(_("DCP-o-matic test email")),
@@ -991,7 +991,7 @@ private:
);
auto config = Config::instance();
try {
- emailer.send(config->mail_server(), config->mail_port(), config->mail_protocol(), config->mail_user(), config->mail_password());
+ email.send(config->mail_server(), config->mail_port(), config->mail_protocol(), config->mail_user(), config->mail_password());
} catch (NetworkError& e) {
error_dialog(_panel, std_to_wx(e.summary()), std_to_wx(e.detail().get_value_or("")));
return;
diff --git a/src/wx/kdm_dialog.cc b/src/wx/kdm_dialog.cc
index c88a1ac40..5ab13b4ce 100644
--- a/src/wx/kdm_dialog.cc
+++ b/src/wx/kdm_dialog.cc
@@ -227,7 +227,7 @@ KDMDialog::make_clicked ()
return;
}
- auto result = _output->make (kdms, film->name(), bind (&KDMDialog::confirm_overwrite, this, _1));
+ auto result = _output->make(kdms, film->dcp_name(), bind (&KDMDialog::confirm_overwrite, this, _1));
if (result.first) {
JobManager::instance()->add (result.first);
}
diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc
index 4683769d4..38e9de4ee 100644
--- a/src/wx/timeline.cc
+++ b/src/wx/timeline.cc
@@ -109,6 +109,7 @@ Timeline::Timeline(wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, Fi
_main_canvas->Bind (wxEVT_RIGHT_DOWN, boost::bind (&Timeline::right_down, this, _1));
_main_canvas->Bind (wxEVT_MOTION, boost::bind (&Timeline::mouse_moved, this, _1));
_main_canvas->Bind (wxEVT_SIZE, boost::bind (&Timeline::resized, this));
+ _main_canvas->Bind (wxEVT_MOUSEWHEEL, boost::bind(&Timeline::mouse_wheel_turned, this, _1));
_main_canvas->Bind (wxEVT_SCROLLWIN_TOP, boost::bind (&Timeline::scrolled, this, _1));
_main_canvas->Bind (wxEVT_SCROLLWIN_BOTTOM, boost::bind (&Timeline::scrolled, this, _1));
_main_canvas->Bind (wxEVT_SCROLLWIN_LINEUP, boost::bind (&Timeline::scrolled, this, _1));
@@ -133,6 +134,50 @@ Timeline::Timeline(wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, Fi
void
+Timeline::mouse_wheel_turned(wxMouseEvent& event)
+{
+ auto const rotation = event.GetWheelRotation();
+
+ if (event.ControlDown()) {
+ /* On my mouse one click of the scroll wheel is 120, and it's -ve when
+ * scrolling the wheel towards me.
+ */
+ auto const scale = rotation > 0 ?
+ (1.0 / (rotation / 90.0)) :
+ (-rotation / 90.0);
+
+ int before_start_x;
+ int before_start_y;
+ _main_canvas->GetViewStart(&before_start_x, &before_start_y);
+
+ auto const before_pps = _pixels_per_second.get_value_or(1);
+ auto const before_pos = _last_mouse_wheel_x && *_last_mouse_wheel_x == event.GetX() ?
+ *_last_mouse_wheel_time :
+ (before_start_x * _x_scroll_rate + event.GetX()) / before_pps;
+
+ set_pixels_per_second(before_pps * scale);
+ setup_scrollbars();
+
+ auto after_left = std::max(0.0, before_pos * _pixels_per_second.get_value_or(1) - event.GetX());
+ _main_canvas->Scroll(after_left / _x_scroll_rate, before_start_y);
+ _labels_canvas->Scroll(0, before_start_y);
+ Refresh();
+
+ if (!_last_mouse_wheel_x || *_last_mouse_wheel_x != event.GetX()) {
+ _last_mouse_wheel_x = event.GetX();
+ _last_mouse_wheel_time = before_pos;
+ }
+ } else if (event.ShiftDown()) {
+ int before_start_x;
+ int before_start_y;
+ _main_canvas->GetViewStart(&before_start_x, &before_start_y);
+ auto const width = _main_canvas->GetSize().GetWidth();
+ _main_canvas->Scroll(std::max(0.0, before_start_x - rotation * 100.0 / width), before_start_y);
+ }
+}
+
+
+void
Timeline::update_playhead ()
{
Refresh ();
diff --git a/src/wx/timeline.h b/src/wx/timeline.h
index 2485e835f..621609fa7 100644
--- a/src/wx/timeline.h
+++ b/src/wx/timeline.h
@@ -110,6 +110,7 @@ private:
void set_pixels_per_track (int h);
void zoom_all ();
void update_playhead ();
+ void mouse_wheel_turned(wxMouseEvent& event);
std::shared_ptr<TimelineView> event_to_view (wxMouseEvent &);
TimelineContentViewList selected_views () const;
@@ -143,6 +144,8 @@ private:
int _pixels_per_track;
bool _first_resize;
wxTimer _timer;
+ boost::optional<int> _last_mouse_wheel_x;
+ boost::optional<double> _last_mouse_wheel_time;
static double const _minimum_pixels_per_second;
static int const _minimum_pixels_per_track;
diff --git a/test/util_test.cc b/test/util_test.cc
index 49d0b3bc2..afcc4cfc9 100644
--- a/test/util_test.cc
+++ b/test/util_test.cc
@@ -155,3 +155,14 @@ BOOST_AUTO_TEST_CASE(word_wrap_test)
BOOST_CHECK(word_wrap("hello this is a longer bit of text and it should be word-wrapped", 31) == string{"hello this is a longer bit of \ntext and it should be word-\nwrapped\n"});
BOOST_CHECK_EQUAL(word_wrap("hellocan'twrapthissadly", 5), "hello\ncan't\nwrapt\nhissa\ndly\n");
}
+
+
+BOOST_AUTO_TEST_CASE(screen_names_to_string_test)
+{
+ BOOST_CHECK_EQUAL(screen_names_to_string({"1", "2", "3"}), "1, 2, 3");
+ BOOST_CHECK_EQUAL(screen_names_to_string({"3", "2", "1"}), "1, 2, 3");
+ BOOST_CHECK_EQUAL(screen_names_to_string({"39", "3", "10", "1", "2"}), "1, 2, 3, 10, 39");
+ BOOST_CHECK_EQUAL(screen_names_to_string({"Sheila", "Fred", "Jim"}), "Fred, Jim, Sheila");
+ BOOST_CHECK_EQUAL(screen_names_to_string({"Sheila", "Fred", "Jim", "1"}), "1, Fred, Jim, Sheila");
+}
+