Compare commits
No commits in common. "14f49aa3e40fe58eb3627eb34331bf9a961cb5c3" and "887e14add9a583d53dff2b26ad556545ba56816b" have entirely different histories.
14f49aa3e4
...
887e14add9
12 changed files with 649 additions and 696 deletions
|
|
@ -36,7 +36,6 @@ add_executable(${PROJECT_NAME}
|
||||||
src/clickableslider.cpp
|
src/clickableslider.cpp
|
||||||
${MusicGeneratorGUI_H}
|
${MusicGeneratorGUI_H}
|
||||||
res/resources.qrc
|
res/resources.qrc
|
||||||
src/elidedlabel.h src/elidedlabel.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# UI file
|
# UI file
|
||||||
|
|
@ -61,7 +60,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE
|
||||||
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
|
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
|
||||||
|
|
||||||
# Install .desktop file
|
# Install .desktop file
|
||||||
install(FILES res/xyz.uvos.aceradio.desktop DESTINATION share/applications)
|
install(FILES aceradio.desktop DESTINATION share/applications)
|
||||||
|
|
||||||
# Install icon files
|
# Install icon files
|
||||||
install(FILES res/xyz.uvos.aceradio.png DESTINATION share/icons/hicolor/256x256/apps RENAME xyz.uvos.aceradio.png)
|
install(FILES res/xyz.uvos.aceradio.png DESTINATION share/icons/hicolor/256x256/apps RENAME xyz.uvos.aceradio.png)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
A C++ Qt graphical user interface for generating music using [acestep.cpp](https://github.com/ServeurpersoCom/acestep.cpp).
|
A C++ Qt graphical user interface for generating music using acestep.cpp.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|
@ -26,13 +26,13 @@ make -j$(nproc)
|
||||||
### Build the GUI:
|
### Build the GUI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/IMbackK/aceradio.git
|
git clone git@github.com:IMbackK/aceradio.git
|
||||||
cd aceradio
|
cd aceradio
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake ..
|
cmake ..
|
||||||
make -j$(nproc)
|
make -j$(nproc)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Setup Paths:
|
### Setup Paths:
|
||||||
|
|
||||||
Go to settings->Ace Step->Model Paths and add the paths to the acestep.cpp binaries the models.
|
Go to settings->Ace Step->Model Paths and add the paths to the acestep.cpp binaries the models.
|
||||||
|
|
|
||||||
|
|
@ -8,205 +8,269 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QRandomGenerator>
|
#include <QRandomGenerator>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
AceStep::AceStep(QObject* parent): QObject(parent)
|
AceStepWorker::AceStepWorker(QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
currentWorker(nullptr)
|
||||||
{
|
{
|
||||||
connect(&qwenProcess, &QProcess::finished, this, &AceStep::qwenProcFinished);
|
|
||||||
connect(&ditVaeProcess, &QProcess::finished, this, &AceStep::ditProcFinished);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AceStep::isGenerateing(SongItem* song)
|
AceStepWorker::~AceStepWorker()
|
||||||
{
|
{
|
||||||
if(!busy && song)
|
cancelGeneration();
|
||||||
*song = this->request.song;
|
|
||||||
return busy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AceStep::cancleGenerateion()
|
void AceStepWorker::generateSong(const SongItem& song, const QString &jsonTemplate,
|
||||||
|
const QString &aceStepPath, const QString &qwen3ModelPath,
|
||||||
|
const QString &textEncoderModelPath, const QString &ditModelPath,
|
||||||
|
const QString &vaeModelPath)
|
||||||
{
|
{
|
||||||
qwenProcess.blockSignals(true);
|
// Cancel any ongoing generation
|
||||||
qwenProcess.terminate();
|
cancelGeneration();
|
||||||
qwenProcess.waitForFinished();
|
|
||||||
qwenProcess.blockSignals(false);
|
|
||||||
|
|
||||||
ditVaeProcess.blockSignals(true);
|
// Create worker and start it
|
||||||
ditVaeProcess.terminate();
|
currentWorker = new Worker(this, song, jsonTemplate, aceStepPath, qwen3ModelPath,
|
||||||
ditVaeProcess.waitForFinished();
|
textEncoderModelPath, ditModelPath, vaeModelPath);
|
||||||
ditVaeProcess.blockSignals(false);
|
currentWorker->setAutoDelete(true);
|
||||||
|
QThreadPool::globalInstance()->start(currentWorker);
|
||||||
progressUpdate(100);
|
|
||||||
if(busy)
|
|
||||||
generationCancled(request.song);
|
|
||||||
|
|
||||||
busy = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AceStep::requestGeneration(SongItem song, QString requestTemplate, QString aceStepPath,
|
void AceStepWorker::cancelGeneration()
|
||||||
QString qwen3ModelPath, QString textEncoderModelPath, QString ditModelPath,
|
|
||||||
QString vaeModelPath)
|
|
||||||
{
|
{
|
||||||
if(busy)
|
currentWorker = nullptr;
|
||||||
{
|
}
|
||||||
qWarning()<<"Droping song:"<<song.caption;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
busy = true;
|
|
||||||
|
|
||||||
request = {song, QRandomGenerator::global()->generate(), aceStepPath, textEncoderModelPath, ditModelPath, vaeModelPath};
|
bool AceStepWorker::songGenerateing(SongItem* song)
|
||||||
|
{
|
||||||
|
workerMutex.lock();
|
||||||
|
if(!currentWorker)
|
||||||
|
{
|
||||||
|
workerMutex.unlock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SongItem workerSong = currentWorker->getSong();
|
||||||
|
workerMutex.unlock();
|
||||||
|
if(song)
|
||||||
|
*song = workerSong;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString qwen3Binary = aceStepPath + "/ace-qwen3";
|
// Worker implementation
|
||||||
QFileInfo qwen3Info(qwen3Binary);
|
void AceStepWorker::Worker::run()
|
||||||
if (!qwen3Info.exists() || !qwen3Info.isExecutable())
|
{
|
||||||
{
|
uint64_t uid = QRandomGenerator::global()->generate();
|
||||||
generationError("ace-qwen3 binary not found at: " + qwen3Binary);
|
// Create temporary JSON file for the request
|
||||||
busy = false;
|
QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
||||||
return false;
|
QString requestFile = tempDir + "/request_" + QString::number(uid) + ".json";
|
||||||
}
|
|
||||||
if (!QFileInfo::exists(qwen3ModelPath))
|
|
||||||
{
|
|
||||||
generationError("Qwen3 model not found: " + qwen3ModelPath);
|
|
||||||
busy = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!QFileInfo::exists(textEncoderModelPath))
|
|
||||||
{
|
|
||||||
generationError("Text encoder model not found: " + textEncoderModelPath);
|
|
||||||
busy = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!QFileInfo::exists(ditModelPath))
|
|
||||||
{
|
|
||||||
generationError("DiT model not found: " + ditModelPath);
|
|
||||||
busy = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!QFileInfo::exists(vaeModelPath))
|
|
||||||
{
|
|
||||||
generationError("VAE model not found: " + vaeModelPath);
|
|
||||||
busy = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.requestFilePath = tempDir + "/request_" + QString::number(request.uid) + ".json";
|
|
||||||
|
|
||||||
|
// Parse and modify the template
|
||||||
QJsonParseError parseError;
|
QJsonParseError parseError;
|
||||||
QJsonDocument templateDoc = QJsonDocument::fromJson(requestTemplate.toUtf8(), &parseError);
|
QJsonDocument templateDoc = QJsonDocument::fromJson(jsonTemplate.toUtf8(), &parseError);
|
||||||
if (!templateDoc.isObject())
|
if (!templateDoc.isObject())
|
||||||
{
|
{
|
||||||
generationError("Invalid JSON template: " + QString(parseError.errorString()));
|
emit parent->generationError("Invalid JSON template: " + QString(parseError.errorString()));
|
||||||
busy = false;
|
return;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject requestObj = templateDoc.object();
|
QJsonObject requestObj = templateDoc.object();
|
||||||
requestObj["caption"] = song.caption;
|
requestObj["caption"] = song.caption;
|
||||||
|
|
||||||
if (!song.lyrics.isEmpty())
|
if (!song.lyrics.isEmpty())
|
||||||
|
{
|
||||||
requestObj["lyrics"] = song.lyrics;
|
requestObj["lyrics"] = song.lyrics;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remove lyrics field if empty to let the LLM generate them
|
||||||
|
requestObj.remove("lyrics");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply vocal language override if set
|
||||||
if (!song.vocalLanguage.isEmpty())
|
if (!song.vocalLanguage.isEmpty())
|
||||||
|
{
|
||||||
requestObj["vocal_language"] = song.vocalLanguage;
|
requestObj["vocal_language"] = song.vocalLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
// Write the request file
|
// Write the request file
|
||||||
QFile requestFileHandle(request.requestFilePath);
|
QFile requestFileHandle(requestFile);
|
||||||
if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text))
|
if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||||
{
|
{
|
||||||
emit generationError("Failed to create request file: " + requestFileHandle.errorString());
|
emit parent->generationError("Failed to create request file: " + requestFileHandle.errorString());
|
||||||
busy = false;
|
return;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
requestFileHandle.write(QJsonDocument(requestObj).toJson(QJsonDocument::Indented));
|
requestFileHandle.write(QJsonDocument(requestObj).toJson(QJsonDocument::Indented));
|
||||||
requestFileHandle.close();
|
requestFileHandle.close();
|
||||||
|
|
||||||
QStringList qwen3Args;
|
// Use provided paths for acestep.cpp binaries
|
||||||
qwen3Args << "--request" << request.requestFilePath;
|
QString qwen3Binary = this->aceStepPath + "/ace-qwen3";
|
||||||
qwen3Args << "--model" << qwen3ModelPath;
|
QString ditVaeBinary = this->aceStepPath + "/dit-vae";
|
||||||
|
|
||||||
progressUpdate(30);
|
// Check if binaries exist
|
||||||
|
QFileInfo qwen3Info(qwen3Binary);
|
||||||
|
QFileInfo ditVaeInfo(ditVaeBinary);
|
||||||
|
|
||||||
qwenProcess.start(qwen3Binary, qwen3Args);
|
if (!qwen3Info.exists() || !qwen3Info.isExecutable())
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AceStep::qwenProcFinished(int code, QProcess::ExitStatus status)
|
|
||||||
{
|
|
||||||
QFile::remove(request.requestFilePath);
|
|
||||||
if(code != 0)
|
|
||||||
{
|
{
|
||||||
QString errorOutput = qwenProcess.readAllStandardError();
|
emit parent->generationError("ace-qwen3 binary not found at: " + qwen3Binary);
|
||||||
generationError("dit-vae exited with code " + QString::number(code) + ": " + errorOutput);
|
|
||||||
busy = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ditVaeBinary = request.aceStepPath + "/dit-vae";
|
|
||||||
QFileInfo ditVaeInfo(ditVaeBinary);
|
|
||||||
if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable())
|
if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable())
|
||||||
{
|
{
|
||||||
generationError("dit-vae binary not found at: " + ditVaeBinary);
|
emit parent->generationError("dit-vae binary not found at: " + ditVaeBinary);
|
||||||
busy = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.requestLlmFilePath = tempDir + "/request_" + QString::number(request.uid) + "0.json";
|
// Use provided model paths
|
||||||
if (!QFileInfo::exists(request.requestLlmFilePath))
|
QString qwen3Model = this->qwen3ModelPath;
|
||||||
|
QString textEncoderModel = this->textEncoderModelPath;
|
||||||
|
QString ditModel = this->ditModelPath;
|
||||||
|
QString vaeModel = this->vaeModelPath;
|
||||||
|
|
||||||
|
if (!QFileInfo::exists(qwen3Model))
|
||||||
{
|
{
|
||||||
generationError("ace-qwen3 failed to create enhaced request file "+request.requestLlmFilePath);
|
emit parent->generationError("Qwen3 model not found: " + qwen3Model);
|
||||||
busy = false;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QFileInfo::exists(textEncoderModel))
|
||||||
|
{
|
||||||
|
emit parent->generationError("Text encoder model not found: " + textEncoderModel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QFileInfo::exists(ditModel))
|
||||||
|
{
|
||||||
|
emit parent->generationError("DiT model not found: " + ditModel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QFileInfo::exists(vaeModel))
|
||||||
|
{
|
||||||
|
emit parent->generationError("VAE model not found: " + vaeModel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Run ace-qwen3 to generate lyrics and audio codes
|
||||||
|
QProcess qwen3Process;
|
||||||
|
QStringList qwen3Args;
|
||||||
|
qwen3Args << "--request" << requestFile;
|
||||||
|
qwen3Args << "--model" << qwen3Model;
|
||||||
|
|
||||||
|
emit parent->progressUpdate(20);
|
||||||
|
|
||||||
|
qwen3Process.start(qwen3Binary, qwen3Args);
|
||||||
|
if (!qwen3Process.waitForStarted())
|
||||||
|
{
|
||||||
|
emit parent->generationError("Failed to start ace-qwen3: " + qwen3Process.errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!qwen3Process.waitForFinished(60000)) // 60 second timeout
|
||||||
|
{
|
||||||
|
qwen3Process.terminate();
|
||||||
|
qwen3Process.waitForFinished(5000);
|
||||||
|
emit parent->generationError("ace-qwen3 timed out after 60 seconds");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int exitCode = qwen3Process.exitCode();
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
QString errorOutput = qwen3Process.readAllStandardError();
|
||||||
|
emit parent->generationError("ace-qwen3 exited with code " + QString::number(exitCode) + ": " + errorOutput);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString requestLmOutputFile = tempDir + "/request_" + QString::number(uid) + "0.json";
|
||||||
|
if (!QFileInfo::exists(requestLmOutputFile))
|
||||||
|
{
|
||||||
|
emit parent->generationError("ace-qwen3 failed to create enhaced request file "+requestLmOutputFile);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load lyrics from the enhanced request file
|
// Load lyrics from the enhanced request file
|
||||||
QFile lmOutputFile(request.requestLlmFilePath);
|
QFile lmOutputFile(requestLmOutputFile);
|
||||||
if (lmOutputFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
if (lmOutputFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
{
|
{
|
||||||
QJsonParseError parseError;
|
QJsonParseError parseError;
|
||||||
request.song.json = lmOutputFile.readAll();
|
song.json = lmOutputFile.readAll();
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(request.song.json.toUtf8(), &parseError);
|
QJsonDocument doc = QJsonDocument::fromJson(song.json.toUtf8(), &parseError);
|
||||||
lmOutputFile.close();
|
lmOutputFile.close();
|
||||||
|
|
||||||
if (doc.isObject() && !parseError.error)
|
if (doc.isObject() && !parseError.error)
|
||||||
{
|
{
|
||||||
QJsonObject obj = doc.object();
|
QJsonObject obj = doc.object();
|
||||||
if (obj.contains("lyrics") && obj["lyrics"].isString())
|
if (obj.contains("lyrics") && obj["lyrics"].isString())
|
||||||
request.song.lyrics = obj["lyrics"].toString();
|
{
|
||||||
|
song.lyrics = obj["lyrics"].toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Run dit-vae to generate audio
|
emit parent->progressUpdate(50);
|
||||||
QStringList ditVaeArgs;
|
|
||||||
ditVaeArgs << "--request"<<request.requestLlmFilePath;
|
|
||||||
ditVaeArgs << "--text-encoder"<<request.textEncoderModelPath;
|
|
||||||
ditVaeArgs << "--dit"<<request.ditModelPath;
|
|
||||||
ditVaeArgs << "--vae"<<request.vaeModelPath;
|
|
||||||
|
|
||||||
progressUpdate(60);
|
// Step 2: Run dit-vae to generate audio
|
||||||
|
QProcess ditVaeProcess;
|
||||||
|
QStringList ditVaeArgs;
|
||||||
|
ditVaeArgs << "--request" << requestLmOutputFile;
|
||||||
|
ditVaeArgs << "--text-encoder" << textEncoderModel;
|
||||||
|
ditVaeArgs << "--dit" << ditModel;
|
||||||
|
ditVaeArgs << "--vae" << vaeModel;
|
||||||
|
|
||||||
|
emit parent->progressUpdate(60);
|
||||||
|
|
||||||
ditVaeProcess.start(ditVaeBinary, ditVaeArgs);
|
ditVaeProcess.start(ditVaeBinary, ditVaeArgs);
|
||||||
}
|
if (!ditVaeProcess.waitForStarted())
|
||||||
|
{
|
||||||
|
emit parent->generationError("Failed to start dit-vae: " + ditVaeProcess.errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void AceStep::ditProcFinished(int code, QProcess::ExitStatus status)
|
if (!ditVaeProcess.waitForFinished(120000)) // 2 minute timeout
|
||||||
{
|
{
|
||||||
QFile::remove(request.requestLlmFilePath);
|
ditVaeProcess.terminate();
|
||||||
if (code != 0)
|
ditVaeProcess.waitForFinished(5000);
|
||||||
|
emit parent->generationError("dit-vae timed out after 2 minutes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = ditVaeProcess.exitCode();
|
||||||
|
if (exitCode != 0)
|
||||||
{
|
{
|
||||||
QString errorOutput = ditVaeProcess.readAllStandardError();
|
QString errorOutput = ditVaeProcess.readAllStandardError();
|
||||||
generationError("dit-vae exited with code " + QString::number(code) + ": " + errorOutput);
|
emit parent->generationError("dit-vae exited with code " + QString::number(exitCode) + ": " + errorOutput);
|
||||||
busy = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit parent->progressUpdate(90);
|
||||||
|
|
||||||
// Find the generated WAV file
|
// Find the generated WAV file
|
||||||
QString wavFile = tempDir+"/request_" + QString::number(request.uid) + "00.wav";
|
QString wavFile = QFileInfo(requestFile).absolutePath()+"/request_" + QString::number(uid) + "00.wav";
|
||||||
if (!QFileInfo::exists(wavFile))
|
if (!QFileInfo::exists(wavFile))
|
||||||
{
|
{
|
||||||
generationError("No WAV file generated at "+wavFile);
|
emit parent->generationError("No WAV file generated at "+wavFile);
|
||||||
busy = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
busy = false;
|
|
||||||
|
|
||||||
progressUpdate(100);
|
// Clean up temporary files
|
||||||
request.song.file = wavFile;
|
QFile::remove(requestLmOutputFile);
|
||||||
songGenerated(request.song);
|
QFile::remove(requestFile);
|
||||||
|
|
||||||
|
emit parent->progressUpdate(100);
|
||||||
|
song.file = wavFile;
|
||||||
|
emit parent->songGenerated(song);
|
||||||
|
parent->workerMutex.lock();
|
||||||
|
parent->currentWorker = nullptr;
|
||||||
|
parent->workerMutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SongItem& AceStepWorker::Worker::getSong()
|
||||||
|
{
|
||||||
|
return song;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,55 +2,64 @@
|
||||||
#define ACESTEPWORKER_H
|
#define ACESTEPWORKER_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QRunnable>
|
||||||
|
#include <QThreadPool>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QProcess>
|
#include <QJsonObject>
|
||||||
#include <QStandardPaths>
|
#include <QMutex>
|
||||||
|
|
||||||
#include "SongItem.h"
|
#include "SongItem.h"
|
||||||
|
|
||||||
class AceStep : public QObject
|
class AceStepWorker : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QProcess qwenProcess;
|
public:
|
||||||
QProcess ditVaeProcess;
|
explicit AceStepWorker(QObject *parent = nullptr);
|
||||||
|
~AceStepWorker();
|
||||||
|
|
||||||
bool busy = false;
|
void generateSong(const SongItem& song, const QString &jsonTemplate,
|
||||||
|
const QString &aceStepPath, const QString &qwen3ModelPath,
|
||||||
|
const QString &textEncoderModelPath, const QString &ditModelPath,
|
||||||
|
const QString &vaeModelPath);
|
||||||
|
void cancelGeneration();
|
||||||
|
bool songGenerateing(SongItem* song);
|
||||||
|
|
||||||
struct Request
|
signals:
|
||||||
|
void songGenerated(const SongItem& song);
|
||||||
|
void generationFinished();
|
||||||
|
void generationError(const QString &error);
|
||||||
|
void progressUpdate(int percent);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Worker : public QRunnable
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
Worker(AceStepWorker *parent, const SongItem& song, const QString &jsonTemplate,
|
||||||
|
const QString &aceStepPath, const QString &qwen3ModelPath,
|
||||||
|
const QString &textEncoderModelPath, const QString &ditModelPath,
|
||||||
|
const QString &vaeModelPath)
|
||||||
|
: parent(parent), song(song), jsonTemplate(jsonTemplate),
|
||||||
|
aceStepPath(aceStepPath), qwen3ModelPath(qwen3ModelPath),
|
||||||
|
textEncoderModelPath(textEncoderModelPath), ditModelPath(ditModelPath),
|
||||||
|
vaeModelPath(vaeModelPath) {}
|
||||||
|
|
||||||
|
void run() override;
|
||||||
|
|
||||||
|
const SongItem& getSong();
|
||||||
|
|
||||||
|
private:
|
||||||
|
AceStepWorker *parent;
|
||||||
SongItem song;
|
SongItem song;
|
||||||
uint64_t uid;
|
QString jsonTemplate;
|
||||||
QString aceStepPath;
|
QString aceStepPath;
|
||||||
|
QString qwen3ModelPath;
|
||||||
QString textEncoderModelPath;
|
QString textEncoderModelPath;
|
||||||
QString ditModelPath;
|
QString ditModelPath;
|
||||||
QString vaeModelPath;
|
QString vaeModelPath;
|
||||||
QString requestFilePath;
|
|
||||||
QString requestLlmFilePath;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Request request;
|
QMutex workerMutex;
|
||||||
|
Worker *currentWorker;
|
||||||
const QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void songGenerated(SongItem song);
|
|
||||||
void generationCancled(SongItem song);
|
|
||||||
void generationError(QString error);
|
|
||||||
void progressUpdate(int progress);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
bool requestGeneration(SongItem song, QString requestTemplate, QString aceStepPath,
|
|
||||||
QString qwen3ModelPath, QString textEncoderModelPath, QString ditModelPath,
|
|
||||||
QString vaeModelPath);
|
|
||||||
|
|
||||||
public:
|
|
||||||
AceStep(QObject* parent = nullptr);
|
|
||||||
bool isGenerateing(SongItem* song = nullptr);
|
|
||||||
void cancleGenerateion();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void qwenProcFinished(int code, QProcess::ExitStatus status);
|
|
||||||
void ditProcFinished(int code, QProcess::ExitStatus status);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ACESTEPWORKER_H
|
#endif // ACESTEPWORKER_H
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,13 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
ui(new Ui::MainWindow),
|
ui(new Ui::MainWindow),
|
||||||
songModel(new SongListModel(this)),
|
songModel(new SongListModel(this)),
|
||||||
audioPlayer(new AudioPlayer(this)),
|
audioPlayer(new AudioPlayer(this)),
|
||||||
aceStep(new AceStep(this)),
|
aceStepWorker(new AceStepWorker(this)),
|
||||||
playbackTimer(new QTimer(this)),
|
playbackTimer(new QTimer(this)),
|
||||||
isPlaying(false),
|
isPlaying(false),
|
||||||
isPaused(false),
|
isPaused(false),
|
||||||
shuffleMode(false),
|
shuffleMode(false),
|
||||||
isGeneratingNext(false)
|
isGeneratingNext(false)
|
||||||
{
|
{
|
||||||
aceStep->moveToThread(&aceThread);
|
|
||||||
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
// Setup lyrics display
|
// Setup lyrics display
|
||||||
|
|
@ -59,10 +57,9 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
connect(audioPlayer, &AudioPlayer::playbackStarted, this, &MainWindow::playbackStarted);
|
connect(audioPlayer, &AudioPlayer::playbackStarted, this, &MainWindow::playbackStarted);
|
||||||
connect(audioPlayer, &AudioPlayer::positionChanged, this, &MainWindow::updatePosition);
|
connect(audioPlayer, &AudioPlayer::positionChanged, this, &MainWindow::updatePosition);
|
||||||
connect(audioPlayer, &AudioPlayer::durationChanged, this, &MainWindow::updateDuration);
|
connect(audioPlayer, &AudioPlayer::durationChanged, this, &MainWindow::updateDuration);
|
||||||
connect(aceStep, &AceStep::songGenerated, this, &MainWindow::songGenerated);
|
connect(aceStepWorker, &AceStepWorker::songGenerated, this, &MainWindow::songGenerated);
|
||||||
connect(aceStep, &AceStep::generationCancled, this, &MainWindow::generationCanceld);
|
connect(aceStepWorker, &AceStepWorker::generationError, this, &MainWindow::generationError);
|
||||||
connect(aceStep, &AceStep::generationError, this, &MainWindow::generationError);
|
connect(aceStepWorker, &AceStepWorker::progressUpdate, ui->progressBar, &QProgressBar::setValue);
|
||||||
connect(aceStep, &AceStep::progressUpdate, ui->progressBar, &QProgressBar::setValue);
|
|
||||||
|
|
||||||
// Connect double-click on song list for editing (works with QTableView too)
|
// Connect double-click on song list for editing (works with QTableView too)
|
||||||
connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
|
connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
|
||||||
|
|
@ -92,16 +89,14 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
ui->songListView->setCurrentIndex(firstIndex);
|
ui->songListView->setCurrentIndex(firstIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->nowPlayingLabel->setText("Now Playing:");
|
|
||||||
|
|
||||||
currentSong = songModel->getSong(0);
|
currentSong = songModel->getSong(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
{
|
{
|
||||||
aceStep->cancleGenerateion();
|
// Auto-save playlist before closing
|
||||||
|
|
||||||
autoSavePlaylist();
|
autoSavePlaylist();
|
||||||
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
@ -130,8 +125,6 @@ void MainWindow::loadSettings()
|
||||||
{
|
{
|
||||||
QSettings settings("MusicGenerator", "AceStepGUI");
|
QSettings settings("MusicGenerator", "AceStepGUI");
|
||||||
|
|
||||||
isFirstRun = settings.value("firstRun", true).toBool();
|
|
||||||
|
|
||||||
// Load JSON template (default to simple configuration)
|
// Load JSON template (default to simple configuration)
|
||||||
jsonTemplate = settings.value("jsonTemplate",
|
jsonTemplate = settings.value("jsonTemplate",
|
||||||
"{\n\t\"inference_steps\": 8,\n\t\"shift\": 3.0,\n\t\"vocal_language\": \"en\"\n}").toString();
|
"{\n\t\"inference_steps\": 8,\n\t\"shift\": 3.0,\n\t\"vocal_language\": \"en\"\n}").toString();
|
||||||
|
|
@ -167,8 +160,6 @@ void MainWindow::saveSettings()
|
||||||
settings.setValue("textEncoderModelPath", textEncoderModelPath);
|
settings.setValue("textEncoderModelPath", textEncoderModelPath);
|
||||||
settings.setValue("ditModelPath", ditModelPath);
|
settings.setValue("ditModelPath", ditModelPath);
|
||||||
settings.setValue("vaeModelPath", vaeModelPath);
|
settings.setValue("vaeModelPath", vaeModelPath);
|
||||||
|
|
||||||
settings.setValue("firstRun", false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MainWindow::formatTime(int milliseconds)
|
QString MainWindow::formatTime(int milliseconds)
|
||||||
|
|
@ -276,10 +267,6 @@ void MainWindow::on_shuffleButton_clicked()
|
||||||
{
|
{
|
||||||
shuffleMode = ui->shuffleButton->isChecked();
|
shuffleMode = ui->shuffleButton->isChecked();
|
||||||
updateControls();
|
updateControls();
|
||||||
|
|
||||||
flushGenerationQueue();
|
|
||||||
if(isPlaying)
|
|
||||||
ensureSongsInQueue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_addSongButton_clicked()
|
void MainWindow::on_addSongButton_clicked()
|
||||||
|
|
@ -307,12 +294,16 @@ void MainWindow::on_songListView_doubleClicked(const QModelIndex &index)
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Temporarily disconnect the signal to prevent multiple invocations
|
||||||
|
// This happens when the dialog closes and triggers another double-click event
|
||||||
disconnect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
|
disconnect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
|
||||||
|
|
||||||
int row = index.row();
|
int row = index.row();
|
||||||
|
|
||||||
|
// Different behavior based on which column was clicked
|
||||||
if (index.column() == 0)
|
if (index.column() == 0)
|
||||||
{
|
{
|
||||||
|
// Column 0 (play indicator): Stop current playback and play this song
|
||||||
if (isPlaying)
|
if (isPlaying)
|
||||||
{
|
{
|
||||||
audioPlayer->stop();
|
audioPlayer->stop();
|
||||||
|
|
@ -323,13 +314,14 @@ void MainWindow::on_songListView_doubleClicked(const QModelIndex &index)
|
||||||
updateControls();
|
updateControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush the generation queue when user selects a different song
|
||||||
flushGenerationQueue();
|
flushGenerationQueue();
|
||||||
ui->nowPlayingLabel->setText("Now Playing: Waiting for generation...");
|
|
||||||
currentSong = songModel->getSong(row);
|
currentSong = songModel->getSong(row);
|
||||||
ensureSongsInQueue(true);
|
ensureSongsInQueue(true);
|
||||||
}
|
}
|
||||||
else if (index.column() == 1 || index.column() == 2)
|
else if (index.column() == 1 || index.column() == 2)
|
||||||
{
|
{
|
||||||
|
// Column 1 (caption): Edit the song
|
||||||
SongItem song = songModel->getSong(row);
|
SongItem song = songModel->getSong(row);
|
||||||
|
|
||||||
SongDialog dialog(this, song.caption, song.lyrics, song.vocalLanguage);
|
SongDialog dialog(this, song.caption, song.lyrics, song.vocalLanguage);
|
||||||
|
|
@ -340,12 +332,14 @@ void MainWindow::on_songListView_doubleClicked(const QModelIndex &index)
|
||||||
QString lyrics = dialog.getLyrics();
|
QString lyrics = dialog.getLyrics();
|
||||||
QString vocalLanguage = dialog.getVocalLanguage();
|
QString vocalLanguage = dialog.getVocalLanguage();
|
||||||
|
|
||||||
|
// Update the model - use column 1 for the song name
|
||||||
songModel->setData(songModel->index(row, 1), caption, SongListModel::CaptionRole);
|
songModel->setData(songModel->index(row, 1), caption, SongListModel::CaptionRole);
|
||||||
songModel->setData(songModel->index(row, 1), lyrics, SongListModel::LyricsRole);
|
songModel->setData(songModel->index(row, 1), lyrics, SongListModel::LyricsRole);
|
||||||
songModel->setData(songModel->index(row, 1), vocalLanguage, SongListModel::VocalLanguageRole);
|
songModel->setData(songModel->index(row, 1), vocalLanguage, SongListModel::VocalLanguageRole);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reconnect the signal after dialog is closed
|
||||||
connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
|
connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,6 +395,7 @@ void MainWindow::on_advancedSettingsButton_clicked()
|
||||||
vaeModelPath = dialog.getVAEModelPath();
|
vaeModelPath = dialog.getVAEModelPath();
|
||||||
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
QMessageBox::information(this, "Settings Saved", "Advanced settings have been saved successfully.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -431,16 +426,11 @@ void MainWindow::songGenerated(const SongItem& song)
|
||||||
{
|
{
|
||||||
generatedSongQueue.enqueue(song);
|
generatedSongQueue.enqueue(song);
|
||||||
}
|
}
|
||||||
ui->statusbar->showMessage("idle");
|
ui->statusLabel->setText("idle");
|
||||||
|
|
||||||
ensureSongsInQueue();
|
ensureSongsInQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::generationCanceld(const SongItem& song)
|
|
||||||
{
|
|
||||||
ui->statusbar->showMessage("Geneartion cancled: " + song.caption);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::playNextSong()
|
void MainWindow::playNextSong()
|
||||||
{
|
{
|
||||||
if (!isPlaying)
|
if (!isPlaying)
|
||||||
|
|
@ -520,7 +510,7 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent)
|
||||||
|
|
||||||
SongItem lastSong;
|
SongItem lastSong;
|
||||||
SongItem workerSong;
|
SongItem workerSong;
|
||||||
if(aceStep->isGenerateing(&workerSong))
|
if(aceStepWorker->songGenerateing(&workerSong))
|
||||||
lastSong = workerSong;
|
lastSong = workerSong;
|
||||||
else if(!generatedSongQueue.empty())
|
else if(!generatedSongQueue.empty())
|
||||||
lastSong = generatedSongQueue.last();
|
lastSong = generatedSongQueue.last();
|
||||||
|
|
@ -540,8 +530,8 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent)
|
||||||
|
|
||||||
isGeneratingNext = true;
|
isGeneratingNext = true;
|
||||||
|
|
||||||
ui->statusbar->showMessage("Generateing: "+nextSong.caption);
|
ui->statusLabel->setText("Generateing: "+nextSong.caption);
|
||||||
aceStep->requestGeneration(nextSong, jsonTemplate,
|
aceStepWorker->generateSong(nextSong, jsonTemplate,
|
||||||
aceStepPath, qwen3ModelPath,
|
aceStepPath, qwen3ModelPath,
|
||||||
textEncoderModelPath, ditModelPath,
|
textEncoderModelPath, ditModelPath,
|
||||||
vaeModelPath);
|
vaeModelPath);
|
||||||
|
|
@ -550,7 +540,7 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent)
|
||||||
void MainWindow::flushGenerationQueue()
|
void MainWindow::flushGenerationQueue()
|
||||||
{
|
{
|
||||||
generatedSongQueue.clear();
|
generatedSongQueue.clear();
|
||||||
aceStep->cancleGenerateion();
|
aceStepWorker->cancelGeneration();
|
||||||
isGeneratingNext = false;
|
isGeneratingNext = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -807,10 +797,3 @@ bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList<SongItem> &
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::show()
|
|
||||||
{
|
|
||||||
QMainWindow::show();
|
|
||||||
if(isFirstRun)
|
|
||||||
QMessageBox::information(this, "Welcome", "Welcome to AceStepGUI! Please configure paths in Settings→Ace Step before generateing your first song.");
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QQueue>
|
#include <QQueue>
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QThread>
|
#include <cstdint>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
@ -27,41 +27,10 @@ class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Ui::MainWindow *ui;
|
|
||||||
SongListModel *songModel;
|
|
||||||
AudioPlayer *audioPlayer;
|
|
||||||
QThread aceThread;
|
|
||||||
AceStep *aceStep;
|
|
||||||
QTimer *playbackTimer;
|
|
||||||
|
|
||||||
QString formatTime(int milliseconds);
|
|
||||||
|
|
||||||
SongItem currentSong;
|
|
||||||
bool isPlaying;
|
|
||||||
bool isPaused;
|
|
||||||
bool shuffleMode;
|
|
||||||
bool isGeneratingNext;
|
|
||||||
bool isFirstRun;
|
|
||||||
QString jsonTemplate;
|
|
||||||
|
|
||||||
// Path settings
|
|
||||||
QString aceStepPath;
|
|
||||||
QString qwen3ModelPath;
|
|
||||||
QString textEncoderModelPath;
|
|
||||||
QString ditModelPath;
|
|
||||||
QString vaeModelPath;
|
|
||||||
|
|
||||||
// Queue for generated songs
|
|
||||||
static constexpr int generationTresh = 2;
|
|
||||||
QQueue<SongItem> generatedSongQueue;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MainWindow(QWidget *parent = nullptr);
|
MainWindow(QWidget *parent = nullptr);
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
|
|
||||||
public slots:
|
|
||||||
void show();
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_playButton_clicked();
|
void on_playButton_clicked();
|
||||||
void on_pauseButton_clicked();
|
void on_pauseButton_clicked();
|
||||||
|
|
@ -78,7 +47,6 @@ private slots:
|
||||||
void on_songListView_doubleClicked(const QModelIndex &index);
|
void on_songListView_doubleClicked(const QModelIndex &index);
|
||||||
|
|
||||||
void songGenerated(const SongItem& song);
|
void songGenerated(const SongItem& song);
|
||||||
void generationCanceld(const SongItem& song);
|
|
||||||
void playNextSong();
|
void playNextSong();
|
||||||
void playbackStarted();
|
void playbackStarted();
|
||||||
void updatePlaybackStatus(bool playing);
|
void updatePlaybackStatus(bool playing);
|
||||||
|
|
@ -89,6 +57,36 @@ private slots:
|
||||||
void on_actionAppendPlaylist();
|
void on_actionAppendPlaylist();
|
||||||
void on_actionSaveSong();
|
void on_actionSaveSong();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void startNextSongGeneration();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::MainWindow *ui;
|
||||||
|
SongListModel *songModel;
|
||||||
|
AudioPlayer *audioPlayer;
|
||||||
|
AceStepWorker *aceStepWorker;
|
||||||
|
QTimer *playbackTimer;
|
||||||
|
|
||||||
|
QString formatTime(int milliseconds);
|
||||||
|
|
||||||
|
SongItem currentSong;
|
||||||
|
bool isPlaying;
|
||||||
|
bool isPaused;
|
||||||
|
bool shuffleMode;
|
||||||
|
bool isGeneratingNext;
|
||||||
|
QString jsonTemplate;
|
||||||
|
|
||||||
|
// Path settings
|
||||||
|
QString aceStepPath;
|
||||||
|
QString qwen3ModelPath;
|
||||||
|
QString textEncoderModelPath;
|
||||||
|
QString ditModelPath;
|
||||||
|
QString vaeModelPath;
|
||||||
|
|
||||||
|
// Queue for generated songs
|
||||||
|
static constexpr int generationTresh = 2;
|
||||||
|
QQueue<SongItem> generatedSongQueue;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
|
|
|
||||||
|
|
@ -1,361 +1,359 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>MainWindow</class>
|
<class>MainWindow</class>
|
||||||
<widget class="QMainWindow" name="MainWindow">
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>800</width>
|
<width>800</width>
|
||||||
<height>600</height>
|
<height>600</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Aceradio</string>
|
<string>Aceradio</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset resource="../res/resources.qrc">
|
<iconset resource="../res/resources.qrc">
|
||||||
<normaloff>:/icons/xyz.uvos.aceradio.png</normaloff>:/icons/xyz.uvos.aceradio.png</iconset>
|
<normaloff>:/icons/xyz.uvos.aceradio.png</normaloff>:/icons/xyz.uvos.aceradio.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="centralwidget">
|
<widget class="QWidget" name="centralwidget">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTabWidget" name="mainTabWidget">
|
<widget class="QTabWidget" name="mainTabWidget">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="songsTab">
|
<widget class="QWidget" name="songsTab">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>Songs</string>
|
<string>Songs</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTableView" name="songListView">
|
<widget class="QTableView" name="songListView">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>0</width>
|
<width>0</width>
|
||||||
<height>200</height>
|
<height>200</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="editTriggers">
|
<property name="editTriggers">
|
||||||
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
||||||
</property>
|
</property>
|
||||||
<property name="selectionBehavior">
|
<property name="selectionBehavior">
|
||||||
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
|
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="infoTab">
|
<widget class="QWidget" name="infoTab">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>Info</string>
|
<string>Info</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPlainTextEdit" name="jsonTextEdit">
|
<widget class="QPlainTextEdit" name="jsonTextEdit">
|
||||||
<property name="readOnly">
|
<property name="readOnly">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="lyricsTab">
|
<widget class="QWidget" name="lyricsTab">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>Lyrics</string>
|
<string>Lyrics</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTextEdit" name="lyricsTextEdit">
|
<widget class="QTextEdit" name="lyricsTextEdit">
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<family>Monospace</family>
|
<family>Monospace</family>
|
||||||
</font>
|
</font>
|
||||||
</property>
|
</property>
|
||||||
<property name="readOnly">
|
<property name="readOnly">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="buttonLayout">
|
<layout class="QHBoxLayout" name="buttonLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="addSongButton">
|
<widget class="QPushButton" name="addSongButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Add Song</string>
|
<string>Add Song</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="removeSongButton">
|
<widget class="QPushButton" name="removeSongButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Remove Song</string>
|
<string>Remove Song</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
<width>40</width>
|
<width>40</width>
|
||||||
<height>20</height>
|
<height>20</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="ElidedLabel" name="nowPlayingLabel">
|
<widget class="QLabel" name="nowPlayingLabel">
|
||||||
<property name="minimumSize">
|
<property name="layoutDirection">
|
||||||
<size>
|
<enum>Qt::LayoutDirection::LeftToRight</enum>
|
||||||
<width>0</width>
|
</property>
|
||||||
<height>20</height>
|
<property name="text">
|
||||||
</size>
|
<string>Now Playing:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="frameShape">
|
<property name="alignment">
|
||||||
<enum>QFrame::Shape::StyledPanel</enum>
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
|
||||||
</property>
|
</property>
|
||||||
<property name="frameShadow">
|
</widget>
|
||||||
<enum>QFrame::Shadow::Raised</enum>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<layout class="QHBoxLayout" name="timeControlsLayout">
|
||||||
</item>
|
<item>
|
||||||
<item>
|
<widget class="QLabel" name="elapsedTimeLabel">
|
||||||
<layout class="QHBoxLayout" name="timeControlsLayout">
|
<property name="text">
|
||||||
<item>
|
<string>0:00</string>
|
||||||
<widget class="QLabel" name="elapsedTimeLabel">
|
</property>
|
||||||
<property name="text">
|
</widget>
|
||||||
<string>0:00</string>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<widget class="ClickableSlider" name="positionSlider" native="true">
|
||||||
</item>
|
<property name="mouseTracking">
|
||||||
<item>
|
<bool>false</bool>
|
||||||
<widget class="ClickableSlider" name="positionSlider" native="true">
|
</property>
|
||||||
<property name="mouseTracking">
|
<property name="tabletTracking">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="tabletTracking">
|
</widget>
|
||||||
<bool>false</bool>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<widget class="QLabel" name="durationLabel">
|
||||||
</item>
|
<property name="text">
|
||||||
<item>
|
<string>0:00</string>
|
||||||
<widget class="QLabel" name="durationLabel">
|
</property>
|
||||||
<property name="text">
|
</widget>
|
||||||
<string>0:00</string>
|
</item>
|
||||||
</property>
|
</layout>
|
||||||
</widget>
|
</item>
|
||||||
</item>
|
<item>
|
||||||
</layout>
|
<widget class="QFrame" name="controlsFrame">
|
||||||
</item>
|
<property name="frameShape">
|
||||||
<item>
|
<enum>QFrame::Shape::StyledPanel</enum>
|
||||||
<widget class="QFrame" name="controlsFrame">
|
</property>
|
||||||
<property name="frameShape">
|
<property name="frameShadow">
|
||||||
<enum>QFrame::Shape::StyledPanel</enum>
|
<enum>QFrame::Shadow::Raised</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="frameShadow">
|
<layout class="QHBoxLayout" name="controlsLayout">
|
||||||
<enum>QFrame::Shadow::Raised</enum>
|
<item>
|
||||||
</property>
|
<widget class="QPushButton" name="playButton">
|
||||||
<layout class="QHBoxLayout" name="controlsLayout">
|
<property name="text">
|
||||||
<item>
|
<string>Play</string>
|
||||||
<widget class="QPushButton" name="playButton">
|
</property>
|
||||||
<property name="text">
|
<property name="icon">
|
||||||
<string>Play</string>
|
<iconset theme="media-playback-start"/>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
</widget>
|
||||||
<iconset theme="media-playback-start"/>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<widget class="QPushButton" name="pauseButton">
|
||||||
</item>
|
<property name="text">
|
||||||
<item>
|
<string>Pause</string>
|
||||||
<widget class="QPushButton" name="pauseButton">
|
</property>
|
||||||
<property name="text">
|
<property name="icon">
|
||||||
<string>Pause</string>
|
<iconset theme="media-playback-pause"/>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
</widget>
|
||||||
<iconset theme="media-playback-pause"/>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<widget class="QPushButton" name="skipButton">
|
||||||
</item>
|
<property name="text">
|
||||||
<item>
|
<string>Skip</string>
|
||||||
<widget class="QPushButton" name="skipButton">
|
</property>
|
||||||
<property name="text">
|
<property name="icon">
|
||||||
<string>Skip</string>
|
<iconset theme="media-skip-forward"/>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
</widget>
|
||||||
<iconset theme="media-skip-forward"/>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<widget class="QPushButton" name="stopButton">
|
||||||
</item>
|
<property name="text">
|
||||||
<item>
|
<string>Stop</string>
|
||||||
<widget class="QPushButton" name="stopButton">
|
</property>
|
||||||
<property name="text">
|
<property name="icon">
|
||||||
<string>Stop</string>
|
<iconset theme="media-playback-stop"/>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
</widget>
|
||||||
<iconset theme="media-playback-stop"/>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<widget class="QPushButton" name="shuffleButton">
|
||||||
</item>
|
<property name="text">
|
||||||
<item>
|
<string>Shuffle</string>
|
||||||
<widget class="QPushButton" name="shuffleButton">
|
</property>
|
||||||
<property name="text">
|
<property name="icon">
|
||||||
<string>Shuffle</string>
|
<iconset theme="media-playlist-shuffle"/>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="checkable">
|
||||||
<iconset theme="media-playlist-shuffle"/>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="checkable">
|
</widget>
|
||||||
<bool>true</bool>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<spacer name="horizontalSpacer_2">
|
||||||
</item>
|
<property name="orientation">
|
||||||
<item>
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
<spacer name="horizontalSpacer_2">
|
</property>
|
||||||
<property name="orientation">
|
<property name="sizeHint" stdset="0">
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
<size>
|
||||||
</property>
|
<width>40</width>
|
||||||
<property name="sizeHint" stdset="0">
|
<height>20</height>
|
||||||
<size>
|
</size>
|
||||||
<width>40</width>
|
</property>
|
||||||
<height>20</height>
|
</spacer>
|
||||||
</size>
|
</item>
|
||||||
</property>
|
</layout>
|
||||||
</spacer>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
<item>
|
||||||
</widget>
|
<layout class="QVBoxLayout" name="statusLayout">
|
||||||
</item>
|
<item>
|
||||||
<item>
|
<widget class="QProgressBar" name="progressBar">
|
||||||
<layout class="QVBoxLayout" name="statusLayout">
|
<property name="value">
|
||||||
<item>
|
<number>0</number>
|
||||||
<widget class="QProgressBar" name="progressBar">
|
</property>
|
||||||
<property name="value">
|
</widget>
|
||||||
<number>0</number>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<widget class="QLabel" name="statusLabel">
|
||||||
</item>
|
<property name="text">
|
||||||
</layout>
|
<string/>
|
||||||
</item>
|
</property>
|
||||||
</layout>
|
</widget>
|
||||||
</widget>
|
</item>
|
||||||
<widget class="QMenuBar" name="menubar">
|
</layout>
|
||||||
<property name="geometry">
|
</item>
|
||||||
<rect>
|
</layout>
|
||||||
<x>0</x>
|
</widget>
|
||||||
<y>0</y>
|
<widget class="QMenuBar" name="menubar">
|
||||||
<width>800</width>
|
<property name="geometry">
|
||||||
<height>32</height>
|
<rect>
|
||||||
</rect>
|
<x>0</x>
|
||||||
</property>
|
<y>0</y>
|
||||||
<widget class="QMenu" name="menuFile">
|
<width>800</width>
|
||||||
<property name="title">
|
<height>32</height>
|
||||||
<string>File</string>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionSaveSong"/>
|
<widget class="QMenu" name="menuFile">
|
||||||
<addaction name="actionSavePlaylist"/>
|
<property name="title">
|
||||||
<addaction name="actionLoadPlaylist"/>
|
<string>File</string>
|
||||||
<addaction name="actionAppendPlaylist"/>
|
</property>
|
||||||
<addaction name="actionClearPlaylist"/>
|
<addaction name="actionSaveSong"/>
|
||||||
<addaction name="actionQuit"/>
|
<addaction name="actionSavePlaylist"/>
|
||||||
</widget>
|
<addaction name="actionLoadPlaylist"/>
|
||||||
<widget class="QMenu" name="menuSettings">
|
<addaction name="actionAppendPlaylist"/>
|
||||||
<property name="title">
|
<addaction name="actionClearPlaylist"/>
|
||||||
<string>Settings</string>
|
<addaction name="actionQuit"/>
|
||||||
</property>
|
</widget>
|
||||||
<addaction name="actionAdvancedSettings"/>
|
<widget class="QMenu" name="menuSettings">
|
||||||
</widget>
|
<property name="title">
|
||||||
<addaction name="menuFile"/>
|
<string>Settings</string>
|
||||||
<addaction name="menuSettings"/>
|
</property>
|
||||||
</widget>
|
<addaction name="actionAdvancedSettings"/>
|
||||||
<widget class="QStatusBar" name="statusbar"/>
|
</widget>
|
||||||
<action name="actionSavePlaylist">
|
<addaction name="menuFile"/>
|
||||||
<property name="icon">
|
<addaction name="menuSettings"/>
|
||||||
<iconset theme="QIcon::ThemeIcon::DocumentSaveAs"/>
|
</widget>
|
||||||
</property>
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
<property name="text">
|
<action name="actionSavePlaylist">
|
||||||
<string>Save Playlist</string>
|
<property name="icon">
|
||||||
</property>
|
<iconset theme="QIcon::ThemeIcon::DocumentSaveAs"/>
|
||||||
<property name="shortcut">
|
</property>
|
||||||
<string>Ctrl+S</string>
|
<property name="text">
|
||||||
</property>
|
<string>Save Playlist</string>
|
||||||
</action>
|
</property>
|
||||||
<action name="actionLoadPlaylist">
|
<property name="shortcut">
|
||||||
<property name="icon">
|
<string>Ctrl+S</string>
|
||||||
<iconset theme="QIcon::ThemeIcon::DocumentOpen"/>
|
</property>
|
||||||
</property>
|
</action>
|
||||||
<property name="text">
|
<action name="actionLoadPlaylist">
|
||||||
<string>Load Playlist...</string>
|
<property name="icon">
|
||||||
</property>
|
<iconset theme="QIcon::ThemeIcon::DocumentOpen"/>
|
||||||
<property name="shortcut">
|
</property>
|
||||||
<string>Ctrl+O</string>
|
<property name="text">
|
||||||
</property>
|
<string>Load Playlist...</string>
|
||||||
</action>
|
</property>
|
||||||
<action name="actionAdvancedSettings">
|
<property name="shortcut">
|
||||||
<property name="text">
|
<string>Ctrl+O</string>
|
||||||
<string>Ace Step</string>
|
</property>
|
||||||
</property>
|
</action>
|
||||||
</action>
|
<action name="actionAdvancedSettings">
|
||||||
<action name="actionQuit">
|
<property name="text">
|
||||||
<property name="icon">
|
<string>Ace Step</string>
|
||||||
<iconset theme="QIcon::ThemeIcon::ApplicationExit"/>
|
</property>
|
||||||
</property>
|
</action>
|
||||||
<property name="text">
|
<action name="actionQuit">
|
||||||
<string>Quit</string>
|
<property name="icon">
|
||||||
</property>
|
<iconset theme="QIcon::ThemeIcon::ApplicationExit"/>
|
||||||
<property name="shortcut">
|
</property>
|
||||||
<string>Ctrl+Q</string>
|
<property name="text">
|
||||||
</property>
|
<string>Quit</string>
|
||||||
</action>
|
</property>
|
||||||
<action name="actionClearPlaylist">
|
<property name="shortcut">
|
||||||
<property name="icon">
|
<string>Ctrl+Q</string>
|
||||||
<iconset theme="QIcon::ThemeIcon::EditDelete"/>
|
</property>
|
||||||
</property>
|
</action>
|
||||||
<property name="text">
|
<action name="actionClearPlaylist">
|
||||||
<string>Clear Playlist</string>
|
<property name="icon">
|
||||||
</property>
|
<iconset theme="QIcon::ThemeIcon::EditDelete"/>
|
||||||
</action>
|
</property>
|
||||||
<action name="actionSaveSong">
|
<property name="text">
|
||||||
<property name="icon">
|
<string>Clear Playlist</string>
|
||||||
<iconset theme="QIcon::ThemeIcon::DocumentSaveAs"/>
|
</property>
|
||||||
</property>
|
</action>
|
||||||
<property name="text">
|
<action name="actionSaveSong">
|
||||||
<string>Save Song</string>
|
<property name="icon">
|
||||||
</property>
|
<iconset theme="QIcon::ThemeIcon::DocumentSaveAs"/>
|
||||||
</action>
|
</property>
|
||||||
<action name="actionAppendPlaylist">
|
<property name="text">
|
||||||
<property name="icon">
|
<string>Save Song</string>
|
||||||
<iconset theme="QIcon::ThemeIcon::DocumentOpen"/>
|
</property>
|
||||||
</property>
|
</action>
|
||||||
<property name="text">
|
<action name="actionAppendPlaylist">
|
||||||
<string>Append Playlist</string>
|
<property name="icon">
|
||||||
</property>
|
<iconset theme="QIcon::ThemeIcon::DocumentOpen"/>
|
||||||
</action>
|
</property>
|
||||||
</widget>
|
<property name="text">
|
||||||
<customwidgets>
|
<string>Append Playlist</string>
|
||||||
<customwidget>
|
</property>
|
||||||
<class>ClickableSlider</class>
|
</action>
|
||||||
<extends>QWidget</extends>
|
</widget>
|
||||||
<header>src/clickableslider.h</header>
|
<customwidgets>
|
||||||
</customwidget>
|
<customwidget>
|
||||||
<customwidget>
|
<class>ClickableSlider</class>
|
||||||
<class>ElidedLabel</class>
|
<extends>QWidget</extends>
|
||||||
<extends>QFrame</extends>
|
<header>src/clickableslider.h</header>
|
||||||
<header>src/elidedlabel.h</header>
|
</customwidget>
|
||||||
<container>1</container>
|
</customwidgets>
|
||||||
</customwidget>
|
<resources>
|
||||||
</customwidgets>
|
<include location="../res/resources.qrc"/>
|
||||||
<resources>
|
</resources>
|
||||||
<include location="../res/resources.qrc"/>
|
<connections/>
|
||||||
</resources>
|
</ui>
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,11 @@ int SongListModel::findNextIndex(int currentIndex, bool shuffle) const
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (shuffle)
|
if (shuffle)
|
||||||
return QRandomGenerator::global()->bounded(songList.size());
|
{
|
||||||
|
// Simple random selection for shuffle mode
|
||||||
|
QRandomGenerator generator;
|
||||||
|
return generator.bounded(songList.size());
|
||||||
|
}
|
||||||
|
|
||||||
// Sequential playback
|
// Sequential playback
|
||||||
int nextIndex = currentIndex + 1;
|
int nextIndex = currentIndex + 1;
|
||||||
|
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
#include "elidedlabel.h"
|
|
||||||
|
|
||||||
#include <QTextLayout>
|
|
||||||
#include <QPainter>
|
|
||||||
|
|
||||||
ElidedLabel::ElidedLabel(const QString &text, QWidget *parent)
|
|
||||||
: QFrame(parent)
|
|
||||||
, elided(false)
|
|
||||||
, content(text)
|
|
||||||
{
|
|
||||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
||||||
}
|
|
||||||
|
|
||||||
ElidedLabel::ElidedLabel(QWidget *parent)
|
|
||||||
: QFrame(parent)
|
|
||||||
, elided(false)
|
|
||||||
, content("")
|
|
||||||
{
|
|
||||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ElidedLabel::setText(const QString &newText)
|
|
||||||
{
|
|
||||||
content = newText;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ElidedLabel::paintEvent(QPaintEvent *event)
|
|
||||||
{
|
|
||||||
QFrame::paintEvent(event);
|
|
||||||
|
|
||||||
QPainter painter(this);
|
|
||||||
QFontMetrics fontMetrics = painter.fontMetrics();
|
|
||||||
|
|
||||||
bool didElide = false;
|
|
||||||
int lineSpacing = fontMetrics.lineSpacing();
|
|
||||||
int y = 0;
|
|
||||||
|
|
||||||
QTextLayout textLayout(content, painter.font());
|
|
||||||
textLayout.beginLayout();
|
|
||||||
forever {
|
|
||||||
QTextLine line = textLayout.createLine();
|
|
||||||
|
|
||||||
if (!line.isValid())
|
|
||||||
break;
|
|
||||||
|
|
||||||
line.setLineWidth(width());
|
|
||||||
int nextLineY = y + lineSpacing;
|
|
||||||
|
|
||||||
if (height() >= nextLineY + lineSpacing) {
|
|
||||||
line.draw(&painter, QPoint(0, y));
|
|
||||||
y = nextLineY;
|
|
||||||
//! [2]
|
|
||||||
//! [3]
|
|
||||||
} else {
|
|
||||||
QString lastLine = content.mid(line.textStart());
|
|
||||||
QString elidedLastLine = fontMetrics.elidedText(lastLine, Qt::ElideRight, width());
|
|
||||||
painter.drawText(QPoint(0, y + fontMetrics.ascent()), elidedLastLine);
|
|
||||||
line = textLayout.createLine();
|
|
||||||
didElide = line.isValid();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
textLayout.endLayout();
|
|
||||||
|
|
||||||
if (didElide != elided) {
|
|
||||||
elided = didElide;
|
|
||||||
emit elisionChanged(didElide);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
#ifndef ELIDEDLABEL_H
|
|
||||||
#define ELIDEDLABEL_H
|
|
||||||
|
|
||||||
#include <QFrame>
|
|
||||||
|
|
||||||
class ElidedLabel : public QFrame
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(QString text READ text WRITE setText)
|
|
||||||
Q_PROPERTY(bool isElided READ isElided)
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ElidedLabel(const QString &text, QWidget *parent = 0);
|
|
||||||
explicit ElidedLabel(QWidget *parent = 0);
|
|
||||||
|
|
||||||
void setText(const QString &text);
|
|
||||||
const QString & text() const { return content; }
|
|
||||||
bool isElided() const { return elided; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void elisionChanged(bool elided);
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool elided;
|
|
||||||
QString content;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif // ELIDEDLABEL_H
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#include <QApplication>
|
|
||||||
#include <QThread>
|
|
||||||
|
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QStyleFactory>
|
||||||
|
#include <QIcon>
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue