Merge branch 'master' of https://github.com/olive-editor/olive into proxies
This commit is contained in:
+140
-13
@@ -1,6 +1,7 @@
|
||||
#include "preferencesdialog.h"
|
||||
|
||||
#include "io/config.h"
|
||||
#include "io/path.h"
|
||||
#include "playback/audio.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
@@ -24,6 +25,7 @@
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QApplication>
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
@@ -140,7 +142,6 @@ void PreferencesDialog::save() {
|
||||
|| config.preferred_audio_input != audio_input_devices->currentData().toString());
|
||||
config.preferred_audio_output = audio_output_devices->currentData().toString();
|
||||
config.preferred_audio_input = audio_input_devices->currentData().toString();
|
||||
qDebug() << "selected audio input" << audio_input_devices->currentData().toString();
|
||||
config.audio_rate = audio_sample_rate->currentData().toInt();
|
||||
|
||||
// the following settings may require a restart of Olive to take effect:
|
||||
@@ -149,13 +150,81 @@ void PreferencesDialog::save() {
|
||||
|
||||
if (config.effect_textbox_lines != effect_textbox_lines_field->value()) {
|
||||
needs_restart = true;
|
||||
config.effect_textbox_lines = effect_textbox_lines_field->value();
|
||||
}
|
||||
config.effect_textbox_lines = effect_textbox_lines_field->value();
|
||||
|
||||
if (config.use_software_fallback != use_software_fallbacks_checkbox->isChecked()) {
|
||||
needs_restart = true;
|
||||
config.use_software_fallback = use_software_fallbacks_checkbox->isChecked();
|
||||
}
|
||||
|
||||
if (config.language_file != language_combobox->currentData().toString()) {
|
||||
needs_restart = true;
|
||||
config.language_file = language_combobox->currentData().toString();
|
||||
}
|
||||
|
||||
if (config.thumbnail_resolution != thumbnail_res_spinbox->value()
|
||||
|| config.waveform_resolution != waveform_res_spinbox->value()) {
|
||||
// we're changing the size of thumbnails and waveforms, so let's delete them and regenerate them next start
|
||||
|
||||
needs_restart = true;
|
||||
|
||||
// delete nothing
|
||||
char delete_match = 0;
|
||||
|
||||
if (config.thumbnail_resolution != thumbnail_res_spinbox->value()) {
|
||||
// delete existing thumbnails
|
||||
config.thumbnail_resolution = thumbnail_res_spinbox->value();
|
||||
|
||||
// delete only thumbnails
|
||||
delete_match = 't';
|
||||
}
|
||||
|
||||
if (config.waveform_resolution != waveform_res_spinbox->value()) {
|
||||
// delete existing waveforms
|
||||
config.waveform_resolution = waveform_res_spinbox->value();
|
||||
|
||||
// if we're already deleting thumbnails
|
||||
if (delete_match == 't') {
|
||||
// delete all
|
||||
delete_match = 1;
|
||||
} else {
|
||||
// just delete waveforms
|
||||
delete_match = 'w';
|
||||
}
|
||||
}
|
||||
|
||||
if (delete_match != 0) {
|
||||
QDir preview_path(get_data_path() + "/previews");
|
||||
|
||||
if (delete_match == 1) {
|
||||
// indiscriminately delete everything
|
||||
preview_path.removeRecursively();
|
||||
} else {
|
||||
QStringList preview_file_list = preview_path.entryList(QDir::Files | QDir::NoDotAndDotDot);
|
||||
for (int i=0;i<preview_file_list.size();i++) {
|
||||
|
||||
const QString& preview_file_str = preview_file_list.at(i);
|
||||
|
||||
// use filename to determine whether this is a thumbnail or a waveform
|
||||
int identifier_char_index = qMax(0, preview_file_str.size()-2);
|
||||
|
||||
// find identifier char
|
||||
while (identifier_char_index >= 0
|
||||
&& preview_file_str.at(identifier_char_index) >= 48
|
||||
&& preview_file_str.at(identifier_char_index) <= 57) {
|
||||
identifier_char_index--;
|
||||
}
|
||||
|
||||
// thumbnails will have a 't' towards the end of the filenames, waveforms will have a 'w'
|
||||
// if they match the type of preview we're deleting, remove them
|
||||
if (preview_file_str.at(identifier_char_index) == delete_match) {
|
||||
QFile::remove(preview_path.filePath(preview_file_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
config.use_software_fallback = use_software_fallbacks_checkbox->isChecked();
|
||||
|
||||
// save keyboard shortcuts
|
||||
for (int i=0;i<key_shortcut_fields.size();i++) {
|
||||
@@ -299,49 +368,107 @@ void PreferencesDialog::setup_ui() {
|
||||
QVBoxLayout* verticalLayout = new QVBoxLayout(this);
|
||||
QTabWidget* tabWidget = new QTabWidget(this);
|
||||
|
||||
// row counter used to ease adding new rows
|
||||
int row = 0;
|
||||
|
||||
// General
|
||||
QWidget* general_tab = new QWidget(this);
|
||||
QGridLayout* general_layout = new QGridLayout(general_tab);
|
||||
|
||||
// General -> Language
|
||||
general_layout->addWidget(new QLabel(tr("Language:")), row, 0, 1, 1);
|
||||
|
||||
language_combobox = new QComboBox();
|
||||
|
||||
// add default language (en-US)
|
||||
language_combobox->addItem(QLocale::languageToString(QLocale("en-US").language()));
|
||||
|
||||
// add languages from file
|
||||
QDir translation_dir(QApplication::applicationDirPath().append("/ts"));
|
||||
QStringList translation_files = translation_dir.entryList({"*.qm"}, QDir::Files | QDir::NoDotAndDotDot);
|
||||
for (int i=0;i<translation_files.size();i++) {
|
||||
QString locale_full_path = translation_dir.filePath(translation_files.at(i));
|
||||
QFileInfo locale_file(translation_files.at(i));
|
||||
QString locale_file_basename = locale_file.baseName();
|
||||
QString locale_str = locale_file_basename.mid(locale_file_basename.lastIndexOf('_')+1);
|
||||
language_combobox->addItem(QLocale(locale_str).nativeLanguageName(), locale_full_path);
|
||||
|
||||
if (config.language_file == locale_full_path) {
|
||||
language_combobox->setCurrentIndex(language_combobox->count() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
general_layout->addWidget(language_combobox, row, 1, 1, 3);
|
||||
|
||||
row++;
|
||||
|
||||
// General -> Custom CSS
|
||||
general_layout->addWidget(new QLabel(tr("Custom CSS:"), this), 0, 0, 1, 1);
|
||||
general_layout->addWidget(new QLabel(tr("Custom CSS:"), this), row, 0, 1, 1);
|
||||
|
||||
custom_css_fn = new QLineEdit(general_tab);
|
||||
custom_css_fn->setText(config.css_path);
|
||||
general_layout->addWidget(custom_css_fn, 0, 1, 1, 1);
|
||||
general_layout->addWidget(custom_css_fn, row, 1, 1, 2);
|
||||
|
||||
QPushButton* custom_css_browse = new QPushButton(tr("Browse"), general_tab);
|
||||
connect(custom_css_browse, SIGNAL(clicked(bool)), this, SLOT(browse_css_file()));
|
||||
general_layout->addWidget(custom_css_browse, 0, 2, 1, 1);
|
||||
general_layout->addWidget(custom_css_browse, row, 3, 1, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// General -> Image Sequence Formats
|
||||
general_layout->addWidget(new QLabel(tr("Image sequence formats:"), this), 1, 0, 1, 1);
|
||||
general_layout->addWidget(new QLabel(tr("Image sequence formats:"), this), row, 0, 1, 1);
|
||||
|
||||
imgSeqFormatEdit = new QLineEdit(general_tab);
|
||||
|
||||
general_layout->addWidget(imgSeqFormatEdit, 1, 1, 1, 2);
|
||||
general_layout->addWidget(imgSeqFormatEdit, row, 1, 1, 3);
|
||||
|
||||
row++;
|
||||
|
||||
// General -> Audio Recording
|
||||
general_layout->addWidget(new QLabel(tr("Audio Recording:"), this), 2, 0, 1, 1);
|
||||
general_layout->addWidget(new QLabel(tr("Audio Recording:"), this), row, 0, 1, 1);
|
||||
|
||||
recordingComboBox = new QComboBox(general_tab);
|
||||
recordingComboBox->addItem(tr("Mono"));
|
||||
recordingComboBox->addItem(tr("Stereo"));
|
||||
general_layout->addWidget(recordingComboBox, 2, 1, 1, 2);
|
||||
general_layout->addWidget(recordingComboBox, row, 1, 1, 3);
|
||||
|
||||
row++;
|
||||
|
||||
// General -> Effect Textbox Lines
|
||||
general_layout->addWidget(new QLabel(tr("Effect Textbox Lines:"), this), 3, 0, 1, 1);
|
||||
general_layout->addWidget(new QLabel(tr("Effect Textbox Lines:"), this), row, 0, 1, 1);
|
||||
|
||||
effect_textbox_lines_field = new QSpinBox(general_tab);
|
||||
effect_textbox_lines_field->setMinimum(1);
|
||||
effect_textbox_lines_field->setValue(config.effect_textbox_lines);
|
||||
general_layout->addWidget(effect_textbox_lines_field, 3, 1, 1, 2);
|
||||
general_layout->addWidget(effect_textbox_lines_field, row, 1, 1, 3);
|
||||
|
||||
row++;
|
||||
|
||||
// General -> Thumbnail and Waveform Resolution
|
||||
general_layout->addWidget(new QLabel(tr("Thumbnail Resolution:"), this), row, 0, 1, 1);
|
||||
|
||||
thumbnail_res_spinbox = new QSpinBox(this);
|
||||
thumbnail_res_spinbox->setMinimum(1);
|
||||
thumbnail_res_spinbox->setMaximum(INT_MAX);
|
||||
thumbnail_res_spinbox->setValue(config.thumbnail_resolution);
|
||||
general_layout->addWidget(thumbnail_res_spinbox, row, 1, 1, 1);
|
||||
|
||||
general_layout->addWidget(new QLabel(tr("Waveform Resolution:"), this), row, 2, 1, 1);
|
||||
|
||||
waveform_res_spinbox = new QSpinBox(this);
|
||||
waveform_res_spinbox->setMinimum(1);
|
||||
waveform_res_spinbox->setMaximum(INT_MAX);
|
||||
waveform_res_spinbox->setValue(config.waveform_resolution);
|
||||
general_layout->addWidget(waveform_res_spinbox, row, 3, 1, 1);
|
||||
|
||||
|
||||
row++;
|
||||
|
||||
// General -> Use Software Fallbacks When Possible
|
||||
use_software_fallbacks_checkbox = new QCheckBox(general_tab);
|
||||
use_software_fallbacks_checkbox->setText(tr("Use Software Fallbacks When Possible"));
|
||||
use_software_fallbacks_checkbox->setChecked(config.use_software_fallback);
|
||||
general_layout->addWidget(use_software_fallbacks_checkbox, 4, 0, 1, 1);
|
||||
general_layout->addWidget(use_software_fallbacks_checkbox, row, 0, 1, 4);
|
||||
|
||||
tabWidget->addTab(general_tab, tr("General"));
|
||||
|
||||
|
||||
@@ -65,6 +65,9 @@ private:
|
||||
QComboBox* audio_output_devices;
|
||||
QComboBox* audio_input_devices;
|
||||
QComboBox* audio_sample_rate;
|
||||
QComboBox* language_combobox;
|
||||
QSpinBox* thumbnail_res_spinbox;
|
||||
QSpinBox* waveform_res_spinbox;
|
||||
|
||||
QVector<QAction*> key_shortcut_actions;
|
||||
QVector<QTreeWidgetItem*> key_shortcut_items;
|
||||
|
||||
+15
-1
@@ -48,7 +48,9 @@ Config::Config()
|
||||
seek_also_selects(false),
|
||||
effect_textbox_lines(3),
|
||||
use_software_fallback(false),
|
||||
center_timeline_timecodes(true)
|
||||
center_timeline_timecodes(true),
|
||||
waveform_resolution(64),
|
||||
thumbnail_resolution(120)
|
||||
{}
|
||||
|
||||
void Config::load(QString path) {
|
||||
@@ -179,6 +181,15 @@ void Config::load(QString path) {
|
||||
} else if (stream.name() == "PreferredAudioInput") {
|
||||
stream.readNext();
|
||||
preferred_audio_input = stream.text().toString();
|
||||
} else if (stream.name() == "LanguageFile") {
|
||||
stream.readNext();
|
||||
language_file = stream.text().toString();
|
||||
} else if (stream.name() == "ThumbnailResolution") {
|
||||
stream.readNext();
|
||||
thumbnail_resolution = stream.text().toInt();
|
||||
} else if (stream.name() == "WaveformResolution") {
|
||||
stream.readNext();
|
||||
waveform_resolution = stream.text().toInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,6 +254,9 @@ void Config::save(QString path) {
|
||||
stream.writeTextElement("CenterTimelineTimecodes", QString::number(center_timeline_timecodes));
|
||||
stream.writeTextElement("PreferredAudioOutput", preferred_audio_output);
|
||||
stream.writeTextElement("PreferredAudioInput", preferred_audio_input);
|
||||
stream.writeTextElement("LanguageFile", language_file);
|
||||
stream.writeTextElement("ThumbnailResolution", QString::number(thumbnail_resolution));
|
||||
stream.writeTextElement("WaveformResolution", QString::number(waveform_resolution));
|
||||
|
||||
stream.writeEndElement(); // configuration
|
||||
stream.writeEndDocument(); // doc
|
||||
|
||||
@@ -66,6 +66,9 @@ struct Config {
|
||||
bool center_timeline_timecodes;
|
||||
QString preferred_audio_output;
|
||||
QString preferred_audio_input;
|
||||
QString language_file;
|
||||
int waveform_resolution;
|
||||
int thumbnail_resolution;
|
||||
|
||||
void load(QString path);
|
||||
void save(QString path);
|
||||
|
||||
+16
-2
@@ -431,10 +431,24 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
|
||||
}
|
||||
}
|
||||
if (cancelled) return false;
|
||||
} else if (stream.isStartElement() && (stream.name() == "effect" || stream.name() == "opening" || stream.name() == "closing")) {
|
||||
} else if (stream.isStartElement()
|
||||
&& (stream.name() == "effect"
|
||||
|| stream.name() == "opening"
|
||||
|| stream.name() == "closing")) {
|
||||
// "opening" and "closing" are backwards compatibility code
|
||||
load_effect(stream, c);
|
||||
}
|
||||
} else if (stream.name() == "marker" && stream.isStartElement()) {
|
||||
Marker m;
|
||||
for (int j=0;j<stream.attributes().size();j++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(j);
|
||||
if (attr.name() == "frame") {
|
||||
m.frame = attr.value().toLong();
|
||||
} else if (attr.name() == "name") {
|
||||
m.name = attr.value().toString();
|
||||
}
|
||||
}
|
||||
c->markers.append(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cancelled) return false;
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
||||
#define WAVEFORM_RESOLUTION 64
|
||||
#define THUMBNAIL_RESOLUTION 120
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
@@ -266,7 +263,7 @@ void PreviewGenerator::generate_waveform() {
|
||||
if (s != nullptr) {
|
||||
if (fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (!s->preview_done) {
|
||||
int dstH = THUMBNAIL_RESOLUTION;
|
||||
int dstH = config.thumbnail_resolution;
|
||||
int dstW = qRound(dstH * (float(temp_frame->width)/float(temp_frame->height)));
|
||||
uint8_t* data = new uint8_t[size_t(dstW*dstH*4)];
|
||||
|
||||
@@ -305,7 +302,7 @@ void PreviewGenerator::generate_waveform() {
|
||||
}
|
||||
media_lengths[packet->stream_index]++;
|
||||
} else if (fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
int interval = qFloor((temp_frame->sample_rate/WAVEFORM_RESOLUTION)/4)*4;
|
||||
int interval = qFloor((temp_frame->sample_rate/config.waveform_resolution)/4)*4;
|
||||
|
||||
AVFrame* swr_frame = av_frame_alloc();
|
||||
swr_frame->channel_layout = temp_frame->channel_layout;
|
||||
|
||||
+58
-4
@@ -51,6 +51,7 @@
|
||||
#include <QLayout>
|
||||
#include <QApplication>
|
||||
#include <QPushButton>
|
||||
#include <QTranslator>
|
||||
|
||||
MainWindow* mainWindow;
|
||||
|
||||
@@ -206,10 +207,18 @@ MainWindow::MainWindow(QWidget *parent, const QString &an) :
|
||||
}
|
||||
}
|
||||
|
||||
// load preferred language from file
|
||||
if (!config.language_file.isEmpty()
|
||||
&& QFileInfo::exists(config.language_file)) {
|
||||
QTranslator* translator = new QTranslator(this);
|
||||
translator->load(config.language_file);
|
||||
QApplication::installTranslator(translator);
|
||||
}
|
||||
|
||||
alloc_panels(this);
|
||||
|
||||
QStatusBar* statusBar = new QStatusBar(this);
|
||||
statusBar->showMessage("Welcome to " + appName);
|
||||
statusBar->showMessage(tr("Welcome to %1").arg(appName));
|
||||
setStatusBar(statusBar);
|
||||
|
||||
// populate menu bars
|
||||
@@ -743,6 +752,8 @@ void MainWindow::setup_menus() {
|
||||
full_screen->setProperty("id", "fullscreen");
|
||||
full_screen->setCheckable(true);
|
||||
|
||||
view_menu->addAction(tr("Full Screen Viewer"), this, SLOT(full_screen_viewer()))->setProperty("id", "fullscreenviewer");
|
||||
|
||||
// INITIALIZE PLAYBACK MENU
|
||||
|
||||
QMenu* playback_menu = menuBar->addMenu(tr("&Playback"));
|
||||
@@ -806,7 +817,11 @@ void MainWindow::setup_menus() {
|
||||
window_sequenceviewer_action->setCheckable(true);
|
||||
window_sequenceviewer_action->setData(reinterpret_cast<quintptr>(panel_sequence_viewer));
|
||||
|
||||
window_menu->addSeparator();
|
||||
window_menu->addSeparator();
|
||||
|
||||
window_menu->addAction(tr("Maximize Panel"), this, SLOT(maximize_panel()), QKeySequence("`"))->setProperty("id", "maximizepanel");
|
||||
|
||||
window_menu->addSeparator();
|
||||
|
||||
window_menu->addAction(tr("Reset to Default Layout"), this, SLOT(reset_layout()))->setProperty("id", "resetdefaultlayout");
|
||||
|
||||
@@ -1195,7 +1210,34 @@ void MainWindow::next_cut() {
|
||||
QDockWidget* focused_panel = get_focused_panel();
|
||||
if (sequence != nullptr && (panel_timeline == focused_panel || panel_sequence_viewer == focused_panel)) {
|
||||
panel_timeline->next_cut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::maximize_panel() {
|
||||
// toggles between normal state and a state of one panel being maximized
|
||||
if (temp_panel_state.isEmpty()) {
|
||||
// get currently hovered panel
|
||||
QDockWidget* focused_panel = get_focused_panel(true);
|
||||
|
||||
// if the mouse is in fact hovering over a panel
|
||||
if (focused_panel != nullptr) {
|
||||
// store the current state of panels
|
||||
temp_panel_state = saveState();
|
||||
|
||||
// remove all dock widgets (kind of painful having to do each individually)
|
||||
if (focused_panel != panel_project) removeDockWidget(panel_project);
|
||||
if (focused_panel != panel_effect_controls) removeDockWidget(panel_effect_controls);
|
||||
if (focused_panel != panel_timeline) removeDockWidget(panel_timeline);
|
||||
if (focused_panel != panel_sequence_viewer) removeDockWidget(panel_sequence_viewer);
|
||||
if (focused_panel != panel_footage_viewer) removeDockWidget(panel_footage_viewer);
|
||||
}
|
||||
} else {
|
||||
// we must be maximized, restore previous state
|
||||
restoreState(temp_panel_state);
|
||||
|
||||
// clear temp panel state for next maximize call
|
||||
temp_panel_state.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::preferences()
|
||||
@@ -1210,7 +1252,15 @@ void MainWindow::zoom_in_tracks() {
|
||||
}
|
||||
|
||||
void MainWindow::zoom_out_tracks() {
|
||||
panel_timeline->decrease_track_height();
|
||||
panel_timeline->decrease_track_height();
|
||||
}
|
||||
|
||||
void MainWindow::full_screen_viewer() {
|
||||
if (get_focused_panel() == panel_footage_viewer) {
|
||||
panel_footage_viewer->viewer_widget->set_fullscreen();
|
||||
} else {
|
||||
panel_sequence_viewer->viewer_widget->set_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::windowMenu_About_To_Be_Shown() {
|
||||
@@ -1595,6 +1645,10 @@ void MainWindow::toggle_panel_visibility() {
|
||||
QAction* action = static_cast<QAction*>(sender());
|
||||
QDockWidget* w = reinterpret_cast<QDockWidget*>(action->data().value<quintptr>());
|
||||
w->setVisible(!w->isVisible());
|
||||
|
||||
// layout has changed, we're no longer in maximized panel mode,
|
||||
// so we clear this byte array
|
||||
temp_panel_state.clear();
|
||||
}
|
||||
|
||||
void MainWindow::set_timecode_view() {
|
||||
|
||||
+6
-1
@@ -78,14 +78,16 @@ private slots:
|
||||
void prev_cut();
|
||||
void next_cut();
|
||||
|
||||
void maximize_panel();
|
||||
void reset_layout();
|
||||
|
||||
void preferences();
|
||||
|
||||
void zoom_in_tracks();
|
||||
|
||||
void zoom_out_tracks();
|
||||
|
||||
void full_screen_viewer();
|
||||
|
||||
void fileMenu_About_To_Be_Shown();
|
||||
void fileMenu_About_To_Hide();
|
||||
void editMenu_About_To_Be_Shown();
|
||||
@@ -204,6 +206,9 @@ private:
|
||||
bool enable_launch_with_project;
|
||||
|
||||
QString appName;
|
||||
|
||||
// used to store the panel state when one panel is maximized
|
||||
QByteArray temp_panel_state;
|
||||
};
|
||||
|
||||
extern MainWindow* mainWindow;
|
||||
|
||||
+2
-2
@@ -115,9 +115,9 @@ void update_ui(bool modified) {
|
||||
panel_graph_editor->update_panel();
|
||||
}
|
||||
|
||||
QDockWidget *get_focused_panel() {
|
||||
QDockWidget *get_focused_panel(bool force_hover) {
|
||||
QDockWidget* w = nullptr;
|
||||
if (config.hover_focus) {
|
||||
if (config.hover_focus || force_hover) {
|
||||
if (panel_project->underMouse()) {
|
||||
w = panel_project;
|
||||
} else if (panel_effect_controls->underMouse()) {
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ extern Timeline* panel_timeline;
|
||||
extern GraphEditor* panel_graph_editor;
|
||||
|
||||
void update_ui(bool modified);
|
||||
QDockWidget* get_focused_panel();
|
||||
QDockWidget* get_focused_panel(bool force_hover = false);
|
||||
void alloc_panels(QWidget *parent);
|
||||
void free_panels();
|
||||
void scroll_to_frame_internal(QScrollBar* bar, long frame, double zoom, int area_width);
|
||||
|
||||
+18
-5
@@ -130,7 +130,11 @@ Project::Project(QWidget *parent) :
|
||||
connect(toolbar_redo, SIGNAL(clicked(bool)), mainWindow, SLOT(redo()));
|
||||
toolbar->addWidget(toolbar_redo);
|
||||
|
||||
toolbar->addStretch();
|
||||
QLineEdit* toolbar_search = new QLineEdit();
|
||||
toolbar_search->setPlaceholderText(tr("Search media, markers, etc."));
|
||||
connect(toolbar_search, SIGNAL(textChanged(QString)), sorter, SLOT(update_search_filter(const QString&)));
|
||||
toolbar->addWidget(toolbar_search);
|
||||
|
||||
QPushButton* toolbar_tree_view = new QPushButton();
|
||||
QIcon icon6;
|
||||
icon6.addFile(QStringLiteral(":/icons/treeview.png"), QSize(), QIcon::Normal, QIcon::On);
|
||||
@@ -915,6 +919,13 @@ void Project::load_project(bool autorecovery) {
|
||||
ld.exec();
|
||||
}
|
||||
|
||||
void save_marker(QXmlStreamWriter& stream, const Marker& m) {
|
||||
stream.writeStartElement("marker");
|
||||
stream.writeAttribute("frame", QString::number(m.frame));
|
||||
stream.writeAttribute("name", m.name);
|
||||
stream.writeEndElement();
|
||||
}
|
||||
|
||||
void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex& parent) {
|
||||
for (int i=0;i<project_model.rowCount(parent);i++) {
|
||||
const QModelIndex& item = project_model.index(i, 0, parent);
|
||||
@@ -1049,6 +1060,11 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only,
|
||||
}
|
||||
}
|
||||
|
||||
// save markers
|
||||
for (int k=0;k<c->markers.size();k++) {
|
||||
save_marker(stream, c->markers.at(k));
|
||||
}
|
||||
|
||||
stream.writeStartElement("linked"); // linked
|
||||
for (int k=0;k<c->linked.size();k++) {
|
||||
stream.writeStartElement("link"); // link
|
||||
@@ -1067,10 +1083,7 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only,
|
||||
}
|
||||
}
|
||||
for (int j=0;j<s->markers.size();j++) {
|
||||
stream.writeStartElement("marker");
|
||||
stream.writeAttribute("frame", QString::number(s->markers.at(j).frame));
|
||||
stream.writeAttribute("name", s->markers.at(j).name);
|
||||
stream.writeEndElement();
|
||||
save_marker(stream, s->markers.at(j));
|
||||
}
|
||||
stream.writeEndElement();
|
||||
}
|
||||
|
||||
+30
-3
@@ -1426,7 +1426,14 @@ bool Timeline::snap_to_timeline(long* l, bool use_playhead, bool use_markers, bo
|
||||
} else if (c->get_closing_transition() != nullptr
|
||||
&& snap_to_point(c->timeline_out - c->get_closing_transition()->get_true_length(), l)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// try to snap to clip markers
|
||||
for (int j=0;j<c->markers.size();j++) {
|
||||
if (snap_to_point(c->markers.at(j).frame + c->timeline_in - c->clip_in, l)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1446,9 +1453,29 @@ void Timeline::set_marker() {
|
||||
marker_name = d.textValue();
|
||||
}
|
||||
|
||||
|
||||
if (add_marker) {
|
||||
undo_stack.push(new AddMarkerAction(sequence, sequence->playhead, marker_name));
|
||||
ComboAction* ca = new ComboAction();
|
||||
|
||||
// see if any clips are selected, and if so add a marker to them
|
||||
bool clip_mode = false;
|
||||
for (int i=0;i<sequence->clips.size();i++) {
|
||||
Clip* c = sequence->clips.at(i);
|
||||
if (c != nullptr
|
||||
&& is_clip_selected(c, true)) {
|
||||
ca->append(new AddMarkerAction(false,
|
||||
c,
|
||||
sequence->playhead - c->timeline_in + c->clip_in,
|
||||
marker_name));
|
||||
clip_mode = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if no clips are selected, we're adding a marker to the sequence
|
||||
if (!clip_mode) {
|
||||
ca->append(new AddMarkerAction(true, sequence, sequence->playhead, marker_name));
|
||||
}
|
||||
|
||||
undo_stack.push(ca);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <QMutex>
|
||||
#include <QVector>
|
||||
|
||||
#include "marker.h"
|
||||
|
||||
#define SKIP_TYPE_DISCARD 0
|
||||
#define SKIP_TYPE_SEEK 1
|
||||
|
||||
@@ -73,6 +75,9 @@ struct Clip
|
||||
bool maintain_audio_pitch;
|
||||
bool autoscale;
|
||||
|
||||
// markers
|
||||
QVector<Marker> markers;
|
||||
|
||||
// other variables (should be deep copied/duplicated in copy())
|
||||
QList<Effect*> effects;
|
||||
QVector<int> linked;
|
||||
|
||||
@@ -1 +1,18 @@
|
||||
#include "marker.h"
|
||||
|
||||
void draw_marker(QPainter &p, int x, int y, int bottom, bool selected, bool flipped) {
|
||||
const QPoint points[5] = {
|
||||
QPoint(x, bottom),
|
||||
QPoint(x + MARKER_SIZE, bottom - MARKER_SIZE),
|
||||
QPoint(x + MARKER_SIZE, y),
|
||||
QPoint(x - MARKER_SIZE, y),
|
||||
QPoint(x - MARKER_SIZE, bottom - MARKER_SIZE)
|
||||
};
|
||||
p.setPen(Qt::black);
|
||||
if (selected) {
|
||||
p.setBrush(QColor(208, 255, 208));
|
||||
} else {
|
||||
p.setBrush(QColor(128, 224, 128));
|
||||
}
|
||||
p.drawPolygon(points, 5);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
#ifndef MARKER_H
|
||||
#define MARKER_H
|
||||
|
||||
#define MARKER_SIZE 4
|
||||
|
||||
#include <QString>
|
||||
#include <QPainter>
|
||||
|
||||
struct Marker {
|
||||
long frame;
|
||||
QString name;
|
||||
};
|
||||
|
||||
void draw_marker(QPainter& p, int x, int y, int bottom, bool selected, bool flipped);
|
||||
|
||||
#endif // MARKER_H
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "projectfilter.h"
|
||||
|
||||
#include "project/media.h"
|
||||
#include "project/sequence.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@@ -15,17 +16,48 @@ bool ProjectFilter::get_show_sequences() {
|
||||
|
||||
void ProjectFilter::set_show_sequences(bool b) {
|
||||
show_sequences = b;
|
||||
invalidateFilter();
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void ProjectFilter::update_search_filter(const QString &s) {
|
||||
search_filter = s;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
bool ProjectFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const {
|
||||
// retrieve media object from index
|
||||
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
|
||||
Media* media = static_cast<Media*>(index.internalPointer());
|
||||
|
||||
// hide sequences if show_sequences is false
|
||||
if (!show_sequences) {
|
||||
// hide sequences if show_sequences is false
|
||||
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
|
||||
Media* media = static_cast<Media*>(index.internalPointer());
|
||||
if (media != nullptr && media->get_type() == MEDIA_TYPE_SEQUENCE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// filter by search filter string
|
||||
if (!search_filter.isEmpty()) {
|
||||
// search markers if media is a sequene
|
||||
bool marker_contains_search = false;
|
||||
|
||||
if (media->get_type() == MEDIA_TYPE_SEQUENCE) {
|
||||
Sequence* s = media->to_sequence();
|
||||
for (int i=0;i<s->markers.size();i++) {
|
||||
if (s->markers.at(i).name.contains(search_filter, Qt::CaseInsensitive)) {
|
||||
marker_contains_search = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hide any rows that don't contain the search string (unless it's a folder)
|
||||
if (!marker_contains_search
|
||||
&& media->get_type() != MEDIA_TYPE_FOLDER
|
||||
&& !media->get_name().contains(search_filter, Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
|
||||
}
|
||||
|
||||
+19
-1
@@ -7,13 +7,31 @@ class ProjectFilter : public QSortFilterProxyModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProjectFilter(QObject *parent = nullptr);
|
||||
|
||||
// are sequences visible
|
||||
bool get_show_sequences();
|
||||
|
||||
public slots:
|
||||
|
||||
// set whether sequences are visible
|
||||
void set_show_sequences(bool b);
|
||||
|
||||
// update search filter
|
||||
void update_search_filter(const QString& s);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
|
||||
|
||||
// function that filters whether rows are displayed or not
|
||||
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
|
||||
|
||||
private:
|
||||
|
||||
// internal variable for whether to show sequences
|
||||
bool show_sequences;
|
||||
|
||||
// search filter variable
|
||||
QString search_filter;
|
||||
|
||||
};
|
||||
|
||||
#endif // PROJECTFILTER_H
|
||||
|
||||
+20
-9
@@ -803,18 +803,23 @@ void SetAutoscaleAction::redo() {
|
||||
mainWindow->setWindowModified(true);
|
||||
}
|
||||
|
||||
AddMarkerAction::AddMarkerAction(Sequence* s, long t, QString n) :
|
||||
seq(s),
|
||||
AddMarkerAction::AddMarkerAction(bool is_sequence, void* s, long t, QString n) :
|
||||
is_sequence_internal(is_sequence),
|
||||
target(s),
|
||||
time(t),
|
||||
name(n),
|
||||
old_project_changed(mainWindow->isWindowModified())
|
||||
{}
|
||||
|
||||
void AddMarkerAction::undo() {
|
||||
QVector<Marker>& markers = is_sequence_internal ?
|
||||
static_cast<Sequence*>(target)->markers :
|
||||
static_cast<Clip*>(target)->markers;
|
||||
|
||||
if (index == -1) {
|
||||
seq->markers.removeLast();
|
||||
markers.removeLast();
|
||||
} else {
|
||||
seq->markers[index].name = old_name;
|
||||
markers[index].name = old_name;
|
||||
}
|
||||
|
||||
mainWindow->setWindowModified(old_project_changed);
|
||||
@@ -822,8 +827,13 @@ void AddMarkerAction::undo() {
|
||||
|
||||
void AddMarkerAction::redo() {
|
||||
index = -1;
|
||||
for (int i=0;i<seq->markers.size();i++) {
|
||||
if (seq->markers.at(i).frame == time) {
|
||||
|
||||
QVector<Marker>& markers = is_sequence_internal ?
|
||||
static_cast<Sequence*>(target)->markers :
|
||||
static_cast<Clip*>(target)->markers;
|
||||
|
||||
for (int i=0;i<markers.size();i++) {
|
||||
if (markers.at(i).frame == time) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
@@ -832,10 +842,11 @@ void AddMarkerAction::redo() {
|
||||
if (index == -1) {
|
||||
Marker m;
|
||||
m.frame = time;
|
||||
seq->markers.append(m);
|
||||
m.name = name;
|
||||
markers.append(m);
|
||||
} else {
|
||||
old_name = seq->markers.at(index).name;
|
||||
seq->markers[index].name = name;
|
||||
old_name = markers.at(index).name;
|
||||
markers[index].name = name;
|
||||
}
|
||||
|
||||
mainWindow->setWindowModified(true);
|
||||
|
||||
+3
-2
@@ -384,11 +384,12 @@ private:
|
||||
|
||||
class AddMarkerAction : public QUndoCommand {
|
||||
public:
|
||||
AddMarkerAction(Sequence* s, long t, QString n);
|
||||
AddMarkerAction(bool is_sequence, void* s, long t, QString n);
|
||||
void undo();
|
||||
void redo();
|
||||
private:
|
||||
Sequence* seq;
|
||||
bool is_sequence_internal;
|
||||
void* target;
|
||||
long time;
|
||||
QString name;
|
||||
QString old_name;
|
||||
|
||||
@@ -23,6 +23,7 @@ apps:
|
||||
- pulseaudio
|
||||
- home
|
||||
- removable-media
|
||||
- unity7
|
||||
|
||||
parts:
|
||||
olive:
|
||||
|
||||
+11
-29
@@ -20,7 +20,6 @@
|
||||
#define PLAYHEAD_SIZE 6
|
||||
#define LINE_MIN_PADDING 50
|
||||
#define SUBLINE_MIN_PADDING 50 // TODO play with this
|
||||
#define MARKER_SIZE 4
|
||||
|
||||
// used only if center_timeline_timecodes is FALSE
|
||||
#define TEXT_PADDING_FROM_LINE 4
|
||||
@@ -406,35 +405,18 @@ void TimelineHeader::paintEvent(QPaintEvent*) {
|
||||
// draw markers
|
||||
for (int i=0;i<viewer->seq->markers.size();i++) {
|
||||
const Marker& m = viewer->seq->markers.at(i);
|
||||
|
||||
int marker_x = getHeaderScreenPointFromFrame(m.frame);
|
||||
const QPoint points[5] = {
|
||||
QPoint(marker_x, height()-1),
|
||||
QPoint(marker_x + MARKER_SIZE, height() - MARKER_SIZE - 1),
|
||||
QPoint(marker_x + MARKER_SIZE, yoff),
|
||||
QPoint(marker_x - MARKER_SIZE, yoff),
|
||||
QPoint(marker_x - MARKER_SIZE, height() - MARKER_SIZE - 1)
|
||||
};
|
||||
/*const QPoint points[5] = {
|
||||
QPoint(marker_x, height()-1),
|
||||
QPoint(marker_x + MARKER_SIZE, height() - MARKER_SIZE - 1),
|
||||
QPoint(marker_x + MARKER_SIZE, yoff),
|
||||
QPoint(marker_x - MARKER_SIZE, yoff),
|
||||
QPoint(marker_x - MARKER_SIZE, height() - MARKER_SIZE - 1)
|
||||
};*/
|
||||
p.setPen(Qt::black);
|
||||
bool selected = false;
|
||||
for (int j=0;j<selected_markers.size();j++) {
|
||||
if (selected_markers.at(j) == i) {
|
||||
selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (selected) {
|
||||
p.setBrush(QColor(208, 255, 208));
|
||||
} else {
|
||||
p.setBrush(QColor(128, 224, 128));
|
||||
}
|
||||
p.drawPolygon(points, 5);
|
||||
|
||||
bool selected = false;
|
||||
for (int j=0;j<selected_markers.size();j++) {
|
||||
if (selected_markers.at(j) == i) {
|
||||
selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
draw_marker(p, marker_x, yoff, height()-1, selected, false);
|
||||
}
|
||||
|
||||
// draw playhead triangle
|
||||
|
||||
+53
-7
@@ -1244,6 +1244,8 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) {
|
||||
// slipping doesn't move the clips so we don't bother snapping for it
|
||||
for (int i=0;i<panel_timeline->ghosts.size();i++) {
|
||||
const Ghost& g = panel_timeline->ghosts.at(i);
|
||||
|
||||
// snap ghost's in point
|
||||
if (panel_timeline->trim_target == -1 || g.trim_in) {
|
||||
fm = g.old_in + frame_diff;
|
||||
if (panel_timeline->snap_to_timeline(&fm, true, true, true)) {
|
||||
@@ -1251,6 +1253,8 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// snap ghost's out point
|
||||
if (panel_timeline->trim_target == -1 || !g.trim_in) {
|
||||
fm = g.old_out + frame_diff;
|
||||
if (panel_timeline->snap_to_timeline(&fm, true, true, true)) {
|
||||
@@ -1258,6 +1262,19 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if the ghost is attached to a clip, snap its markers too
|
||||
if (panel_timeline->trim_target == -1 && g.clip >= 0) {
|
||||
Clip* c = sequence->clips.at(g.clip);
|
||||
for (int j=0;j<c->markers.size();j++) {
|
||||
long marker_real_time = c->markers.at(j).frame + c->timeline_in - c->clip_in;
|
||||
fm = marker_real_time + frame_diff;
|
||||
if (panel_timeline->snap_to_timeline(&fm, true, true, true)) {
|
||||
frame_diff = fm - marker_real_time;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2147,8 +2164,11 @@ void draw_waveform(Clip* clip, const FootageStream* ms, long media_length, QPain
|
||||
int divider = ms->audio_channels*2;
|
||||
int channel_height = clip_rect.height()/ms->audio_channels;
|
||||
|
||||
int last_waveform_index = -1;
|
||||
|
||||
for (int i=waveform_start;i<waveform_limit;i++) {
|
||||
int waveform_index = qFloor((((clip->clip_in + ((double) i/zoom))/media_length) * ms->audio_preview.size())/divider)*divider;
|
||||
if (last_waveform_index < 0) last_waveform_index = waveform_index;
|
||||
|
||||
if (clip->reverse) {
|
||||
waveform_index = ms->audio_preview.size() - waveform_index - (ms->audio_channels * 2);
|
||||
@@ -2156,21 +2176,35 @@ void draw_waveform(Clip* clip, const FootageStream* ms, long media_length, QPain
|
||||
|
||||
for (int j=0;j<ms->audio_channels;j++) {
|
||||
int mid = (config.rectified_waveforms) ? clip_rect.top()+channel_height*(j+1) : clip_rect.top()+channel_height*j+(channel_height/2);
|
||||
int offset = waveform_index+(j*2);
|
||||
|
||||
if ((offset + 1) < ms->audio_preview.size()) {
|
||||
qint8 min = (double)ms->audio_preview.at(offset) / 128.0 * (channel_height/2);
|
||||
qint8 max = (double)ms->audio_preview.at(offset+1) / 128.0 * (channel_height/2);
|
||||
int offset_range_start = last_waveform_index+(j*2);
|
||||
int offset_range_end = waveform_index+(j*2);
|
||||
|
||||
qint8 min = qint8(qRound(double(ms->audio_preview.at(offset_range_start)) / 128.0 * (channel_height/2)));
|
||||
qint8 max = qint8(qRound(double(ms->audio_preview.at(offset_range_start+1)) / 128.0 * (channel_height/2)));
|
||||
|
||||
if ((offset_range_end + 1) < ms->audio_preview.size()) {
|
||||
|
||||
// for waveform drawings, we get the maximum below 0 and maximum above 0 for this waveform range
|
||||
for (int k=offset_range_start+2;k<=offset_range_end;k+=2) {
|
||||
min = qMin(min, qint8(qRound(double(ms->audio_preview.at(k)) / 128.0 * (channel_height/2))));
|
||||
max = qMax(max, qint8(qRound(double(ms->audio_preview.at(k+1)) / 128.0 * (channel_height/2))));
|
||||
}
|
||||
|
||||
// draw waveforms
|
||||
if (config.rectified_waveforms) {
|
||||
|
||||
// rectified waveforms start from the bottom and draw upwards
|
||||
p->drawLine(clip_rect.left()+i, mid, clip_rect.left()+i, mid - (max - min));
|
||||
} else {
|
||||
|
||||
// non-rectified waveforms start from the center and draw outwards
|
||||
p->drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max);
|
||||
|
||||
}
|
||||
}/* else {
|
||||
qWarning() << "Tried to reach" << offset + 1 << ", limit:" << ms->audio_preview.size();
|
||||
}*/
|
||||
}
|
||||
}
|
||||
last_waveform_index = waveform_index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2394,6 +2428,18 @@ void TimelineWidget::paintEvent(QPaintEvent*) {
|
||||
}
|
||||
}
|
||||
|
||||
// draw clip markers
|
||||
for (int j=0;j<clip->markers.size();j++) {
|
||||
const Marker& m = clip->markers.at(j);
|
||||
|
||||
// convert marker time (in clip time) to sequence time
|
||||
long marker_time = m.frame + clip->timeline_in - clip->clip_in;
|
||||
int marker_x = panel_timeline->getTimelineScreenPointFromFrame(marker_time);
|
||||
if (marker_x > clip_rect.x() && marker_x < clip_rect.right()) {
|
||||
draw_marker(p, marker_x, clip_rect.bottom()-p.fontMetrics().height(), clip_rect.bottom(), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
// draw clip transitions
|
||||
draw_transition(p, clip, clip_rect, text_rect, TA_OPENING_TRANSITION);
|
||||
draw_transition(p, clip, clip_rect, text_rect, TA_CLOSING_TRANSITION);
|
||||
|
||||
+15
-7
@@ -93,7 +93,20 @@ void ViewerWidget::set_waveform_scroll(int s) {
|
||||
if (waveform) {
|
||||
waveform_scroll = s;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::set_fullscreen(int screen) {
|
||||
if (screen >= 0 && screen < QGuiApplication::screens().size()) {
|
||||
QScreen* selected_screen = QGuiApplication::screens().at(screen);
|
||||
window->showFullScreen();
|
||||
window->setGeometry(selected_screen->geometry());
|
||||
|
||||
// HACK: window seems to show with distorted texture on first showing, so we queue an update after it's shown
|
||||
QTimer::singleShot(100, window, SLOT(update()));
|
||||
} else {
|
||||
qCritical() << "Failed to find requested screen" << screen << "to set fullscreen to";
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::show_context_menu() {
|
||||
@@ -170,12 +183,7 @@ void ViewerWidget::fullscreen_menu_action(QAction *action) {
|
||||
if (action->data().isNull()) {
|
||||
window->hide();
|
||||
} else {
|
||||
QScreen* selected_screen = QGuiApplication::screens().at(action->data().toInt());
|
||||
window->showFullScreen();
|
||||
window->setGeometry(selected_screen->geometry());
|
||||
|
||||
// HACK: window seems to show with distorted texture on first showing, so we queue an update after it's shown
|
||||
QTimer::singleShot(100, window, SLOT(update()));
|
||||
set_fullscreen(action->data().toInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
void set_scroll(double x, double y);
|
||||
public slots:
|
||||
void set_waveform_scroll(int s);
|
||||
void set_fullscreen(int screen = 0);
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
|
||||
Reference in New Issue
Block a user