app: migrate all engine access to the C ABI facade (nm U _ZN5olive = 0)
Every app module now reaches liboakengine exclusively through oakengine_* C calls, EngineEventBridge subscriptions and app-side handle headers (cliphandle/keyframehandle/nodevaluehandle/oakvaluehelper). Direct C++ command construction, engine signal connect()s, and engine type usage in MOC-visible signatures are gone: 557 -> 0 undefined olive:: symbols in oak-editor.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
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 "common/colorcodingapp.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
QVector<Color> ColorCoding::colors = {
|
||||
Color(0.545f, 0.255f, 0.255f), Color(0.412f, 0.188f, 0.259f),
|
||||
Color(0.561f, 0.427f, 0.239f), Color(0.486f, 0.306f, 0.235f),
|
||||
Color(0.631f, 0.612f, 0.212f), Color(0.404f, 0.478f, 0.243f),
|
||||
Color(0.349f, 0.576f, 0.275f), Color(0.224f, 0.459f, 0.251f),
|
||||
Color(0.259f, 0.471f, 0.541f), Color(0.184f, 0.376f, 0.329f),
|
||||
Color(0.259f, 0.365f, 0.541f), Color(0.196f, 0.216f, 0.412f),
|
||||
Color(0.612f, 0.294f, 0.502f), Color(0.404f, 0.220f, 0.459f),
|
||||
Color(0.800f, 0.800f, 0.800f), Color(0.502f, 0.502f, 0.502f)
|
||||
};
|
||||
|
||||
const QVector<Color> &ColorCoding::standard_colors()
|
||||
{
|
||||
return colors;
|
||||
}
|
||||
|
||||
QString ColorCoding::get_color_name(int c)
|
||||
{
|
||||
switch (c) {
|
||||
case k_red: return QObject::tr("Red");
|
||||
case k_maroon: return QObject::tr("Maroon");
|
||||
case k_orange: return QObject::tr("Orange");
|
||||
case k_brown: return QObject::tr("Brown");
|
||||
case k_yellow: return QObject::tr("Yellow");
|
||||
case k_olive: return QObject::tr("Olive");
|
||||
case k_lime: return QObject::tr("Lime");
|
||||
case k_green: return QObject::tr("Green");
|
||||
case k_cyan: return QObject::tr("Cyan");
|
||||
case k_teal: return QObject::tr("Teal");
|
||||
case k_blue: return QObject::tr("Blue");
|
||||
case k_navy: return QObject::tr("Navy");
|
||||
case k_pink: return QObject::tr("Pink");
|
||||
case k_purple: return QObject::tr("Purple");
|
||||
case k_silver: return QObject::tr("Silver");
|
||||
case k_gray: return QObject::tr("Gray");
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
Color ColorCoding::get_color(int c)
|
||||
{
|
||||
return colors.at(c);
|
||||
}
|
||||
|
||||
Qt::GlobalColor ColorCoding::get_ui_selector_color(const Color &c)
|
||||
{
|
||||
if (c.get_rough_luminance() > 0.40f) {
|
||||
return Qt::black;
|
||||
} else {
|
||||
return Qt::white;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
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 OAK_COLORCODINGAPP_H
|
||||
#define OAK_COLORCODINGAPP_H
|
||||
|
||||
#include <olive/core/core.h>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using namespace core;
|
||||
|
||||
/**
|
||||
* @brief App-side ColorCoding (moved from engine/ui/colorcoding.h)
|
||||
*
|
||||
* Provides the same static color-label mapping as the engine version but
|
||||
* without QObject inheritance (no moc symbols). Only the static methods
|
||||
* used by app code are included.
|
||||
*/
|
||||
class ColorCoding {
|
||||
public:
|
||||
enum Code {
|
||||
k_red,
|
||||
k_maroon,
|
||||
k_orange,
|
||||
k_brown,
|
||||
k_yellow,
|
||||
k_olive,
|
||||
k_lime,
|
||||
k_green,
|
||||
k_cyan,
|
||||
k_teal,
|
||||
k_blue,
|
||||
k_navy,
|
||||
k_pink,
|
||||
k_purple,
|
||||
k_silver,
|
||||
k_gray
|
||||
};
|
||||
|
||||
static QString get_color_name(int c);
|
||||
|
||||
static Color get_color(int c);
|
||||
|
||||
static Qt::GlobalColor get_ui_selector_color(const Color &c);
|
||||
|
||||
static const QVector<Color> &standard_colors();
|
||||
|
||||
private:
|
||||
static QVector<Color> colors;
|
||||
};
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_COLORCODINGAPP_H
|
||||
@@ -0,0 +1,206 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_CONFIGWRAPPER_H
|
||||
#define OAK_CONFIGWRAPPER_H
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
#include "olive/core/util/rational.h"
|
||||
#include "oakengine/config.h"
|
||||
|
||||
// Facade migration B9b: replace the engine's OAK_CONFIG macro (which
|
||||
// references olive::Config::current()/operator[] and brings C++ symbols into
|
||||
// the editor binary) with a thin header-only wrapper around the C ABI.
|
||||
//
|
||||
// Include this header instead of "config/config.h" in app code. It undefines
|
||||
// the engine macros and redefines them to return an inline OakConfigValue that
|
||||
// forwards reads/writes to oakengine_config_*().
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class OakConfigValue {
|
||||
public:
|
||||
explicit OakConfigValue(const QString &key) : key_(key) {}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return oakengine_config_get_int(key_utf8(), 0) != 0;
|
||||
}
|
||||
operator int() const
|
||||
{
|
||||
return static_cast<int>(oakengine_config_get_int(key_utf8(), 0));
|
||||
}
|
||||
operator qint64() const
|
||||
{
|
||||
return static_cast<qint64>(oakengine_config_get_int(key_utf8(), 0));
|
||||
}
|
||||
operator quint64() const
|
||||
{
|
||||
return static_cast<quint64>(oakengine_config_get_int(key_utf8(), 0));
|
||||
}
|
||||
operator int64_t() const
|
||||
{
|
||||
return oakengine_config_get_int(key_utf8(), 0);
|
||||
}
|
||||
operator uint64_t() const
|
||||
{
|
||||
return static_cast<uint64_t>(oakengine_config_get_int(key_utf8(), 0));
|
||||
}
|
||||
operator QString() const
|
||||
{
|
||||
char buf[1024];
|
||||
const int len = oakengine_config_get_string(key_utf8(), buf,
|
||||
sizeof(buf));
|
||||
return QString::fromUtf8(buf, len);
|
||||
}
|
||||
operator QVariant() const
|
||||
{
|
||||
return QVariant(static_cast<QString>(*this));
|
||||
}
|
||||
|
||||
OakConfigValue &operator=(bool v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), v ? 1 : 0);
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(int v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), static_cast<int64_t>(v));
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(uint v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), static_cast<int64_t>(v));
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(qint64 v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), static_cast<int64_t>(v));
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(quint64 v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), static_cast<int64_t>(v));
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(int64_t v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), v);
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(uint64_t v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), static_cast<int64_t>(v));
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(const QString &v)
|
||||
{
|
||||
const QByteArray utf8 = v.toUtf8();
|
||||
oakengine_config_set_string(key_utf8(), utf8.constData());
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(const char *v)
|
||||
{
|
||||
oakengine_config_set_string(key_utf8(), v ? v : "");
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(const QVariant &v)
|
||||
{
|
||||
switch (v.typeId()) {
|
||||
case QMetaType::Bool:
|
||||
*this = v.toBool();
|
||||
break;
|
||||
case QMetaType::Int:
|
||||
case QMetaType::UInt:
|
||||
case QMetaType::LongLong:
|
||||
case QMetaType::ULongLong:
|
||||
case QMetaType::Long:
|
||||
case QMetaType::Short:
|
||||
case QMetaType::Char:
|
||||
case QMetaType::ULong:
|
||||
case QMetaType::UShort:
|
||||
case QMetaType::UChar:
|
||||
*this = v.toLongLong();
|
||||
break;
|
||||
case QMetaType::Double:
|
||||
case QMetaType::Float:
|
||||
*this = static_cast<int64_t>(v.toDouble());
|
||||
break;
|
||||
default:
|
||||
*this = v.toString();
|
||||
break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool toBool() const { return static_cast<bool>(*this); }
|
||||
int toInt() const { return static_cast<int>(*this); }
|
||||
qint64 toLongLong() const { return static_cast<qint64>(*this); }
|
||||
quint64 toULongLong() const { return static_cast<quint64>(*this); }
|
||||
QString toString() const { return static_cast<QString>(*this); }
|
||||
|
||||
bool operator==(int rhs) const { return toInt() == rhs; }
|
||||
bool operator!=(int rhs) const { return toInt() != rhs; }
|
||||
bool operator==(qint64 rhs) const { return toLongLong() == rhs; }
|
||||
bool operator!=(qint64 rhs) const { return toLongLong() != rhs; }
|
||||
bool operator==(const QString &rhs) const { return toString() == rhs; }
|
||||
bool operator!=(const QString &rhs) const { return toString() != rhs; }
|
||||
bool operator==(const char *rhs) const { return toString() == QString::fromUtf8(rhs); }
|
||||
bool operator!=(const char *rhs) const { return toString() != QString::fromUtf8(rhs); }
|
||||
|
||||
template <typename T> T value() const
|
||||
{
|
||||
if constexpr (std::is_same_v<T, olive::core::Rational>) {
|
||||
const QString s = static_cast<QString>(*this);
|
||||
const QByteArray utf8 = s.toUtf8();
|
||||
return olive::core::Rational::from_string(
|
||||
std::string(utf8.constData(), size_t(utf8.size())));
|
||||
} else {
|
||||
return static_cast<T>(*this);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const char *key_utf8() const
|
||||
{
|
||||
key_utf8_ = key_.toUtf8();
|
||||
return key_utf8_.constData();
|
||||
}
|
||||
|
||||
QString key_;
|
||||
mutable QByteArray key_utf8_;
|
||||
};
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#ifdef OAK_CONFIG
|
||||
#undef OAK_CONFIG
|
||||
#endif
|
||||
#ifdef OAK_CONFIG_STR
|
||||
#undef OAK_CONFIG_STR
|
||||
#endif
|
||||
|
||||
#define OAK_CONFIG(x) olive::OakConfigValue(QStringLiteral(x))
|
||||
#define OAK_CONFIG_STR(x) olive::OakConfigValue(x)
|
||||
|
||||
#endif // OAK_CONFIGWRAPPER_H
|
||||
@@ -0,0 +1,87 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
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 OAK_DEBUGAPP_H
|
||||
#define OAK_DEBUGAPP_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QDateTime>
|
||||
#include <QMutex>
|
||||
#include <QTextStream>
|
||||
#include <iostream>
|
||||
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief App-side debug handler (moved from engine/common/debug.cpp)
|
||||
*
|
||||
* Replaces engine's olive::debug_handler so oak-editor doesn't import
|
||||
* that symbol. Only used in main.cpp's qInstallMessageHandler.
|
||||
*/
|
||||
static void debug_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
// Suppress noisy warnings from Qt's QXcbIntegration
|
||||
if (type == QtWarningMsg && msg.contains("QXcbIntegration")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Suppress all Qt warnings during automated testing
|
||||
static const bool is_testing = qEnvironmentVariableIsSet("OAK_TESTING");
|
||||
if (is_testing && type == QtWarningMsg) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString log_line;
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
log_line = QStringLiteral("Debug: %1 (%2:%3, %4)\n");
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
log_line = QStringLiteral("Info: %1 (%2:%3, %4)\n");
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
log_line = QStringLiteral("Warning: %1 (%2:%3, %4)\n");
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
log_line = QStringLiteral("Critical: %1 (%2:%3, %4)\n");
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
log_line = QStringLiteral("Fatal: %1 (%2:%3, %4)\n");
|
||||
break;
|
||||
}
|
||||
|
||||
log_line = log_line.arg(msg, context.file != nullptr ? context.file : "<null>",
|
||||
QString::number(context.line), context.function != nullptr ?
|
||||
context.function : "<null>");
|
||||
|
||||
std::cerr << log_line.toUtf8().constData();
|
||||
|
||||
if (type == QtFatalMsg) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_DEBUGAPP_H
|
||||
@@ -0,0 +1,98 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
// App-side implementations of FileFunctions methods that would otherwise
|
||||
// be imported from liboakengine. The declarations live in the engine header
|
||||
// (common/filefunctions.h) which is on the public include path; these
|
||||
// definitions resolve the symbols locally in the app binary.
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextStream>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
bool FileFunctions::directory_is_valid(const QDir &d,
|
||||
bool try_to_create_if_not_exists)
|
||||
{
|
||||
return d.exists() ||
|
||||
(try_to_create_if_not_exists && d.mkpath(QStringLiteral(".")));
|
||||
}
|
||||
|
||||
QString FileFunctions::read_file_as_string(const QString &filename)
|
||||
{
|
||||
QFile f(filename);
|
||||
QString file_data;
|
||||
if (f.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QTextStream text_stream(&f);
|
||||
file_data = text_stream.readAll();
|
||||
f.close();
|
||||
}
|
||||
return file_data;
|
||||
}
|
||||
|
||||
QString FileFunctions::get_auto_recovery_root()
|
||||
{
|
||||
return QDir(QStandardPaths::writableLocation(
|
||||
QStandardPaths::AppLocalDataLocation))
|
||||
.filePath(QStringLiteral("autorecovery"));
|
||||
}
|
||||
|
||||
QString FileFunctions::ensure_filename_extension(QString fn,
|
||||
const QString &extension)
|
||||
{
|
||||
if (!fn.isEmpty() && !extension.isEmpty()) {
|
||||
QString extension_with_dot;
|
||||
extension_with_dot.append('.');
|
||||
extension_with_dot.append(extension);
|
||||
if (!fn.endsWith(extension_with_dot, Qt::CaseInsensitive)) {
|
||||
fn.append(extension_with_dot);
|
||||
}
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
QString FileFunctions::get_configuration_location()
|
||||
{
|
||||
if (is_portable()) {
|
||||
return get_application_path();
|
||||
} else {
|
||||
QString s = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
QDir(s).mkpath(".");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
bool FileFunctions::is_portable()
|
||||
{
|
||||
return QFileInfo::exists(QDir(get_application_path()).filePath("portable"));
|
||||
}
|
||||
|
||||
QString FileFunctions::get_application_path()
|
||||
{
|
||||
return QCoreApplication::applicationDirPath();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
// App-side implementations of qHash overloads and stream operators
|
||||
// used by QHash containers and QDataStream serialization in app code.
|
||||
// Provides local definitions so the app doesn't import these from liboakengine.
|
||||
|
||||
#include "node/param.h"
|
||||
#include "node/output/track/track.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
uint qHash(const NodeInput &i)
|
||||
{
|
||||
return qHash(i.node()) ^ qHash(i.input()) ^ ::qHash(i.element());
|
||||
}
|
||||
|
||||
uint qHash(const NodeInputPair &i)
|
||||
{
|
||||
return qHash(i.node) ^ qHash(i.input);
|
||||
}
|
||||
|
||||
uint qHash(const NodeKeyframeTrackReference &i)
|
||||
{
|
||||
return qHash(i.input()) ^ ::qHash(i.track());
|
||||
}
|
||||
|
||||
uint qHash(const Track::Reference &r, uint seed)
|
||||
{
|
||||
return ::qHash(QStringLiteral("%1:%2").arg(QString::number(r.type()),
|
||||
QString::number(r.index())),
|
||||
seed);
|
||||
}
|
||||
|
||||
QDataStream &operator<<(QDataStream &out, const Track::Reference &ref)
|
||||
{
|
||||
out << static_cast<int>(ref.type()) << ref.index();
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream &operator>>(QDataStream &in, Track::Reference &ref)
|
||||
{
|
||||
int type, index;
|
||||
in >> type >> index;
|
||||
ref = Track::Reference(static_cast<Track::Type>(type), index);
|
||||
return in;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* 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 "htmlapp.h"
|
||||
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QFont>
|
||||
#include <QTextBlock>
|
||||
#include <QTextBlockFormat>
|
||||
#include <QTextCharFormat>
|
||||
#include <QTextDocument>
|
||||
#include <QTextFragment>
|
||||
#include <QTextList>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "common/xmlutils.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QTextBlock>
|
||||
|
||||
#include "common/xmlutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const QVector<QString> Html::k_block_tags = { QStringLiteral("p"),
|
||||
QStringLiteral("div") };
|
||||
|
||||
inline bool str_equals(const QStringView &a, const QStringView &b)
|
||||
{
|
||||
return !a.compare(b, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
QString Html::doc_to_html(const QTextDocument *doc)
|
||||
{
|
||||
QString html;
|
||||
QXmlStreamWriter writer(&html);
|
||||
|
||||
//writer.setAutoFormatting(true);
|
||||
|
||||
for (auto it = doc->begin(); it != doc->end(); it = it.next()) {
|
||||
write_block(&writer, it);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
struct HtmlNode {
|
||||
QString tag;
|
||||
QTextCharFormat format;
|
||||
};
|
||||
|
||||
QTextCharFormat merge_html_formats(const QVector<HtmlNode> &stack)
|
||||
{
|
||||
QTextCharFormat f;
|
||||
|
||||
for (int i = 0; i < stack.size(); i++) {
|
||||
f.merge(stack.at(i).format);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void Html::html_to_doc(QTextDocument *doc, const QString &html)
|
||||
{
|
||||
// Empty doc
|
||||
doc->clear();
|
||||
bool inside_block = true;
|
||||
|
||||
// Create cursor, which appears to be Qt's official way of inserting blocks and fragments
|
||||
QTextCursor c(doc);
|
||||
|
||||
QString wrapped = QStringLiteral("<html>").append(html).append("</html>");
|
||||
QXmlStreamReader reader(wrapped);
|
||||
|
||||
QVector<HtmlNode> fmt_stack;
|
||||
|
||||
QTextCharFormat default_fmt;
|
||||
default_fmt.setFontWeight(QFont::Normal);
|
||||
fmt_stack.append({ QStringLiteral("html"), default_fmt });
|
||||
|
||||
QTextCharFormat current_fmt;
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
reader.readNext();
|
||||
|
||||
if (reader.tokenType() == QXmlStreamReader::StartElement) {
|
||||
QString tag = reader.name().toString().toLower();
|
||||
|
||||
fmt_stack.append({ tag, read_char_format(reader.attributes()) });
|
||||
current_fmt = merge_html_formats(fmt_stack);
|
||||
|
||||
if (k_block_tags.contains(tag)) {
|
||||
QTextBlockFormat block_fmt =
|
||||
read_block_format(reader.attributes());
|
||||
if (inside_block) {
|
||||
c.setBlockFormat(block_fmt);
|
||||
c.setBlockCharFormat(current_fmt);
|
||||
} else {
|
||||
c.insertBlock(block_fmt, current_fmt);
|
||||
inside_block = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader.tokenType() == QXmlStreamReader::Characters) {
|
||||
QString characters = reader.text().toString();
|
||||
c.insertText(characters, current_fmt);
|
||||
|
||||
} else if (reader.tokenType() == QXmlStreamReader::EndElement) {
|
||||
QString tag = reader.name().toString().toLower();
|
||||
|
||||
for (int i = fmt_stack.size() - 1; i >= 0; i--) {
|
||||
if (fmt_stack.at(i).tag == tag) {
|
||||
fmt_stack.removeAt(i);
|
||||
current_fmt = merge_html_formats(fmt_stack);
|
||||
|
||||
if (k_block_tags.contains(tag)) {
|
||||
inside_block = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.error()) {
|
||||
qCritical() << "Failed to parse HTML:" << reader.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void Html::write_block(QXmlStreamWriter *writer, const QTextBlock &block)
|
||||
{
|
||||
writer->writeStartElement(QStringLiteral("p"));
|
||||
|
||||
const QTextBlockFormat &fmt = block.blockFormat();
|
||||
|
||||
// Write block alignment
|
||||
if (!(fmt.alignment() & Qt::AlignLeft)) {
|
||||
if (fmt.alignment() & Qt::AlignRight) {
|
||||
writer->writeAttribute(QStringLiteral("align"),
|
||||
QStringLiteral("right"));
|
||||
} else if (fmt.alignment() & Qt::AlignHCenter) {
|
||||
writer->writeAttribute(QStringLiteral("align"),
|
||||
QStringLiteral("center"));
|
||||
} else if (fmt.alignment() & Qt::AlignJustify) {
|
||||
writer->writeAttribute(QStringLiteral("align"),
|
||||
QStringLiteral("justify"));
|
||||
}
|
||||
}
|
||||
|
||||
// RTL support
|
||||
if (block.textDirection() == Qt::RightToLeft) {
|
||||
writer->writeAttribute(QStringLiteral("dir"), QStringLiteral("rtl"));
|
||||
}
|
||||
|
||||
// Write CSS attributes
|
||||
QString style;
|
||||
|
||||
if (fmt.lineHeightType() != QTextBlockFormat::SingleHeight) {
|
||||
write_css_property(&style, QStringLiteral("line-height"),
|
||||
QStringLiteral("%1%").arg(fmt.lineHeight()));
|
||||
}
|
||||
|
||||
write_char_format(&style, block.charFormat());
|
||||
|
||||
if (!style.isEmpty()) {
|
||||
writer->writeAttribute(QStringLiteral("style"), style);
|
||||
}
|
||||
|
||||
auto it = block.begin();
|
||||
|
||||
if (it != block.end()) {
|
||||
for (; it != block.end(); it++) {
|
||||
write_fragment(writer, it.fragment());
|
||||
}
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // p
|
||||
}
|
||||
|
||||
void Html::write_fragment(QXmlStreamWriter *writer,
|
||||
const QTextFragment &fragment)
|
||||
{
|
||||
const QTextCharFormat &fmt = fragment.charFormat();
|
||||
|
||||
writer->writeStartElement(QStringLiteral("span"));
|
||||
|
||||
// Write CSS attributes
|
||||
QString style;
|
||||
|
||||
write_char_format(&style, fmt);
|
||||
|
||||
if (!style.isEmpty()) {
|
||||
writer->writeAttribute(QStringLiteral("style"), style);
|
||||
}
|
||||
|
||||
QStringList lines = fragment.text().split(QChar::LineSeparator);
|
||||
bool first_line = true;
|
||||
foreach (const QString &l, lines) {
|
||||
if (first_line) {
|
||||
first_line = false;
|
||||
} else {
|
||||
writer->writeEmptyElement(QStringLiteral("br"));
|
||||
}
|
||||
writer->writeCharacters(l);
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // span
|
||||
}
|
||||
|
||||
void Html::write_css_property(QString *style, const QString &key,
|
||||
const QStringList &values)
|
||||
{
|
||||
QString value;
|
||||
foreach (QString v, values) {
|
||||
if (v.contains(' ')) {
|
||||
v = QStringLiteral("'%1'").arg(v);
|
||||
}
|
||||
|
||||
append_string_auto_space(&value, v);
|
||||
}
|
||||
|
||||
append_string_auto_space(style, QStringLiteral("%1: %2;").arg(key, value));
|
||||
}
|
||||
|
||||
void Html::write_char_format(QString *style, const QTextCharFormat &fmt)
|
||||
{
|
||||
QStringList families = fmt.fontFamilies().toStringList();
|
||||
if (!families.isEmpty()) {
|
||||
write_css_property(style, QStringLiteral("font-family"),
|
||||
families.first());
|
||||
}
|
||||
|
||||
if (fmt.hasProperty(QTextFormat::FontPointSize)) {
|
||||
write_css_property(
|
||||
style, QStringLiteral("font-size"),
|
||||
QStringLiteral("%1pt").arg(QString::number(fmt.fontPointSize())));
|
||||
}
|
||||
|
||||
if (fmt.hasProperty(QTextFormat::FontWeight)) {
|
||||
write_css_property(style, QStringLiteral("font-weight"),
|
||||
QString::number(fmt.fontWeight() * 8));
|
||||
}
|
||||
|
||||
if (fmt.hasProperty(QTextFormat::FontItalic)) {
|
||||
write_css_property(style, QStringLiteral("font-style"),
|
||||
fmt.fontItalic() ? QStringLiteral("italic") :
|
||||
QStringLiteral("normal"));
|
||||
}
|
||||
|
||||
if (fmt.hasProperty(QTextFormat::FontStyleName)) {
|
||||
write_css_property(style, QStringLiteral("-ove-font-style"),
|
||||
fmt.fontStyleName().toString());
|
||||
}
|
||||
|
||||
QStringList deco;
|
||||
|
||||
if (fmt.fontUnderline()) {
|
||||
deco.append(QStringLiteral("underline"));
|
||||
}
|
||||
|
||||
if (fmt.fontStrikeOut()) {
|
||||
deco.append(QStringLiteral("line-through"));
|
||||
}
|
||||
|
||||
if (fmt.fontOverline()) {
|
||||
deco.append(QStringLiteral("overline"));
|
||||
}
|
||||
|
||||
if (!deco.isEmpty()) {
|
||||
write_css_property(style, QStringLiteral("text-decoration"), deco);
|
||||
}
|
||||
|
||||
if (fmt.foreground().style() != Qt::NoBrush) {
|
||||
const QColor &color = fmt.foreground().color();
|
||||
QString cs;
|
||||
|
||||
if (color.alpha() == 255) {
|
||||
cs = color.name();
|
||||
} else if (color.alpha()) {
|
||||
cs = QStringLiteral("rgba(%1, %2, %3, %4)")
|
||||
.arg(QString::number(color.red()),
|
||||
QString::number(color.green()),
|
||||
QString::number(color.blue()),
|
||||
QString::number(color.alphaF()));
|
||||
}
|
||||
|
||||
write_css_property(style, QStringLiteral("color"), cs);
|
||||
}
|
||||
|
||||
if (fmt.fontCapitalization() != QFont::MixedCase) {
|
||||
if (fmt.fontCapitalization() == QFont::SmallCaps) {
|
||||
write_css_property(style, QStringLiteral("font-variant"),
|
||||
QStringLiteral("small-caps"));
|
||||
// TODO: Add others
|
||||
}
|
||||
}
|
||||
|
||||
if (fmt.fontLetterSpacing() != 0.0) {
|
||||
write_css_property(style, QStringLiteral("letter-spacing"),
|
||||
QStringLiteral("%1%").arg(
|
||||
QString::number(fmt.fontLetterSpacing())));
|
||||
}
|
||||
|
||||
if (fmt.fontStretch() != 0) {
|
||||
write_css_property(
|
||||
style, QStringLiteral("font-stretch"),
|
||||
QStringLiteral("%1%").arg(QString::number(fmt.fontStretch())));
|
||||
}
|
||||
}
|
||||
|
||||
QTextCharFormat Html::read_char_format(const QXmlStreamAttributes &attributes)
|
||||
{
|
||||
QTextCharFormat fmt;
|
||||
|
||||
foreach (const QXmlStreamAttribute &attr, attributes) {
|
||||
if (str_equals(attr.name(), QStringLiteral("style"))) {
|
||||
auto css = get_css_from_style(attr.value().toString());
|
||||
|
||||
for (auto it = css.begin(); it != css.end(); it++) {
|
||||
const QString &first_val = it.value().first();
|
||||
|
||||
if (it.key() == QStringLiteral("font-family")) {
|
||||
fmt.setFontFamilies({ first_val });
|
||||
} else if (it.key() == QStringLiteral("font-size")) {
|
||||
if (first_val.endsWith(QStringLiteral("pt"),
|
||||
Qt::CaseInsensitive)) {
|
||||
fmt.setFontPointSize(first_val.chopped(2).toDouble());
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("font-weight")) {
|
||||
fmt.setFontWeight(first_val.toInt() / 8);
|
||||
} else if (it.key() == QStringLiteral("font-style")) {
|
||||
fmt.setFontItalic(
|
||||
str_equals(first_val, QStringLiteral("italic")));
|
||||
} else if (it.key() == QStringLiteral("text-decoration")) {
|
||||
foreach (const QString &v, it.value()) {
|
||||
if (str_equals(v, QStringLiteral("underline"))) {
|
||||
fmt.setFontUnderline(true);
|
||||
} else if (str_equals(v,
|
||||
QStringLiteral("line-through"))) {
|
||||
fmt.setFontStrikeOut(true);
|
||||
} else if (str_equals(v, QStringLiteral("overline"))) {
|
||||
fmt.setFontOverline(true);
|
||||
}
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("color")) {
|
||||
if (first_val.startsWith(QStringLiteral("rgba"),
|
||||
Qt::CaseInsensitive)) {
|
||||
QString vals_only = first_val;
|
||||
vals_only.remove(QStringLiteral("rgba"));
|
||||
vals_only.remove(QStringLiteral("("));
|
||||
vals_only.remove(QStringLiteral(")"));
|
||||
QStringList rgba = vals_only.split(',');
|
||||
if (rgba.size() == 4) {
|
||||
QColor c;
|
||||
c.setRed(rgba.at(0).toInt()); // Writer emits 0-255 RGB (CSS rgba() convention)
|
||||
c.setGreen(rgba.at(1).toInt());
|
||||
c.setBlue(rgba.at(2).toInt());
|
||||
c.setAlphaF(rgba.at(3).toDouble());
|
||||
fmt.setForeground(c);
|
||||
}
|
||||
} else {
|
||||
fmt.setForeground(QColor(first_val));
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("font-variant")) {
|
||||
if (str_equals(first_val, QStringLiteral("small-caps"))) {
|
||||
fmt.setFontCapitalization(QFont::SmallCaps);
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("letter-spacing")) {
|
||||
if (first_val.contains(QChar('%'))) {
|
||||
fmt.setFontLetterSpacing(
|
||||
first_val.chopped(1).toDouble());
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("font-stretch")) {
|
||||
if (first_val.contains(QChar('%'))) {
|
||||
fmt.setFontStretch(first_val.chopped(1).toInt());
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("-ove-font-style")) {
|
||||
fmt.setFontStyleName(first_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt;
|
||||
}
|
||||
|
||||
QTextBlockFormat Html::read_block_format(const QXmlStreamAttributes &attributes)
|
||||
{
|
||||
QTextBlockFormat block_fmt;
|
||||
|
||||
foreach (const QXmlStreamAttribute &attr, attributes) {
|
||||
if (str_equals(attr.name(), QStringLiteral("align"))) {
|
||||
if (str_equals(attr.value(), QStringLiteral("right"))) {
|
||||
block_fmt.setAlignment(Qt::AlignRight);
|
||||
} else if (str_equals(attr.value(), QStringLiteral("center"))) {
|
||||
block_fmt.setAlignment(Qt::AlignHCenter);
|
||||
} else if (str_equals(attr.value(), QStringLiteral("justify"))) {
|
||||
block_fmt.setAlignment(Qt::AlignJustify);
|
||||
}
|
||||
} else if (str_equals(attr.name(), QStringLiteral("dir"))) {
|
||||
if (str_equals(attr.value(), QStringLiteral("rtl"))) {
|
||||
block_fmt.setLayoutDirection(Qt::RightToLeft);
|
||||
}
|
||||
} else if (str_equals(attr.name(), QStringLiteral("style"))) {
|
||||
auto css = get_css_from_style(attr.value().toString());
|
||||
|
||||
for (auto it = css.begin(); it != css.end(); it++) {
|
||||
if (it.key() == QStringLiteral("line-height")) {
|
||||
const QString &first_val = it.value().constFirst();
|
||||
if (first_val.contains(QChar('%'))) {
|
||||
block_fmt.setLineHeight(
|
||||
first_val.chopped(1).toDouble(),
|
||||
QTextBlockFormat::ProportionalHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return block_fmt;
|
||||
}
|
||||
|
||||
void Html::append_string_auto_space(QString *s, const QString &append)
|
||||
{
|
||||
if (!s->isEmpty()) {
|
||||
s->append(QChar(' '));
|
||||
}
|
||||
|
||||
s->append(append);
|
||||
}
|
||||
|
||||
QMap<QString, QStringList> Html::get_css_from_style(const QString &s)
|
||||
{
|
||||
QMap<QString, QStringList> map;
|
||||
|
||||
QStringList list = s.split(QChar(';'));
|
||||
|
||||
foreach (const QString &a, list) {
|
||||
QStringList kv = a.split(QChar(':'));
|
||||
|
||||
if (kv.size() != 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// I'm sure there's regex that could do this, but I couldn't figure it out. It needs to split
|
||||
// by space EXCEPT within quotes OR double-quotes, and said quotes should be EXCLUDED from each
|
||||
// match. Also commas should be filtered out.
|
||||
QStringList values;
|
||||
const QString &val = kv.at(1);
|
||||
QChar in_quote(0);
|
||||
QString current_str;
|
||||
for (int i = 0; i < val.size(); i++) {
|
||||
const QChar ¤t_char = val.at(i);
|
||||
|
||||
if (!in_quote.isNull()) {
|
||||
// If inside quotes and character isn't quote, indiscriminately append char
|
||||
if (current_char == in_quote) {
|
||||
in_quote = QChar(0);
|
||||
} else {
|
||||
current_str.append(current_char);
|
||||
}
|
||||
} else if (current_char.isSpace() || current_char == QChar(',')) {
|
||||
// Dump current
|
||||
if (!current_str.isEmpty()) {
|
||||
values.append(current_str);
|
||||
current_str.clear();
|
||||
}
|
||||
} else if (in_quote.isNull() && (current_char == QChar('\'') ||
|
||||
current_char == QChar('"'))) {
|
||||
in_quote = current_char;
|
||||
} else {
|
||||
current_str.append(current_char);
|
||||
}
|
||||
}
|
||||
|
||||
if (!current_str.isEmpty()) {
|
||||
values.append(current_str);
|
||||
}
|
||||
|
||||
// Not sure if this will ever happen, but just in case, we will avoid assert failures with this
|
||||
if (values.isEmpty()) {
|
||||
values.append(QString());
|
||||
}
|
||||
|
||||
map[kv.at(0).trimmed().toLower()] = values;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Oak Video Editor - Non-Linear Video Editor
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* 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 OAK_HTMLAPP_H
|
||||
#define OAK_HTML_H
|
||||
|
||||
#include <QTextDocument>
|
||||
#include <QTextFragment>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Functions for converting HTML to QTextDocument and vice versa
|
||||
*
|
||||
* Qt does contain its own functions for this, however they have some limitations. Some things that
|
||||
* we want to support (e.g. kerning/spacing and font stretch) are not implemented in Qt's
|
||||
* QTextHtmlExporter and QTextHtmlParser. Additionally, since these functions are not part of Qt's
|
||||
* public API, and make many references to other parts of Qt that are not part of the public API,
|
||||
* there is no way to subclass or extend their functionality without forking Qt as a whole.
|
||||
*
|
||||
* Therefore, it became necessary to write a custom class for the conversion so that we can
|
||||
* ensure support for the features we need.
|
||||
*
|
||||
* If someone wishes to extend this class for more feature support, feel free to open a pull
|
||||
* request. But this is NOT intended to be an exhaustive HTML implementation, and is primarily
|
||||
* designed to store rich text in a standard format for the purpose of text formatting for video.
|
||||
*/
|
||||
class Html {
|
||||
public:
|
||||
static QString doc_to_html(const QTextDocument *doc);
|
||||
|
||||
static void html_to_doc(QTextDocument *doc, const QString &html);
|
||||
|
||||
private:
|
||||
static void write_block(QXmlStreamWriter *writer, const QTextBlock &block);
|
||||
|
||||
static void write_fragment(QXmlStreamWriter *writer,
|
||||
const QTextFragment &fragment);
|
||||
|
||||
static void write_css_property(QString *style, const QString &key,
|
||||
const QStringList &value);
|
||||
static void write_css_property(QString *style, const QString &key,
|
||||
const QString &value)
|
||||
{
|
||||
write_css_property(style, key, QStringList({ value }));
|
||||
}
|
||||
|
||||
static void write_char_format(QString *style, const QTextCharFormat &fmt);
|
||||
|
||||
static QTextCharFormat
|
||||
read_char_format(const QXmlStreamAttributes &attributes);
|
||||
|
||||
static QTextBlockFormat
|
||||
read_block_format(const QXmlStreamAttributes &attributes);
|
||||
|
||||
static void append_string_auto_space(QString *s, const QString &append);
|
||||
|
||||
static QMap<QString, QStringList> get_css_from_style(const QString &s);
|
||||
|
||||
static const QVector<QString> k_block_tags;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_HTML_H
|
||||
@@ -0,0 +1,68 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
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 OAK_NODEVALUEHANDLE_H
|
||||
#define OAK_NODEVALUEHANDLE_H
|
||||
|
||||
#include "node/value.h"
|
||||
#include "oakengine/node.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Convert engine NodeValue::Type to oak_node_value_type (app-side).
|
||||
*
|
||||
* The two enums do NOT share ordinals (e.g. k_boolean=4 vs BOOL=3), so a
|
||||
* plain int cast is a bug. Mirrors from_c_type() in
|
||||
* engine/src/capi/node.cpp. Lives in an app header, NOT in the public
|
||||
* facade headers — the C ABI surface stays pure C (see
|
||||
* docs/zh/r6-cleanup-plan.md red line 3 context). Returns -1 for types the
|
||||
* facade cannot represent (caller falls back to the input's declared type).
|
||||
*/
|
||||
inline int node_value_type_to_c(NodeValue::Type t)
|
||||
{
|
||||
switch (t) {
|
||||
case NodeValue::k_int: return OAK_NODE_VALUE_INT;
|
||||
case NodeValue::k_float: return OAK_NODE_VALUE_FLOAT;
|
||||
case NodeValue::k_boolean: return OAK_NODE_VALUE_BOOL;
|
||||
case NodeValue::k_rational: return OAK_NODE_VALUE_RATIONAL;
|
||||
case NodeValue::k_color: return OAK_NODE_VALUE_COLOR;
|
||||
case NodeValue::k_vec2: return OAK_NODE_VALUE_VEC2;
|
||||
case NodeValue::k_vec3: return OAK_NODE_VALUE_VEC3;
|
||||
case NodeValue::k_vec4: return OAK_NODE_VALUE_VEC4;
|
||||
case NodeValue::k_combo: return OAK_NODE_VALUE_COMBO;
|
||||
case NodeValue::k_file: return OAK_NODE_VALUE_STRING;
|
||||
case NodeValue::k_text: return OAK_NODE_VALUE_TEXT;
|
||||
case NodeValue::k_font: return OAK_NODE_VALUE_FONT;
|
||||
case NodeValue::k_str_combo: return OAK_NODE_VALUE_STR_COMBO;
|
||||
case NodeValue::k_binary: return OAK_NODE_VALUE_BINARY;
|
||||
case NodeValue::k_bezier: return OAK_NODE_VALUE_BEZIER;
|
||||
case NodeValue::k_texture: return OAK_NODE_VALUE_TEXTURE;
|
||||
case NodeValue::k_samples: return OAK_NODE_VALUE_SAMPLES;
|
||||
case NodeValue::k_video_params: return OAK_NODE_VALUE_VIDEO_PARAMS;
|
||||
case NodeValue::k_audio_params: return OAK_NODE_VALUE_AUDIO_PARAMS;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_NODEVALUEHANDLE_H
|
||||
@@ -0,0 +1,226 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
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 OAKVALUEHELPER_H
|
||||
#define OAKVALUEHELPER_H
|
||||
|
||||
#include <QVariant>
|
||||
#include <QVector2D>
|
||||
#include <QVector3D>
|
||||
#include <QVector4D>
|
||||
|
||||
#include "node/keyframe.h"
|
||||
#include "node/value.h"
|
||||
#include "oakengine/node.h"
|
||||
#include "olive/core/util/color.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief Convert a per-track component QVariant into the C ABI oak_node_value POD.
|
||||
*
|
||||
* `type` is the declared input data type (e.g. k_float/k_color). For split-track
|
||||
* types the component is the track-0 scalar (float for k_color's red channel, etc.).
|
||||
* Returns false for types that have no POD representation.
|
||||
*/
|
||||
static inline bool QVariantToOakNodeValue(NodeValue::Type type, const QVariant &v,
|
||||
oak_node_value *out)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
switch (type) {
|
||||
case NodeValue::k_int:
|
||||
case NodeValue::k_combo:
|
||||
out->type = (type == NodeValue::k_combo) ? OAK_NODE_VALUE_COMBO
|
||||
: OAK_NODE_VALUE_INT;
|
||||
out->num = v.toLongLong();
|
||||
return true;
|
||||
case NodeValue::k_float:
|
||||
out->type = OAK_NODE_VALUE_FLOAT;
|
||||
out->f[0] = v.toDouble();
|
||||
return true;
|
||||
case NodeValue::k_boolean:
|
||||
out->type = OAK_NODE_VALUE_BOOL;
|
||||
out->num = v.toBool() ? 1 : 0;
|
||||
return true;
|
||||
case NodeValue::k_rational:
|
||||
out->type = OAK_NODE_VALUE_RATIONAL;
|
||||
{
|
||||
const Rational r = v.value<Rational>();
|
||||
out->num = r.numerator();
|
||||
out->den = r.denominator();
|
||||
}
|
||||
return true;
|
||||
case NodeValue::k_color:
|
||||
out->type = OAK_NODE_VALUE_COLOR;
|
||||
{
|
||||
const core::Color c = v.value<core::Color>();
|
||||
out->f[0] = c.red();
|
||||
out->f[1] = c.green();
|
||||
out->f[2] = c.blue();
|
||||
out->f[3] = c.alpha();
|
||||
}
|
||||
return true;
|
||||
case NodeValue::k_vec2:
|
||||
out->type = OAK_NODE_VALUE_VEC2;
|
||||
{
|
||||
const QVector2D vec = v.value<QVector2D>();
|
||||
out->f[0] = vec.x();
|
||||
out->f[1] = vec.y();
|
||||
}
|
||||
return true;
|
||||
case NodeValue::k_vec3:
|
||||
out->type = OAK_NODE_VALUE_VEC3;
|
||||
{
|
||||
const QVector3D vec = v.value<QVector3D>();
|
||||
out->f[0] = vec.x();
|
||||
out->f[1] = vec.y();
|
||||
out->f[2] = vec.z();
|
||||
}
|
||||
return true;
|
||||
case NodeValue::k_vec4:
|
||||
out->type = OAK_NODE_VALUE_VEC4;
|
||||
{
|
||||
const QVector4D vec = v.value<QVector4D>();
|
||||
out->f[0] = vec.x();
|
||||
out->f[1] = vec.y();
|
||||
out->f[2] = vec.z();
|
||||
out->f[3] = vec.w();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a per-track component QVariant into the C ABI oak_node_value POD.
|
||||
*
|
||||
* Unlike QVariantToOakNodeValue() which takes a full normal value, this takes a
|
||||
* single track's component (e.g. one float for a k_color channel). The resulting
|
||||
* POD has the input's declared type with the component in f[0]/num, exactly what
|
||||
* the per-track facade commands expect.
|
||||
*/
|
||||
static inline bool NodeTrackComponentToOakNodeValue(NodeValue::Type type,
|
||||
const QVariant &v,
|
||||
oak_node_value *out)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
switch (type) {
|
||||
case NodeValue::k_int:
|
||||
case NodeValue::k_combo:
|
||||
out->type = (type == NodeValue::k_combo) ? OAK_NODE_VALUE_COMBO
|
||||
: OAK_NODE_VALUE_INT;
|
||||
out->num = v.toLongLong();
|
||||
return true;
|
||||
case NodeValue::k_float:
|
||||
case NodeValue::k_bezier:
|
||||
out->type = OAK_NODE_VALUE_FLOAT;
|
||||
out->f[0] = v.toDouble();
|
||||
return true;
|
||||
case NodeValue::k_boolean:
|
||||
out->type = OAK_NODE_VALUE_BOOL;
|
||||
out->num = v.toBool() ? 1 : 0;
|
||||
return true;
|
||||
case NodeValue::k_rational:
|
||||
out->type = OAK_NODE_VALUE_RATIONAL;
|
||||
{
|
||||
const Rational r = v.value<Rational>();
|
||||
out->num = r.numerator();
|
||||
out->den = r.denominator();
|
||||
}
|
||||
return true;
|
||||
case NodeValue::k_color:
|
||||
out->type = OAK_NODE_VALUE_COLOR;
|
||||
out->f[0] = v.toFloat();
|
||||
return true;
|
||||
case NodeValue::k_vec2:
|
||||
out->type = OAK_NODE_VALUE_VEC2;
|
||||
out->f[0] = v.toFloat();
|
||||
return true;
|
||||
case NodeValue::k_vec3:
|
||||
out->type = OAK_NODE_VALUE_VEC3;
|
||||
out->f[0] = v.toFloat();
|
||||
return true;
|
||||
case NodeValue::k_vec4:
|
||||
out->type = OAK_NODE_VALUE_VEC4;
|
||||
out->f[0] = v.toFloat();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a full C ABI oak_node_value POD back into a QVariant.
|
||||
*
|
||||
* Mirrors QVariantToOakNodeValue(). String/binary/bezier are not represented
|
||||
* in the POD and return an invalid QVariant; use the dedicated string/binary/
|
||||
* bezier facade getters for those.
|
||||
*/
|
||||
static inline QVariant OakNodeValueToQVariant(const oak_node_value &v)
|
||||
{
|
||||
switch (v.type) {
|
||||
case OAK_NODE_VALUE_INT:
|
||||
return QVariant::fromValue<qlonglong>(v.num);
|
||||
case OAK_NODE_VALUE_FLOAT:
|
||||
return QVariant::fromValue(v.f[0]);
|
||||
case OAK_NODE_VALUE_BOOL:
|
||||
return QVariant::fromValue(v.num != 0);
|
||||
case OAK_NODE_VALUE_RATIONAL:
|
||||
return QVariant::fromValue(
|
||||
Rational(int(v.num), int(v.den)));
|
||||
case OAK_NODE_VALUE_COLOR:
|
||||
return QVariant::fromValue(core::Color(
|
||||
float(v.f[0]), float(v.f[1]), float(v.f[2]), float(v.f[3])));
|
||||
case OAK_NODE_VALUE_VEC2:
|
||||
return QVariant::fromValue(
|
||||
QVector2D(float(v.f[0]), float(v.f[1])));
|
||||
case OAK_NODE_VALUE_VEC3:
|
||||
return QVariant::fromValue(
|
||||
QVector3D(float(v.f[0]), float(v.f[1]), float(v.f[2])));
|
||||
case OAK_NODE_VALUE_VEC4:
|
||||
return QVariant::fromValue(
|
||||
QVector4D(float(v.f[0]), float(v.f[1]), float(v.f[2]), float(v.f[3])));
|
||||
case OAK_NODE_VALUE_COMBO:
|
||||
return QVariant::fromValue<int>(int(v.num));
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Map an engine NodeKeyframe::Type to the facade easing type.
|
||||
*/
|
||||
static inline int NodeKeyframeTypeToFacade(NodeKeyframe::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case NodeKeyframe::k_bezier:
|
||||
return 1;
|
||||
case NodeKeyframe::k_hold:
|
||||
return 2;
|
||||
case NodeKeyframe::k_linear:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAKVALUEHELPER_H
|
||||
@@ -0,0 +1,143 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
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 "common/qtutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
int QtUtils::q_font_metrics_width(QFontMetrics fm, const QString &s)
|
||||
{
|
||||
return fm.horizontalAdvance(s);
|
||||
}
|
||||
|
||||
QFrame *QtUtils::create_horizontal_line()
|
||||
{
|
||||
QFrame *horizontal_line = new QFrame();
|
||||
horizontal_line->setFrameShape(QFrame::HLine);
|
||||
horizontal_line->setFrameShadow(QFrame::Sunken);
|
||||
return horizontal_line;
|
||||
}
|
||||
|
||||
QFrame *QtUtils::create_vertical_line()
|
||||
{
|
||||
QFrame *l = create_horizontal_line();
|
||||
l->setFrameShape(QFrame::VLine);
|
||||
return l;
|
||||
}
|
||||
|
||||
QString QtUtils::get_formatted_date_time(const QDateTime &dt)
|
||||
{
|
||||
return dt.toString(Qt::TextDate);
|
||||
}
|
||||
|
||||
QStringList QtUtils::word_wrap_string(const QString &s, const QFontMetrics &fm,
|
||||
int bounding_width)
|
||||
{
|
||||
QStringList list;
|
||||
QStringList lines = s.split('\n');
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
QString this_line = lines.at(i);
|
||||
while (this_line.size() > 1 &&
|
||||
q_font_metrics_width(fm, this_line) >= bounding_width) {
|
||||
int old_size = this_line.size();
|
||||
int hard_break = -1;
|
||||
for (int j = this_line.size() - 1; j >= 0; j--) {
|
||||
const QChar &char_test = this_line.at(j);
|
||||
if (char_test.isSpace() || char_test == '-') {
|
||||
if (q_font_metrics_width(fm, this_line.left(j)) <
|
||||
bounding_width) {
|
||||
if (!char_test.isSpace()) j++;
|
||||
list.append(this_line.left(j));
|
||||
while (j < this_line.size() &&
|
||||
this_line.at(j).isSpace()) j++;
|
||||
this_line.remove(0, j);
|
||||
break;
|
||||
}
|
||||
} else if (hard_break == -1 &&
|
||||
q_font_metrics_width(fm, this_line.left(j)) <
|
||||
bounding_width) {
|
||||
hard_break = j;
|
||||
}
|
||||
}
|
||||
if (old_size == this_line.size()) {
|
||||
if (hard_break != -1) {
|
||||
list.append(this_line.left(hard_break));
|
||||
this_line.remove(0, hard_break);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this_line.isEmpty()) {
|
||||
list.append(this_line);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
Qt::KeyboardModifiers
|
||||
QtUtils::flip_control_and_shift_modifiers(Qt::KeyboardModifiers e)
|
||||
{
|
||||
if (e & Qt::ControlModifier & Qt::ShiftModifier) return e;
|
||||
if (e & Qt::ShiftModifier) {
|
||||
e |= Qt::ControlModifier;
|
||||
e &= ~Qt::ShiftModifier;
|
||||
} else if (e & Qt::ControlModifier) {
|
||||
e |= Qt::ShiftModifier;
|
||||
e &= ~Qt::ControlModifier;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
void QtUtils::set_combo_box_data(QComboBox *cb, int data)
|
||||
{
|
||||
for (int i = 0; i < cb->count(); i++) {
|
||||
if (cb->itemData(i).toInt() == data) {
|
||||
cb->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtUtils::set_combo_box_data(QComboBox *cb, const QString &data)
|
||||
{
|
||||
for (int i = 0; i < cb->count(); i++) {
|
||||
if (cb->itemData(i).toString() == data) {
|
||||
cb->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QColor QtUtils::to_q_color(const core::Color &i)
|
||||
{
|
||||
QColor c;
|
||||
|
||||
// QColor only supports values from 0.0 to 1.0 and are only used for UI representations
|
||||
c.setRedF(std::clamp(i.red(), 0.0f, 1.0f));
|
||||
c.setGreenF(std::clamp(i.green(), 0.0f, 1.0f));
|
||||
c.setBlueF(std::clamp(i.blue(), 0.0f, 1.0f));
|
||||
c.setAlphaF(std::clamp(i.alpha(), 0.0f, 1.0f));
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_UNDOWRAPPER_H
|
||||
#define OAK_UNDOWRAPPER_H
|
||||
|
||||
#include "oakengine/undo.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* Wrap an app-side undo command object in the facade custom-command API.
|
||||
*
|
||||
* `Cmd` must provide public `redo()` and `undo()` methods. Ownership of `cmd`
|
||||
* is transferred to the returned opaque command pointer; the wrapper deletes
|
||||
* `cmd` when the engine command is destroyed.
|
||||
*
|
||||
* This helper lets app code keep small app-state undo commands (selections,
|
||||
* splitter sizes, etc.) without defining new subclasses of olive::UndoCommand,
|
||||
* which would keep olive::UndoCommand symbols in the editor binary.
|
||||
*/
|
||||
template <typename Cmd>
|
||||
void *wrap_app_undo_command(const char *name, Cmd *cmd)
|
||||
{
|
||||
return oakengine_undo_command_create(
|
||||
name,
|
||||
[](void *userdata) {
|
||||
static_cast<Cmd *>(userdata)->redo();
|
||||
},
|
||||
[](void *userdata) {
|
||||
static_cast<Cmd *>(userdata)->undo();
|
||||
},
|
||||
[](void *userdata) {
|
||||
delete static_cast<Cmd *>(userdata);
|
||||
},
|
||||
cmd);
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_UNDOWRAPPER_H
|
||||
@@ -0,0 +1,47 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
// App-side implementation of xml_read_next_start_element
|
||||
// Provides a local definition so the app doesn't import this from liboakengine.
|
||||
|
||||
#include "common/xmlutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
bool xml_read_next_start_element(QXmlStreamReader *reader,
|
||||
CancelAtom *cancel_atom)
|
||||
{
|
||||
QXmlStreamReader::TokenType token;
|
||||
|
||||
while ((token = reader->readNext()) != QXmlStreamReader::Invalid &&
|
||||
token != QXmlStreamReader::EndDocument &&
|
||||
(!cancel_atom || !cancel_atom->is_cancelled())) {
|
||||
if (reader->isEndElement()) {
|
||||
return false;
|
||||
} else if (reader->isStartElement()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user