Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add notes panel #1225

Merged
merged 1 commit into from Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file added icons/dark/32x32/document-edit.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/light/32x32/document-edit.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/oxygen/32x32/actions/document-edit.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions icons/resources.qrc
Expand Up @@ -9,6 +9,7 @@
<file>dark/32x32/color-picker.png</file>
<file>dark/32x32/cpu.png</file>
<file>dark/32x32/dialog-information.png</file>
<file>dark/32x32/document-edit.png</file>
<file>dark/32x32/document-open-recent.png</file>
<file>dark/32x32/document-open.png</file>
<file>dark/32x32/document-save.png</file>
Expand Down Expand Up @@ -42,6 +43,7 @@
<file>light/32x32/color-picker.png</file>
<file>light/32x32/cpu.png</file>
<file>light/32x32/dialog-information.png</file>
<file>light/32x32/document-edit.png</file>
<file>light/32x32/document-open-recent.png</file>
<file>light/32x32/document-open.png</file>
<file>light/32x32/document-save.png</file>
Expand Down Expand Up @@ -71,6 +73,7 @@
<file>oxygen/32x32/actions/list-add.png</file>
<file>oxygen/32x32/actions/list-remove.png</file>
<file>oxygen/32x32/actions/player-volume.png</file>
<file>oxygen/32x32/actions/document-edit.png</file>
<file>oxygen/32x32/actions/document-open-recent.png</file>
<file>oxygen/32x32/actions/document-open.png</file>
<file>oxygen/32x32/actions/document-save.png</file>
Expand Down
129 changes: 129 additions & 0 deletions src/docks/notesdock.cpp
@@ -0,0 +1,129 @@
/*
* Copyright (c) 2022 Meltytech, LLC
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "notesdock.h"

#include <Logger.h>
#include "mltcontroller.h"
#include "qmltypes/qmlutilities.h"
#include "qmltypes/qmlview.h"

#include <QAction>
#include <QDir>
#include <QIcon>
#include <QQuickItem>
#include <QQuickView>
#include <QQmlContext>
#include <QQmlEngine>
#include <QUrl>

NotesDock::NotesDock(QWidget *parent) :
QDockWidget(tr("Notes"), parent),
m_qview(QmlUtilities::sharedEngine(), this),
m_blockUpdate(false)
{
LOG_DEBUG() << "begin";
setObjectName("NotesDock");
QIcon filterIcon = QIcon::fromTheme("document-edit", QIcon(":/icons/oxygen/32x32/actions/document-edit.png"));
setWindowIcon(filterIcon);
toggleViewAction()->setIcon(windowIcon());
m_qview.setFocusPolicy(Qt::StrongFocus);
m_qview.quickWindow()->setPersistentSceneGraph(false);
#ifdef Q_OS_MAC
setFeatures(DockWidgetClosable | DockWidgetMovable);
#else
m_qview.setAttribute(Qt::WA_AcceptTouchEvents);
#endif
QmlUtilities::setCommonProperties(m_qview.rootContext());
connect(m_qview.quickWindow(), SIGNAL(sceneGraphInitialized()), SLOT(resetQview()));

QDockWidget::setWidget(&m_qview);

LOG_DEBUG() << "end";
}

QString NotesDock::getText()
{
QVariant returnVal;
QMetaObject::invokeMethod(m_qview.rootObject(), "getText", Qt::DirectConnection, Q_RETURN_ARG(QVariant, returnVal));
return returnVal.toString();
}

void NotesDock::setText(const QString& text)
{
m_blockUpdate = true;
QMetaObject::invokeMethod(m_qview.rootObject(), "setText", Q_ARG(QVariant, QVariant(text)));
m_blockUpdate = false;
}

bool NotesDock::event(QEvent *event)
{
bool result = QDockWidget::event(event);
if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange) {
resetQview();
}
return result;
}

void NotesDock::keyPressEvent(QKeyEvent *event)
{
QDockWidget::keyPressEvent(event);
if (event->key() == Qt::Key_F) {
event->ignore();
} else if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right) {
event->accept();
}
}

void NotesDock::resetQview()
{
LOG_DEBUG() << "begin";
if (m_qview.status() != QQuickWidget::Null) {
QObject* root = m_qview.rootObject();
QObject::disconnect(root, SIGNAL(textChanged(const QString&)), this, SLOT(onTextChanged(const QString&)));
QObject::disconnect(root, SIGNAL(minWidthChanged()), this, SLOT(onMinimumWidthChanged()));
m_qview.setSource(QUrl(""));
}

QDir modulePath = QmlUtilities::qmlDir();
modulePath.cd("modules");
m_qview.engine()->addImportPath(modulePath.path());

m_qview.setResizeMode(QQuickWidget::SizeRootObjectToView);
m_qview.quickWindow()->setColor(palette().window().color());
QDir controlPath = QmlUtilities::qmlDir();
controlPath.cd("modules");
controlPath.cd("Shotcut");
controlPath.cd("Controls");
QUrl source = QUrl::fromLocalFile(controlPath.absoluteFilePath("RichTextEditor.qml"));
m_qview.setSource(source);

m_qview.rootObject()->setProperty("allowRichText", false);
QObject::connect(m_qview.rootObject(), SIGNAL(textModified(QString)), SLOT(onTextChanged(QString)));
QObject::connect(m_qview.rootObject(), SIGNAL(minWidthChanged()), SLOT(onMinimumWidthChanged()));
}

void NotesDock::onTextChanged(QString /*text*/)
{
if (!m_blockUpdate) {
emit modified();
}
}

void NotesDock::onMinimumWidthChanged() {
m_qview.setMinimumWidth(m_qview.rootObject()->property("minWidth").toInt());
}
52 changes: 52 additions & 0 deletions src/docks/notesdock.h
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2022 Meltytech, LLC
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef NOTESDOCK_H
#define NOTESDOCK_H

#include <QDockWidget>
#include <QObject>
#include <QQuickView>
#include <QQuickWidget>

class NotesDock : public QDockWidget
{
Q_OBJECT

public:
explicit NotesDock(QWidget *parent = 0);
QString getText();
void setText(const QString& text);

protected:
bool event(QEvent *event);
void keyPressEvent(QKeyEvent* event);

signals:
void modified();

private slots:
void resetQview();
void onTextChanged(QString);
void onMinimumWidthChanged();

private:
QQuickWidget m_qview;
bool m_blockUpdate;
};

#endif // NOTESDOCK_H
35 changes: 31 additions & 4 deletions src/mainwindow.cpp
Expand Up @@ -72,6 +72,7 @@
#include "dialogs/unlinkedfilesdialog.h"
#include "docks/keyframesdock.h"
#include "docks/markersdock.h"
#include "docks/notesdock.h"
#include "util.h"
#include "models/keyframesmodel.h"
#include "dialogs/listselectiondialog.h"
Expand Down Expand Up @@ -473,6 +474,14 @@ MainWindow::MainWindow()
connect(m_jobsDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onJobsDockTriggered(bool)));
connect(ui->actionJobs, SIGNAL(triggered()), this, SLOT(onJobsDockTriggered()));

m_notesDock = new NotesDock(this);
m_notesDock->hide();
m_notesDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_0));
ui->menuView->addAction(m_notesDock->toggleViewAction());
connect(m_notesDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onNotesDockTriggered(bool)));
connect(ui->actionNotes, SIGNAL(triggered()), this, SLOT(onNotesDockTriggered()));
connect(m_notesDock, SIGNAL(modified()), this, SLOT(onNoteModified()));

addDockWidget(Qt::LeftDockWidgetArea, m_propertiesDock);
addDockWidget(Qt::RightDockWidgetArea, m_recentDock);
addDockWidget(Qt::LeftDockWidgetArea, m_playlistDock);
Expand All @@ -482,10 +491,12 @@ MainWindow::MainWindow()
addDockWidget(Qt::RightDockWidgetArea, m_historyDock);
addDockWidget(Qt::LeftDockWidgetArea, m_encodeDock);
addDockWidget(Qt::RightDockWidgetArea, m_jobsDock);
addDockWidget(Qt::LeftDockWidgetArea, m_notesDock);
splitDockWidget(m_timelineDock, m_markersDock, Qt::Horizontal);
tabifyDockWidget(m_propertiesDock, m_playlistDock);
tabifyDockWidget(m_playlistDock, m_filtersDock);
tabifyDockWidget(m_filtersDock, m_encodeDock);
tabifyDockWidget(m_encodeDock, m_notesDock);
splitDockWidget(m_recentDock, findChild<QDockWidget*>("AudioWaveformDock"), Qt::Vertical);
splitDockWidget(audioMeterDock, m_recentDock, Qt::Horizontal);
tabifyDockWidget(m_recentDock, m_historyDock);
Expand Down Expand Up @@ -1445,6 +1456,7 @@ void MainWindow::open(QString url, const Mlt::Properties* properties, bool play)
setAudioChannels(MLT.audioChannels());
if (url.endsWith(".mlt") || url.endsWith(".xml")) {
setVideoModeMenu();
m_notesDock->setText(MLT.producer()->get(kShotcutProjectNote));
}

open(MLT.producer());
Expand Down Expand Up @@ -2971,6 +2983,14 @@ void MainWindow::onMarkersDockTriggered(bool checked)
}
}

void MainWindow::onNotesDockTriggered(bool checked)
{
if (checked) {
m_notesDock->show();
m_notesDock->raise();
}
}

void MainWindow::onPlaylistCreated()
{
if (!playlist() || playlist()->count() == 0) return;
Expand Down Expand Up @@ -3085,6 +3105,11 @@ void MainWindow::onMultitrackDurationChanged()
m_player->onDurationChanged();
}

void MainWindow::onNoteModified()
{
setWindowModified(true);
}

void MainWindow::onCutModified()
{
if (!playlist() && !multitrack()) {
Expand Down Expand Up @@ -3152,20 +3177,21 @@ void MainWindow::on_actionForum_triggered()
bool MainWindow::saveXML(const QString &filename, bool withRelativePaths)
{
bool result;
QString notes = m_notesDock->getText();
if (m_timelineDock->model()->rowCount() > 0) {
result = MLT.saveXML(filename, multitrack(), withRelativePaths);
result = MLT.saveXML(filename, multitrack(), withRelativePaths, nullptr, false, notes);
} else if (m_playlistDock->model()->rowCount() > 0) {
int in = MLT.producer()->get_in();
int out = MLT.producer()->get_out();
MLT.producer()->set_in_and_out(0, MLT.producer()->get_length() - 1);
result = MLT.saveXML(filename, playlist(), withRelativePaths);
result = MLT.saveXML(filename, playlist(), withRelativePaths, nullptr, false, notes);
MLT.producer()->set_in_and_out(in, out);
} else if (MLT.producer()) {
result = MLT.saveXML(filename, (MLT.isMultitrack() || MLT.isPlaylist())? MLT.savedProducer() : 0, withRelativePaths);
result = MLT.saveXML(filename, (MLT.isMultitrack() || MLT.isPlaylist())? MLT.savedProducer() : 0, withRelativePaths, nullptr, false, notes);
} else {
// Save an empty playlist, which is accepted by both MLT and Shotcut.
Mlt::Playlist playlist(MLT.profile());
result = MLT.saveXML(filename, &playlist, withRelativePaths);
result = MLT.saveXML(filename, &playlist, withRelativePaths, nullptr, false, notes);
}
return result;
}
Expand Down Expand Up @@ -4063,6 +4089,7 @@ void MainWindow::on_actionClose_triggered()
m_playlistDock->model()->close();
else
onMultitrackClosed();
m_notesDock->setText("");
m_player->enableTab(Player::SourceTabIndex, false);
MLT.purgeMemoryPool();
MLT.resetLocale();
Expand Down
4 changes: 4 additions & 0 deletions src/mainwindow.h
Expand Up @@ -49,6 +49,7 @@ class AutoSaveFile;
class QNetworkReply;
class KeyframesDock;
class MarkersDock;
class NotesDock;

class MainWindow : public QMainWindow
{
Expand Down Expand Up @@ -188,6 +189,7 @@ class MainWindow : public QMainWindow
QDateTime m_clipboardUpdatedAt;
QDateTime m_sourceUpdatedAt;
MarkersDock* m_markersDock;
NotesDock* m_notesDock;

#ifdef WITH_LIBLEAP
LeapListener m_leapListener;
Expand Down Expand Up @@ -238,6 +240,7 @@ private slots:
void onFiltersDockTriggered(bool checked = true);
void onKeyframesDockTriggered(bool checked = true);
void onMarkersDockTriggered(bool = true);
void onNotesDockTriggered(bool = true);
void onPlaylistCreated();
void onPlaylistLoaded();
void onPlaylistCleared();
Expand All @@ -247,6 +250,7 @@ private slots:
void onMultitrackClosed();
void onMultitrackModified();
void onMultitrackDurationChanged();
void onNoteModified();
void onCutModified();
void onProducerModified();
void onFilterModelChanged();
Expand Down
10 changes: 10 additions & 0 deletions src/mainwindow.ui
Expand Up @@ -305,6 +305,7 @@
<addaction name="actionHistory"/>
<addaction name="actionEncode"/>
<addaction name="actionJobs"/>
<addaction name="actionNotes"/>
<addaction name="dummyAction"/>
<addaction name="actionLayoutLogging"/>
<addaction name="actionLayoutEditing"/>
Expand Down Expand Up @@ -1132,6 +1133,15 @@
<string>Markers</string>
</property>
</action>
<action name="actionNotes">
<property name="icon">
<iconset theme="document-edit" resource="../icons/resources.qrc">
<normaloff>:/icons/oxygen/32x32/actions/document-edit.png</normaloff>:/icons/oxygen/32x32/actions/document-edit.png</iconset>
</property>
<property name="text">
<string>Notes</string>
</property>
</action>
<action name="actionPreview540">
<property name="checkable">
<bool>true</bool>
Expand Down
5 changes: 4 additions & 1 deletion src/mltcontroller.cpp
Expand Up @@ -463,7 +463,7 @@ void Controller::refreshConsumer(bool scrubAudio)
}

bool Controller::saveXML(const QString& filename, Service* service, bool withRelativePaths,
QTemporaryFile* tempFile, bool proxy)
QTemporaryFile* tempFile, bool proxy, QString projectNote)
{
QMutexLocker locker(&m_saveXmlMutex);
QFileInfo fi(filename);
Expand All @@ -474,6 +474,9 @@ bool Controller::saveXML(const QString& filename, Service* service, bool withRel
QString root = withRelativePaths? QDir::fromNativeSeparators(fi.absolutePath()) : "";
s.set(kShotcutProjectAudioChannels, m_audioChannels);
s.set(kShotcutProjectFolder, m_projectFolder.isEmpty()? 0 : 1);
if (!projectNote.isEmpty()) {
s.set(kShotcutProjectNote, projectNote.toUtf8().constData());
}
int ignore = s.get_int("ignore_points");
if (ignore)
s.set("ignore_points", 0);
Expand Down
2 changes: 1 addition & 1 deletion src/mltcontroller.h
Expand Up @@ -85,7 +85,7 @@ class Controller
virtual void seek(int position);
virtual void refreshConsumer(bool scrubAudio = false);
bool saveXML(const QString& filename, Service* service = nullptr, bool withRelativePaths = true,
QTemporaryFile* tempFile = nullptr, bool proxy = false);
QTemporaryFile* tempFile = nullptr, bool proxy = false, QString projectNote = QString());
QString XML(Service* service = nullptr, bool withProfile = false, bool withMetadata = false);
int consumerChanged();
void setProfile(const QString& profile_name);
Expand Down