Merge pull request #3741 from lasconic/fix-extension-master

fix #273032: implement packages to install soundfont, templates, etc.
This commit is contained in:
Nicolas Froment 2018-07-10 15:08:14 +02:00 committed by GitHub
commit 5f9fde47a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 1791 additions and 435 deletions

View file

@ -21,6 +21,7 @@
#include "synthesizer/event.h"
#include "synthesizer/msynthesizer.h"
#include "mscore/preferences.h"
#include "mscore/extension.h"
#include "fluid.h"
#include "sfont.h"
@ -623,9 +624,7 @@ bool Fluid::loadSoundFonts(const QStringList& sl)
locker.unlock();
bool ok = true;
QFileInfoList l = sfFiles();
for (int i = sl.size() - 1; i >= 0; --i) {
QString s = sl[i];
if (s.isEmpty())
@ -896,6 +895,10 @@ QFileInfoList Fluid::sfFiles()
QStringList pl = preferences.getString(PREF_APP_PATHS_MYSOUNDFONTS).split(";");
pl.prepend(QFileInfo(QString("%1%2").arg(mscoreGlobalShare).arg("sound")).absoluteFilePath());
// append extensions directory
QStringList extensionsDir = Ms::Extension::getDirectoriesByType(Ms::Extension::soundfontsDir);
pl.append(extensionsDir);
foreach (const QString& s, pl) {
QString ss(s);
if (!s.isEmpty() && s[0] == '~')

View file

@ -52,6 +52,19 @@ static InstrumentGroup* searchInstrumentGroup(const QString& name)
return nullptr;
}
//---------------------------------------------------------
// searchArticulation
//---------------------------------------------------------
static MidiArticulation searchArticulation(const QString& name)
{
foreach(MidiArticulation a, articulation) {
if (a.name == name)
return a;
}
return MidiArticulation();
}
//---------------------------------------------------------
// readStaffIdx
//---------------------------------------------------------
@ -67,7 +80,7 @@ static int readStaffIdx(XmlReader& e)
}
//---------------------------------------------------------
// readInstrumentGroup
// read InstrumentGroup
//---------------------------------------------------------
void InstrumentGroup::read(XmlReader& e)
@ -108,6 +121,16 @@ void InstrumentGroup::read(XmlReader& e)
id = name.toLower().replace(" ", "-");
}
//---------------------------------------------------------
// clear InstrumentGroup
//---------------------------------------------------------
void InstrumentGroup::clear()
{
qDeleteAll(instrumentTemplates);
instrumentTemplates.clear();
}
//---------------------------------------------------------
// InstrumentTemplate
//---------------------------------------------------------
@ -586,6 +609,21 @@ bool saveInstrumentTemplates1(const QString& instrTemplates)
return true;
}
//---------------------------------------------------------
// clearInstrumentTemplates
//---------------------------------------------------------
void clearInstrumentTemplates()
{
for (InstrumentGroup* g : instrumentGroups)
g->clear();
qDeleteAll(instrumentGroups);
instrumentGroups.clear();
qDeleteAll(instrumentGenres);
instrumentGenres.clear();
articulation.clear();
}
//---------------------------------------------------------
// loadInstrumentTemplates
//---------------------------------------------------------
@ -604,8 +642,8 @@ bool loadInstrumentTemplates(const QString& instrTemplates)
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "instrument-group" || tag == "InstrumentGroup") {
QString id(e.attribute("id"));
InstrumentGroup* group = searchInstrumentGroup(id);
QString idGroup(e.attribute("id"));
InstrumentGroup* group = searchInstrumentGroup(idGroup);
if (group == 0) {
group = new InstrumentGroup;
instrumentGroups.append(group);
@ -614,13 +652,14 @@ bool loadInstrumentTemplates(const QString& instrTemplates)
}
else if (tag == "Articulation") {
// read global articulation
MidiArticulation a;
QString name(e.attribute("name"));
MidiArticulation a = searchArticulation(name);
a.read(e);
articulation.append(a);
}
else if (tag == "Genre") {
QString id(e.attribute("id"));
InstrumentGenre* genre = searchInstrumentGenre(id);
QString idGenre(e.attribute("id"));
InstrumentGenre* genre = searchInstrumentGenre(idGenre);
if (!genre) {
genre = new InstrumentGenre;
instrumentGenres.append(genre);

View file

@ -111,6 +111,7 @@ struct InstrumentGroup {
bool extended; // belongs to extended instruments set if true
QList<InstrumentTemplate*> instrumentTemplates;
void read(XmlReader&);
void clear();
InstrumentGroup() { extended = false; }
};
@ -118,6 +119,7 @@ struct InstrumentGroup {
extern QList<InstrumentGenre *> instrumentGenres;
extern QList<MidiArticulation> articulation;
extern QList<InstrumentGroup*> instrumentGroups;
extern void clearInstrumentTemplates();
extern bool loadInstrumentTemplates(const QString& instrTemplates);
extern bool saveInstrumentTemplates(const QString& instrTemplates);
extern InstrumentTemplate* searchTemplate(const QString& name);

View file

@ -781,7 +781,7 @@ Score::FileError MasterScore::loadCompressedMsc(QIODevice* io, bool ignoreVersio
QByteArray dbuf = uz.fileData(rootfile);
if (dbuf.isEmpty()) {
QList<MQZipReader::FileInfo> fil = uz.fileInfoList();
QVector<MQZipReader::FileInfo> fil = uz.fileInfoList();
foreach(const MQZipReader::FileInfo& fi, fil) {
if (fi.filePath.endsWith(".mscx")) {
dbuf = uz.fileData(fi.filePath);

View file

@ -669,6 +669,34 @@ int updateVersion()
return _updateVersion;
}
//---------------------------------------------------------
// updateVersion
/// Up to 4 digits X.X.X.X
/// Each digit can be double XX.XX.XX.XX
/// return true if v1 < v2
//---------------------------------------------------------
bool compareVersion(QString v1, QString v2)
{
auto v1l = v1.split(".");
auto v2l = v2.split(".");
int ma = qPow(100,qMax(v1l.size(), v2l.size()));
int m = ma;
int vv1 = 0;
for (int i = 0; i < v1l.size(); i++) {
vv1 += (m * v1l[i].toInt());
m /= 100;
}
m = ma;
int vv2 = 0;
for (int i = 0; i < v2l.size(); i++) {
vv2 += (m * v2l[i].toInt());
m /= 100;
}
return vv1 < vv2;
}
//---------------------------------------------------------
// diatonicUpDown
// used to find the second note of a trill, mordent etc.

View file

@ -65,6 +65,7 @@ extern int version();
extern int majorVersion();
extern int minorVersion();
extern int updateVersion();
extern bool compareVersion(QString v1, QString v2);
extern Note* nextChordNote(Note* note);
extern Note* prevChordNote(Note* note);

View file

@ -351,6 +351,7 @@ add_executable ( ${ExecutableName}
abstractdialog.cpp abstractdialog.h
toolbuttonmenu.cpp
preferenceslistwidget.cpp preferenceslistwidget.h
extension.cpp extension.h
${COCOABRIDGE}
${OMR_FILES}

View file

@ -36,7 +36,6 @@ bool DownloadUtils::saveFile()
void DownloadUtils::downloadFinished(QNetworkReply *data)
{
sdata = data->readAll();
qDebug() << "size" << sdata.size();
emit done();
}
@ -45,16 +44,31 @@ QByteArray DownloadUtils::returnData()
return sdata;
}
void DownloadUtils::download()
void DownloadUtils::download(bool showProgress)
{
QUrl url = QUrl::fromEncoded(_target.toLocal8Bit());
QNetworkRequest request(url);
QEventLoop loop;
QNetworkReply* reply = manager.get(request);
QObject::connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64,qint64)));
QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
if (showProgress) {
progressDialog = new QProgressDialog(static_cast<QWidget*>(parent()));
progressDialog->setWindowFlags(Qt::WindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint));
progressDialog->setWindowModality(Qt::ApplicationModal);
progressDialog->setCancelButtonText(tr("Cancel"));
progressDialog->setLabelText(tr("Downloading..."));
progressDialog->setAutoClose(true);
progressDialog->setAutoReset(true);
QObject::connect(progressDialog, SIGNAL(canceled()), &loop, SLOT(quit()));
progressDialog->show();
}
loop.exec();
QObject::disconnect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64,qint64)));
QObject::disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
QObject::disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
@ -62,7 +76,9 @@ void DownloadUtils::download()
void DownloadUtils::downloadProgress(qint64 received, qint64 total)
{
qDebug() << (double(received)/total)*100 << "%";
double curVal = (double(received)/total)*100;
if (progressDialog && progressDialog->isVisible())
progressDialog->setValue(curVal);
}
}

View file

@ -24,6 +24,8 @@ class DownloadUtils : public QObject
QString _target;
QString _localFile;
QProgressDialog* progressDialog = nullptr;
public:
explicit DownloadUtils(QWidget *parent=0);
@ -36,7 +38,7 @@ class DownloadUtils : public QObject
void done();
public slots:
void download();
void download(bool showProgress = false);
void downloadFinished(QNetworkReply* data);
void downloadProgress(qint64 received, qint64 total);
};

71
mscore/extension.cpp Normal file
View file

@ -0,0 +1,71 @@
//=============================================================================
// MusE Score
// Linux Music Score Editor
// $Id:$
//
// Copyright (C) 2018 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// 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, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "extension.h"
#include "preferences.h"
#include "libmscore/utils.h"
namespace Ms {
//---------------------------------------------------------
// getDirectoriesByType
//---------------------------------------------------------
QStringList Extension::getDirectoriesByType(const char* type)
{
QStringList result;
QDir d(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS));
for (auto dd : d.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot| QDir::Readable | QDir::NoSymLinks)) {
QDir extensionsDir(dd.absoluteFilePath());
auto extDir = extensionsDir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot| QDir::Readable | QDir::NoSymLinks, QDir::Name);
// take the most recent version only
if (!extDir.isEmpty()) {
QString typeDir = QString("%1/%2").arg(extDir.last().absoluteFilePath()).arg(type);
if (QFileInfo(typeDir).exists())
result.append(typeDir);
}
}
return result;
}
//---------------------------------------------------------
// isInstalled
//---------------------------------------------------------
bool Extension::isInstalled(QString extensionId)
{
QDir extensionDir(QString("%1/%2").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId));
return extensionDir.exists();
}
//---------------------------------------------------------
// getLatestVersion
//---------------------------------------------------------
QString Extension::getLatestVersion(QString extensionId)
{
QString result = "0.0";
QDir extensionDir(QString("%1/%2").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId));
auto extDir = extensionDir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot| QDir::Readable | QDir::NoSymLinks, QDir::Name);
if (!extDir.isEmpty())
result = extDir.last().fileName();
return result;
}
}

47
mscore/extension.h Normal file
View file

@ -0,0 +1,47 @@
//=============================================================================
// MusE Score
// Linux Music Score Editor
// $Id:$
//
// Copyright (C) 2018 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// 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, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef __EXTENSION_H__
#define __EXTENSION_H__
namespace Ms {
//---------------------------------------------------------
// Extension
//---------------------------------------------------------
class Extension {
public:
Extension() {}
static constexpr const char* workspacesDir = "workspaces";
static constexpr const char* sfzsDir = "sfzs";
static constexpr const char* soundfontsDir = "soundfonts";
static constexpr const char* templatesDir = "templates";
static constexpr const char* instrumentsDir = "instruments";
static QStringList getDirectoriesByType(const char* type);
static bool isInstalled(QString extensionId);
static QString getLatestVersion(QString extensionId);
};
} // namespace Ms
#endif

View file

@ -74,6 +74,7 @@
#include "synthesizer/msynthesizer.h"
#include "svggenerator.h"
#include "scorePreview.h"
#include "extension.h"
#ifdef OMR
#include "omr/omr.h"
@ -90,6 +91,7 @@
namespace Ms {
extern void importSoundfont(QString name);
extern bool savePositions(Score*, const QString& name, bool segments);
extern MasterSynthesizer* synti;
@ -2120,6 +2122,15 @@ void importSoundfont(QString name)
}
}
//---------------------------------------------------------
// importExtension
//---------------------------------------------------------
void importExtension(QString name)
{
mscore->importExtension(name);
}
//---------------------------------------------------------
// readScore
/// Import file \a name
@ -2143,6 +2154,10 @@ Score::FileError readScore(MasterScore* score, QString name, bool ignoreVersionE
importSoundfont(name);
return Score::FileError::FILE_IGNORE_ERROR;
}
else if (suffix == "muxt") {
importExtension(name);
return Score::FileError::FILE_IGNORE_ERROR;
}
else {
// typedef Score::FileError (*ImportFunction)(MasterScore*, const QString&);
struct ImportDef {

View file

@ -52,7 +52,8 @@ InstrumentsDialog::InstrumentsDialog(QWidget* parent)
QAction* a = getAction("instruments");
connect(a, SIGNAL(triggered()), SLOT(reject()));
addAction(a);
saveButton->setVisible(false);
loadButton->setVisible(false);
readSettings();
}
@ -172,6 +173,24 @@ QTreeWidget* InstrumentsDialog::partiturList()
}
//---------------------------------------------------------
// buildInstrumentsList
//---------------------------------------------------------
void InstrumentsDialog::buildInstrumentsList()
{
instrumentsWidget->buildTemplateList();
}
//---------------------------------------------------------
// updateInstrumentDialog
//---------------------------------------------------------
void MuseScore::updateInstrumentDialog()
{
if (instrList)
instrList->buildInstrumentsList();
}
//---------------------------------------------------------
// editInstrList
//---------------------------------------------------------

View file

@ -38,6 +38,7 @@ class InstrumentsDialog : public QDialog, public Ui::InstrumentsDialog {
void writeSettings();
void genPartList(Score*);
QTreeWidget* partiturList();
void buildInstrumentsList();
};
} // namespace Ms

View file

@ -413,7 +413,7 @@ void populateGenreCombo(QComboBox* combo)
void populateInstrumentList(QTreeWidget* instrumentList)
{
instrumentList->clear();
// TODO: memory leak
// TODO: memory leak?
foreach(InstrumentGroup* g, instrumentGroups) {
InstrumentTemplateListItem* group = new InstrumentTemplateListItem(g->name, instrumentList);
group->setFlags(Qt::ItemIsEnabled);

View file

@ -94,6 +94,7 @@
#include "libmscore/lasso.h"
#include "libmscore/excerpt.h"
#include "libmscore/synthesizerstate.h"
#include "libmscore/utils.h"
#include "driver.h"
@ -111,6 +112,8 @@
#include "startcenter.h"
#include "help.h"
#include "awl/aslider.h"
#include "extension.h"
#include "thirdparty/qzip/qzipreader_p.h"
#ifdef USE_LAME
#include "exportmp3.h"
@ -163,6 +166,7 @@ static QString jsonFileName;
static QString audioDriver;
static QString pluginName;
static QString styleFile;
static QString extensionName;
static bool scoresOnCommandline { false };
static QList<QTranslator*> translatorList;
@ -465,6 +469,7 @@ void updateExternalValuesFromPreferences() {
dir.mkpath(preferences.getString(PREF_APP_PATHS_MYSTYLES));
dir.mkpath(preferences.getString(PREF_APP_PATHS_MYIMAGES));
dir.mkpath(preferences.getString(PREF_APP_PATHS_MYTEMPLATES));
dir.mkpath(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS));
dir.mkpath(preferences.getString(PREF_APP_PATHS_MYPLUGINS));
foreach (QString path, preferences.getString(PREF_APP_PATHS_MYSOUNDFONTS).split(";"))
dir.mkpath(path);
@ -551,6 +556,8 @@ void MuseScore::preferencesChanged()
delete newWizard;
newWizard = 0;
reloadInstrumentTemplates();
updateInstrumentDialog();
}
//---------------------------------------------------------
@ -609,6 +616,291 @@ void MuseScore::populateNoteInputMenu()
}
}
//---------------------------------------------------------
// onLongOperationFinished
//---------------------------------------------------------
void MuseScore::onLongOperationFinished()
{
infoMsgBox->accept();
}
//---------------------------------------------------------
// importExtension
//---------------------------------------------------------
bool MuseScore::importExtension(QString path)
{
MQZipReader zipFile(path);
// compute total unzipped size
qint64 totalZipSize = 0;
for (auto fi : zipFile.fileInfoList())
totalZipSize += fi.size;
// check if extension path is writable and has enough space
QStorageInfo storage = QStorageInfo(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS));
if (storage.isReadOnly()) {
if (!MScore::noGui)
QMessageBox::critical(mscore, QWidget::tr("Import Extension File"), QWidget::tr("Cannot import extension on read-only storage: %1").arg(storage.displayName()));
return false;
}
if (totalZipSize >= storage.bytesAvailable()) {
if (!MScore::noGui)
QMessageBox::critical(mscore, QWidget::tr("Import Extension File"), QWidget::tr("Cannot import extension: storage %1 is full").arg(storage.displayName()));
return false;
}
// Check structure of the extension
bool hasMetadata = false;
bool hasAlienDirectory = false;
bool hasAlienFiles = false;
QSet<QString> acceptableFolders = { Extension::sfzsDir, Extension::soundfontsDir, Extension::templatesDir, Extension::instrumentsDir, Extension::workspacesDir };
for (auto fi : zipFile.fileInfoList()) {
if (fi.filePath == "metadata.json")
hasMetadata = true;
else {
// get folders
auto path = QDir::cleanPath(fi.filePath);
QStringList folders(path);
while ((path = QFileInfo(path).path()).length() < folders.last().length())
folders << path;
if (folders.size() < 2) {
hasAlienFiles = true; // in root dir
break;
}
QString rootDir = folders.at(folders.size() - 2);
if (!acceptableFolders.contains(rootDir)) {
hasAlienDirectory = true; // in root dir
break;
}
}
}
if (!hasMetadata) {
if (!MScore::noGui)
QMessageBox::critical(mscore, QWidget::tr("Import Extension File"), QWidget::tr("Corrupted extension: no metadata.json"));
return false;
}
if (hasAlienDirectory) {
if (!MScore::noGui)
QMessageBox::critical(mscore, QWidget::tr("Import Extension File"), QWidget::tr("Corrupted extension: unsupported directories in root directory"));
return false;
}
if (hasAlienFiles) {
if (!MScore::noGui)
QMessageBox::critical(mscore, QWidget::tr("Import Extension File"), QWidget::tr("Corrupted extension: unsupported files in root directory"));
return false;
}
zipFile.close();
MQZipReader zipFile2(path);
// get extension id from metadata.json
QByteArray mdba = zipFile2.fileData("metadata.json");
zipFile2.close();
QJsonDocument loadDoc = QJsonDocument::fromJson(mdba);
QJsonObject mdObject = loadDoc.object();
QString extensionId = mdObject["id"].toString();
QString version = mdObject["version"].toString();
if (extensionId.isEmpty() || version.isEmpty()) {
if (!MScore::noGui)
QMessageBox::critical(mscore, QWidget::tr("Import Extension File"), QWidget::tr("Corrupted extension: corrupted metadata.json"));
return false;
}
// Check if extension is already installed, ask for uninstall
QDir dir(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS));
auto dirList = dir.entryList(QStringList(extensionId), QDir::Dirs | QDir::NoDotAndDotDot);
bool newerVersion = false;
if (dirList.contains(extensionId)) {
QString extDirName = QString("%1/%2").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId);
QDir extDir(extDirName);
auto versionDirList = extDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
if (versionDirList.size() > 0) {
// potentially other versions
// is there a more recent version?
for (auto versionDir : versionDirList) {
if (compareVersion(version, versionDir)) {
qDebug() << "There is a newer version. We don't install";
if (!MScore::noGui)
QMessageBox::critical(mscore, QWidget::tr("Import Extension File"), QWidget::tr("A newer version is already installed"));
newerVersion = true;
return false;
}
}
}
if (!newerVersion) {
qDebug() << "found already install extension without newer version: deleting it";
QDir d(QString("%1/%2").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId));
if (!d.removeRecursively()) {
if (!MScore::noGui)
QMessageBox::critical(mscore, QWidget::tr("Import Extension File"), QWidget::tr("Error while deleting previous version of the extension: %1").arg(extensionId));
return false;
}
}
}
//setup the message box
infoMsgBox = new QMessageBox();
infoMsgBox->setWindowModality(Qt::ApplicationModal);
infoMsgBox->setWindowFlags(Qt::WindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint));
infoMsgBox->setTextFormat(Qt::RichText);
infoMsgBox->setMinimumSize(300, 100);
infoMsgBox->setMaximumSize(300, 100);
infoMsgBox->setStandardButtons(0);
infoMsgBox->setText(QString("<p align='center'>") + tr("Please wait, unpacking extension...") + QString("</p>"));
//setup async run of long operations
QFutureWatcher<bool> futureWatcherUnzip;
connect(&futureWatcherUnzip, SIGNAL(finished()), this, SLOT(onLongOperationFinished()));
MQZipReader* zipFile3 = new MQZipReader(path);
// Unzip the extension asynchronously
QFuture<bool> futureUnzip = QtConcurrent::run(zipFile3, &MQZipReader::extractAll, QString("%1/%2/%3").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId).arg(version));
futureWatcherUnzip.setFuture(futureUnzip);
if (!MScore::noGui)
infoMsgBox->exec();
bool unzipResult = futureUnzip.result();
zipFile3->close();
delete zipFile3;
zipFile3 = nullptr;
if (!unzipResult) {
if (!MScore::noGui)
QMessageBox::critical(mscore, QWidget::tr("Import Extension File"), QWidget::tr("Unable to extract files from the extension"));
return false;
}
delete newWizard;
newWizard = 0;
mscore->reloadInstrumentTemplates();
mscore->updateInstrumentDialog();
auto loadSoundFontAsync = [&]() {
// After install: add sfz to zerberus
QDir sfzDir(QString("%1/%2/%3/%4").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId).arg(version).arg(Extension::sfzsDir));
if (sfzDir.exists()) {
// get all sfz files
QDirIterator it(sfzDir.absolutePath(), QStringList("*.sfz"), QDir::Files, QDirIterator::Subdirectories);
Synthesizer* s = synti->synthesizer("Zerberus");
QStringList sfzs;
while (it.hasNext()) {
it.next();
sfzs.append(it.fileName());
}
sfzs.sort();
for (int sfzNum = 0; sfzNum < sfzs.size(); ++sfzNum)
s->addSoundFont(sfzs[sfzNum]);
if (!sfzs.isEmpty())
synti->storeState();
}
// After install: add soundfont to fluid
QDir sfDir(QString("%1/%2/%3/%4").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId).arg(version).arg(Extension::soundfontsDir));
if (sfDir.exists()) {
// get all soundfont files
QStringList filters("*.sf2");
filters.append("*.sf3");
QDirIterator it(sfzDir.absolutePath(), filters, QDir::Files, QDirIterator::Subdirectories);
Synthesizer* s = synti->synthesizer("Fluid");
QStringList sfs;
while (it.hasNext()) {
it.next();
sfs.append(it.fileName());
}
sfs.sort();
for (auto sf : sfs)
s->addSoundFont(sf);
if (!sfs.isEmpty())
synti->storeState();
}
};
if (!enableExperimental) {
//load soundfonts async
QFuture<void> futureLoadSFs = QtConcurrent::run(loadSoundFontAsync);
QFutureWatcher<void> futureWatcherLoadSFs;
futureWatcherLoadSFs.setFuture(futureLoadSFs);
connect(&futureWatcherLoadSFs, SIGNAL(finished()), this, SLOT(onLongOperationFinished()));
infoMsgBox->setText(QString("<p align='center'>") + tr("Please wait, loading soundfonts...") + QString("</p>"));
if (!MScore::noGui)
infoMsgBox->exec();
else
futureLoadSFs.waitForFinished();
}
// after install: refresh workspaces if needed
QDir workspacesDir(QString("%1/%2/%3/%4").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId).arg(version).arg(Extension::workspacesDir));
if (workspacesDir.exists() && !MScore::noGui) {
auto wsList = workspacesDir.entryInfoList(QStringList("*.workspace"), QDir::Files);
if (!wsList.isEmpty()) {
Workspace::refreshWorkspaces();
paletteBox->updateWorkspaces();
paletteBox->selectWorkspace(wsList.last().absoluteFilePath());
}
}
return true;
}
//---------------------------------------------------------
// uninstallExtension
//---------------------------------------------------------
bool MuseScore::uninstallExtension(QString extensionId)
{
QString version = Extension::getLatestVersion(extensionId);
// Before install: remove sfz from zerberus
QDir sfzDir(QString("%1/%2/%3/%4").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId).arg(version).arg(Extension::sfzsDir));
if (sfzDir.exists()) {
// get all sfz files
QDirIterator it(sfzDir.absolutePath(), QStringList("*.sfz"), QDir::Files, QDirIterator::Subdirectories);
Synthesizer* s = synti->synthesizer("Zerberus");
bool found = it.hasNext();
while (it.hasNext()) {
it.next();
s->removeSoundFont(it.fileInfo().absoluteFilePath());
}
if (found)
synti->storeState();
}
// Before install: remove soundfont from fluid
QDir sfDir(QString("%1/%2/%3/%4").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId).arg(version).arg(Extension::soundfontsDir));
if (sfDir.exists()) {
// get all soundfont files
QStringList filters("*.sf2");
filters.append("*.sf3");
QDirIterator it(sfzDir.absolutePath(), filters, QDir::Files, QDirIterator::Subdirectories);
Synthesizer* s = synti->synthesizer("Fluid");
bool found = it.hasNext();
while (it.hasNext()) {
it.next();
s->removeSoundFont(it.fileInfo().absoluteFilePath());
}
if (found)
synti->storeState();
}
bool refreshWorkspaces = false;
QDir workspacesDir(QString("%1/%2/%3/%4").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId).arg(version).arg(Extension::workspacesDir));
if (workspacesDir.exists()) {
auto wsList = workspacesDir.entryInfoList(QStringList("*.workspace"), QDir::Files);
if (!wsList.isEmpty())
refreshWorkspaces = true;
}
// delete directories
QDir extensionDir(QString("%1/%2").arg(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS)).arg(extensionId));
extensionDir.removeRecursively();
// update UI
delete newWizard;
newWizard = 0;
mscore->reloadInstrumentTemplates();
mscore->updateInstrumentDialog();
if (refreshWorkspaces) {
Workspace::refreshWorkspaces();
paletteBox->updateWorkspaces();
paletteBox->selectWorkspace("Basic");
}
return true;
}
//---------------------------------------------------------
// MuseScore
//---------------------------------------------------------
@ -646,7 +938,8 @@ MuseScore::MuseScore()
setWindowTitle(QString(MUSESCORE_NAME_VERSION));
setIconSize(QSize(preferences.getInt(PREF_UI_THEME_ICONWIDTH) * guiScaling, preferences.getInt(PREF_UI_THEME_ICONHEIGHT) * guiScaling));
ucheck = new UpdateChecker();
ucheck = new UpdateChecker(this);
packUChecker = new ExtensionsUpdateChecker(this);
setAcceptDrops(true);
setFocusPolicy(Qt::NoFocus);
@ -1395,14 +1688,9 @@ MuseScore::MuseScore()
setCentralWidget(envelope);
// load cascading instrument templates
loadInstrumentTemplates(preferences.getString(PREF_APP_PATHS_INSTRUMENTLIST1));
QString instrList2 = preferences.getString(PREF_APP_PATHS_INSTRUMENTLIST2);
if (!instrList2.isEmpty())
loadInstrumentTemplates(instrList2);
if (!MScore::noGui)
preferencesChanged();
if (seq) {
connect(seq, SIGNAL(started()), SLOT(seqStarted()));
connect(seq, SIGNAL(stopped()), SLOT(seqStopped()));
@ -1818,6 +2106,31 @@ void MuseScore::openRecentMenu()
}
}
//---------------------------------------------------------
// reloadInstrumentTemplates
//---------------------------------------------------------
void MuseScore::reloadInstrumentTemplates()
{
clearInstrumentTemplates();
// load cascading instrument templates
loadInstrumentTemplates(preferences.getString(PREF_APP_PATHS_INSTRUMENTLIST1));
QString list2 = preferences.getString(PREF_APP_PATHS_INSTRUMENTLIST2);
if (!list2.isEmpty())
loadInstrumentTemplates(list2);
// load instrument templates from extension
QStringList extensionDir = Extension::getDirectoriesByType(Extension::instrumentsDir);
QStringList filter("*.xml");
for (QString s : extensionDir) {
QDir extDir(s);
extDir.setNameFilters(filter);
auto instFiles = extDir.entryInfoList(QDir::Files | QDir::NoSymLinks | QDir::Readable);
for (auto instFile : instFiles)
loadInstrumentTemplates(instFile.absoluteFilePath());
}
}
//---------------------------------------------------------
// setCurrentView
//---------------------------------------------------------
@ -2898,14 +3211,23 @@ static bool processNonGui(const QStringList& argv)
if (!converterMode)
return res;
}
bool rv = true;
if (converterMode) {
if (processJob)
return doProcessJob(jsonFileName);
else
return convert(argv[0], outFileName);
}
return rv;
if (!extensionName.isEmpty()) {
QFileInfo fi(extensionName);
QString suffix = fi.suffix().toLower();
if (suffix == "muxt")
return mscore->importExtension(extensionName);
else {
fprintf(stderr, "cannot install extension: <%s>\n", qPrintable(extensionName));
return false;
}
}
return true;
}
//---------------------------------------------------------
@ -3069,7 +3391,16 @@ bool MuseScore::hasToCheckForUpdate()
if (ucheck)
return ucheck->hasToCheck();
else
return false;
return false;
}
//---------------------------------------------------------
// hasToCheckForExtensionsUpdate
//---------------------------------------------------------
bool MuseScore::hasToCheckForExtensionsUpdate()
{
return packUChecker ? packUChecker->hasToCheck() : false;
}
//---------------------------------------------------------
@ -3082,6 +3413,16 @@ void MuseScore::checkForUpdate()
ucheck->check(version(), sender() != 0);
}
//---------------------------------------------------------
// checkForExtensionsUpdate
//---------------------------------------------------------
void MuseScore::checkForExtensionsUpdate()
{
if (packUChecker)
packUChecker->check();
}
//---------------------------------------------------------
// readLanguages
//---------------------------------------------------------
@ -5501,6 +5842,8 @@ void MuseScore::showSearchDialog()
_searchDialog->show();
}
#ifndef SCRIPT_INTERFACE
void MuseScore::pluginTriggered(int) {}
void MuseScore::loadPlugins() {}
@ -6037,6 +6380,7 @@ int main(int argc, char* av[])
parser.addOption(QCommandLineOption( "no-fallback-font", "Don't use Bravura as fallback musical font"));
parser.addOption(QCommandLineOption({"f", "force"}, "Used with '-o <file>', ignore warnings reg. score being corrupted or from wrong version"));
parser.addOption(QCommandLineOption({"b", "bitrate"}, "Used with '-o <file>.mp3', sets bitrate, in kbps", "bitrate"));
parser.addOption(QCommandLineOption({"E", "install-extension"}, "Install an extension, load soundfont as default unless if -e is passed too", "extension file"));
parser.addPositionalArgument("scorefiles", "The files to open", "[scorefile...]");
@ -6083,6 +6427,10 @@ int main(int argc, char* av[])
if (pluginName.isEmpty())
parser.showHelp(EXIT_FAILURE);
}
if (parser.isSet("E")) {
MScore::noGui = true;
extensionName = parser.value("E");
}
MScore::saveTemplateMode = parser.isSet("template-mode");
if (parser.isSet("r")) {
QString temp = parser.value("r");
@ -6278,7 +6626,6 @@ int main(int argc, char* av[])
MScore::readDefaultStyle(preferences.getString(PREF_SCORE_STYLE_DEFAULTSTYLEFILE));
QSplashScreen* sc = 0;
QTimer* stimer = 0;
if (!MScore::noGui && preferences.getBool(PREF_UI_APP_STARTUP_SHOWSPLASHSCREEN)) {
QPixmap pm(":/data/splash.png");
sc = new QSplashScreen(pm);
@ -6286,10 +6633,6 @@ int main(int argc, char* av[])
#ifdef Q_OS_MAC // to have session dialog on top of splashscreen on mac
sc->setWindowFlags(Qt::FramelessWindowHint);
#endif
// show splash screen for 5 sec
stimer = new QTimer(0);
qApp->connect(stimer, SIGNAL(timeout()), sc, SLOT(close()));
stimer->start(5000);
sc->show();
qApp->processEvents();
}
@ -6459,6 +6802,9 @@ int main(int argc, char* av[])
mscore->checkForUpdate();
#endif
if (mscore->hasToCheckForExtensionsUpdate())
mscore->checkForExtensionsUpdate();
if (!scoresOnCommandline && preferences.getBool(PREF_UI_APP_STARTUP_SHOWSTARTCENTER) && (!restoredSession || mscore->scores().size() == 0)) {
#ifdef Q_OS_MAC
// ugly, but on mac we get an event when a file is open.
@ -6481,6 +6827,7 @@ int main(int argc, char* av[])
#endif
}
sc->close();
mscore->showPlayPanel(preferences.getBool(PREF_UI_APP_STARTUP_SHOWPLAYPANEL));
QSettings settings;
if (settings.value("synthControlVisible", false).toBool())

View file

@ -222,7 +222,8 @@ class MuseScore : public QMainWindow, public MuseScoreCore {
QSettings settings;
ScoreView* cv { 0 };
ScoreState _sstate;
UpdateChecker* ucheck = nullptr;
UpdateChecker* ucheck;
ExtensionsUpdateChecker* packUChecker = nullptr;
static const std::list<const char*> _allNoteInputMenuEntries;
static const std::list<const char*> _basicNoteInputMenuEntries;
@ -409,6 +410,7 @@ class MuseScore : public QMainWindow, public MuseScoreCore {
qreal _physicalDotsPerInch;
QMessageBox* infoMsgBox;
//---------------------
virtual void closeEvent(QCloseEvent*);
@ -511,6 +513,7 @@ class MuseScore : public QMainWindow, public MuseScoreCore {
void switchLayoutMode(int);
void showMidiImportPanel();
void changeWorkspace(QAction*);
void onLongOperationFinished();
virtual QMenu* createPopupMenu() override;
@ -526,6 +529,7 @@ class MuseScore : public QMainWindow, public MuseScoreCore {
void setPlayState() { changeState(STATE_PLAY); }
void setNoteEntryState() { changeState(STATE_NOTE_ENTRY); }
void checkForUpdate();
void checkForExtensionsUpdate();
void midiNoteReceived(int channel, int pitch, int velo);
void midiNoteReceived(int pitch, bool ctrl, int velo);
void instrumentChanged();
@ -608,6 +612,7 @@ class MuseScore : public QMainWindow, public MuseScoreCore {
QNetworkAccessManager* networkManager();
virtual Score* openScore(const QString& fn);
bool hasToCheckForUpdate();
bool hasToCheckForExtensionsUpdate();
static bool unstable();
bool eventFilter(QObject *, QEvent *);
void setMidiRecordId(int id) { _midiRecordId = id; }
@ -752,6 +757,8 @@ class MuseScore : public QMainWindow, public MuseScoreCore {
QHelpEngine* helpEngine() const { return _helpEngine; }
virtual void updateInspector() override;
void updateInstrumentDialog();
void reloadInstrumentTemplates();
void showSynthControl(bool);
void showMixer(bool);
@ -777,6 +784,8 @@ class MuseScore : public QMainWindow, public MuseScoreCore {
static void restoreGeometry(QWidget*const qw);
void updateWindowTitle(Score* score);
bool importExtension(QString path);
bool uninstallExtension(QString extensionId);
};
extern MuseScore* mscore;

View file

@ -1,3 +1,4 @@
//=============================================================================
// MusE Score
// Linux Music Score Editor
@ -24,6 +25,7 @@
#include "palette.h"
#include "instrdialog.h"
#include "scoreBrowser.h"
#include "extension.h"
#include "libmscore/instrtemplate.h"
#include "libmscore/score.h"
@ -274,18 +276,9 @@ NewWizardPage4::NewWizardPage4(QWidget* parent)
templateFileBrowser = new ScoreBrowser;
templateFileBrowser->setStripNumbers(true);
QDir dir(mscoreGlobalShare + "/templates");
QFileInfoList fil = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Readable | QDir::Dirs | QDir::Files, QDir::Name);
if(fil.isEmpty()){
fil.append(QFileInfo(QFile(":data/Empty_Score.mscz")));
}
QDir myTemplatesDir(preferences.getString(PREF_APP_PATHS_MYTEMPLATES));
fil.append(myTemplatesDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Readable | QDir::Dirs | QDir::Files, QDir::Name));
templateFileBrowser->setShowCustomCategory(true);
templateFileBrowser->setScores(fil);
templateFileBrowser->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
buildTemplatesList();
QVBoxLayout* layout = new QVBoxLayout;
QHBoxLayout* searchLayout = new QHBoxLayout;
@ -317,6 +310,31 @@ void NewWizardPage4::initializePage()
path.clear();
}
//---------------------------------------------------------
// buildTemplatesList
//---------------------------------------------------------
void NewWizardPage4::buildTemplatesList()
{
QDir dir(mscoreGlobalShare + "/templates");
QFileInfoList fil = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Readable | QDir::Dirs | QDir::Files, QDir::Name);
if(fil.isEmpty()){
fil.append(QFileInfo(QFile(":data/Empty_Score.mscz")));
}
QDir myTemplatesDir(preferences.getString(PREF_APP_PATHS_MYTEMPLATES));
fil.append(myTemplatesDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Readable | QDir::Dirs | QDir::Files, QDir::Name));
// append templates directories from extensions
QStringList extensionsDir = Extension::getDirectoriesByType(Extension::templatesDir);
for (QString extDir : extensionsDir) {
QDir extTemplateDir(extDir);
fil.append(extTemplateDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Readable | QDir::Dirs | QDir::Files, QDir::Name));
}
templateFileBrowser->setScores(fil);
}
//---------------------------------------------------------
// isComplete
//---------------------------------------------------------

View file

@ -145,6 +145,7 @@ class NewWizardPage4 : public QWizardPage {
virtual bool isComplete() const override;
QString templatePath() const;
virtual void initializePage();
void buildTemplatesList();
};
//---------------------------------------------------------
@ -207,6 +208,7 @@ class NewWizard : public QWizard {
double tempo() const { return p5->tempo(); }
bool createTempo() const { return p5->createTempo(); }
bool emptyScore() const;
void updateValues() const;
};

View file

@ -170,6 +170,16 @@ void PaletteBox::updateWorkspaces()
workspaceList->setCurrentIndex(curIdx);
}
//---------------------------------------------------------
// selectWorkspace
//---------------------------------------------------------
void PaletteBox::selectWorkspace(QString path)
{
int idx = workspaceList->findData(path);
workspaceList->setCurrentIndex(idx);
}
//---------------------------------------------------------
// clear
//---------------------------------------------------------

View file

@ -68,6 +68,7 @@ class PaletteBox : public QDockWidget {
bool eventFilter(QObject* obj, QEvent *event);
void setKeyboardNavigation(bool val) { keyboardNavigation = val; }
bool getKeyboardNavigation() { return keyboardNavigation; }
void selectWorkspace(QString path);
};
//---------------------------------------------------------

View file

@ -32,7 +32,6 @@ void Preferences::init(bool storeInMemoryOnly)
if (!storeInMemoryOnly) {
if (_settings)
delete _settings;
_settings = new QSettings();
}
@ -40,8 +39,10 @@ void Preferences::init(bool storeInMemoryOnly)
#if defined(Q_OS_MAC) || (defined(Q_OS_WIN) && !defined(FOR_WINSTORE))
bool checkUpdateStartup = true;
bool checkExtensionsUpdateStartup = true;
#else
bool checkUpdateStartup = false;
bool checkExtensionsUpdateStartup = false;
#endif
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
@ -51,7 +52,6 @@ void Preferences::init(bool storeInMemoryOnly)
#else
bool nativeDialogs = false; // don't use system native file dialogs
#endif
bool defaultUsePortAudio = false;
bool defaultUsePulseAudio = false;
bool defaultUseJackAudio = false;
@ -84,6 +84,7 @@ void Preferences::init(bool storeInMemoryOnly)
{PREF_APP_PATHS_MYSHORTCUTS, new StringPreference(QFileInfo(QString("%1/%2").arg(wd).arg(QCoreApplication::translate("shortcuts_directory", "Shortcuts"))).absoluteFilePath(), false)},
{PREF_APP_PATHS_MYSTYLES, new StringPreference(QFileInfo(QString("%1/%2").arg(wd).arg(QCoreApplication::translate("styles_directory", "Styles"))).absoluteFilePath(), false)},
{PREF_APP_PATHS_MYTEMPLATES, new StringPreference(QFileInfo(QString("%1/%2").arg(wd).arg(QCoreApplication::translate("templates_directory", "Templates"))).absoluteFilePath(), false)},
{PREF_APP_PATHS_MYEXTENSIONS, new StringPreference(QFileInfo(QString("%1/%2").arg(wd).arg(QCoreApplication::translate("extensions_directory", "Extensions"))).absoluteFilePath(), false)},
{PREF_APP_PLAYBACK_FOLLOWSONG, new BoolPreference(true)},
{PREF_APP_PLAYBACK_PANPLAYBACK, new BoolPreference(true)},
{PREF_APP_PLAYBACK_PLAYREPEATS, new BoolPreference(true)},
@ -150,6 +151,7 @@ void Preferences::init(bool storeInMemoryOnly)
{PREF_UI_CANVAS_SCROLL_LIMITSCROLLAREA, new BoolPreference(false, false)},
{PREF_UI_CANVAS_SCROLL_VERTICALORIENTATION, new BoolPreference(false, false)},
{PREF_UI_APP_STARTUP_CHECKUPDATE, new BoolPreference(checkUpdateStartup, false)},
{PREF_UI_APP_STARTUP_CHECK_EXTENSIONS_UPDATE, new BoolPreference(checkExtensionsUpdateStartup, false)},
{PREF_UI_APP_STARTUP_SHOWNAVIGATOR, new BoolPreference(false, false)},
{PREF_UI_APP_STARTUP_SHOWPLAYPANEL, new BoolPreference(false, false)},
{PREF_UI_APP_STARTUP_SHOWSPLASHSCREEN, new BoolPreference(true, false)},
@ -404,7 +406,6 @@ void Preferences::clearMidiRemote(int recordId)
remove(baseKey);
}
Preference::Preference(QVariant defaultValue, QMetaType::Type type, bool showInAdvancedList)
: _defaultValue(defaultValue),
_showInAdvancedList(showInAdvancedList),

View file

@ -91,6 +91,7 @@ enum class MusicxmlExportBreaks : char {
#define PREF_APP_PATHS_MYSOUNDFONTS "application/paths/mySoundfonts"
#define PREF_APP_PATHS_MYSTYLES "application/paths/myStyles"
#define PREF_APP_PATHS_MYTEMPLATES "application/paths/myTemplates"
#define PREF_APP_PATHS_MYEXTENSIONS "application/paths/myExtensions"
#define PREF_APP_PLAYBACK_FOLLOWSONG "application/playback/followSong"
#define PREF_APP_PLAYBACK_PANPLAYBACK "application/playback/panPlayback"
#define PREF_APP_PLAYBACK_PLAYREPEATS "application/playback/playRepeats"
@ -158,6 +159,7 @@ enum class MusicxmlExportBreaks : char {
#define PREF_UI_CANVAS_SCROLL_VERTICALORIENTATION "ui/canvas/scroll/verticalOrientation"
#define PREF_UI_CANVAS_SCROLL_LIMITSCROLLAREA "ui/canvas/scroll/limitScrollArea"
#define PREF_UI_APP_STARTUP_CHECKUPDATE "ui/application/startup/checkUpdate"
#define PREF_UI_APP_STARTUP_CHECK_EXTENSIONS_UPDATE "ui/application/startup/checkExtensionsUpdate"
#define PREF_UI_APP_STARTUP_SHOWNAVIGATOR "ui/application/startup/showNavigator"
#define PREF_UI_APP_STARTUP_SHOWPLAYPANEL "ui/application/startup/showPlayPanel"
#define PREF_UI_APP_STARTUP_SHOWSPLASHSCREEN "ui/application/startup/showSplashScreen"

View file

@ -153,6 +153,8 @@ PreferenceDialog::PreferenceDialog(QWidget* parent)
connect(myPluginsButton, SIGNAL(clicked()), SLOT(selectPluginsDirectory()));
connect(myImagesButton, SIGNAL(clicked()), SLOT(selectImagesDirectory()));
connect(mySoundfontsButton, SIGNAL(clicked()), SLOT(changeSoundfontPaths()));
connect(myExtensionsButton, SIGNAL(clicked()), SLOT(selectExtensionsDirectory()));
connect(updateTranslation, SIGNAL(clicked()), SLOT(updateTranslationClicked()));
@ -532,6 +534,7 @@ void PreferenceDialog::updateValues(bool useDefaultValues)
myTemplates->setText(preferences.getString(PREF_APP_PATHS_MYTEMPLATES));
myPlugins->setText(preferences.getString(PREF_APP_PATHS_MYPLUGINS));
mySoundfonts->setText(preferences.getString(PREF_APP_PATHS_MYSOUNDFONTS));
myExtensions->setText(preferences.getString(PREF_APP_PATHS_MYEXTENSIONS));
index = exportAudioSampleRate->findData(preferences.getInt(PREF_EXPORT_AUDIO_SAMPLERATE));
exportAudioSampleRate->setCurrentIndex(index);
@ -898,6 +901,7 @@ void PreferenceDialog::apply()
preferences.setPreference(PREF_APP_PATHS_MYSOUNDFONTS, mySoundfonts->text());
preferences.setPreference(PREF_APP_PATHS_MYSTYLES, myStyles->text());
preferences.setPreference(PREF_APP_PATHS_MYTEMPLATES, myTemplates->text());
preferences.setPreference(PREF_APP_PATHS_MYEXTENSIONS, myExtensions->text());
preferences.setPreference(PREF_APP_STARTUP_STARTSCORE, sessionScore->text());
preferences.setPreference(PREF_EXPORT_AUDIO_SAMPLERATE, exportAudioSampleRate->currentData().toInt());
preferences.setPreference(PREF_EXPORT_MP3_BITRATE, exportMp3BitRate->currentData().toInt());
@ -1300,6 +1304,22 @@ void PreferenceDialog::changeSoundfontPaths()
mySoundfonts->setText(pld.path());
}
//---------------------------------------------------------
// selectExtensionsDirectory
//---------------------------------------------------------
void PreferenceDialog::selectExtensionsDirectory()
{
QString s = QFileDialog::getExistingDirectory(
this,
tr("Choose Extensions Folder"),
myExtensions->text(),
QFileDialog::ShowDirsOnly | (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS) ? QFileDialog::Options() : QFileDialog::DontUseNativeDialog)
);
if (!s.isNull())
myExtensions->setText(s);
}
//---------------------------------------------------------
// updateLanguagesClicked
//---------------------------------------------------------
@ -1307,6 +1327,7 @@ void PreferenceDialog::changeSoundfontPaths()
void PreferenceDialog::updateTranslationClicked()
{
ResourceManager r(0);
r.selectLanguagesTab();
r.exec();
}

View file

@ -76,6 +76,7 @@ class PreferenceDialog : public AbstractDialog, private Ui::PrefsDialogBase {
void selectTemplatesDirectory();
void selectPluginsDirectory();
void selectImagesDirectory();
void selectExtensionsDirectory();
void printShortcutsClicked();
void filterShortcutsTextChanged(const QString &);
void filterAdvancedPreferences(const QString&);

View file

@ -428,6 +428,19 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_extensions">
<property name="text">
<string>Extensions:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>5</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_86">
<property name="text">
@ -588,6 +601,16 @@
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="myExtensions">
<property name="accessibleName">
<string>Extensions folder</string>
</property>
<property name="accessibleDescription">
<string>Insert path to extensions folder</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QToolButton" name="myTemplatesButton">
<property name="focusPolicy">
@ -628,6 +651,26 @@
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QToolButton" name="myExtensionsButton">
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="accessibleName">
<string>Extensions folder</string>
</property>
<property name="accessibleDescription">
<string>Opens a folder dialog for selecting the extensions folder</string>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="icon">
<iconset resource="musescore.qrc">
<normaloff>:/data/icons/document-open.svg</normaloff>:/data/icons/document-open.svg</iconset>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QToolButton" name="mySoundfontsButton">
<property name="focusPolicy">
@ -1047,6 +1090,7 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
@ -4109,7 +4153,20 @@ Adjusting latency can help synchronize your MIDI hardware with MuseScore's inter
<string>Automatic Update Check</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="1" column="0">
<item row="4" column="0">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer_5">
@ -4126,19 +4183,6 @@ Adjusting latency can help synchronize your MIDI hardware with MuseScore's inter
</item>
</layout>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="checkUpdateStartup">
<property name="text">
@ -4146,6 +4190,13 @@ Adjusting latency can help synchronize your MIDI hardware with MuseScore's inter
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="checkExtensionsUpdateStartup">
<property name="text">
<string>Check for new version of MuseScore extensions</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -4261,6 +4312,8 @@ Adjusting latency can help synchronize your MIDI hardware with MuseScore's inter
<tabstop>mySoundfontsButton</tabstop>
<tabstop>myImages</tabstop>
<tabstop>myImagesButton</tabstop>
<tabstop>myExtensions</tabstop>
<tabstop>myExtensionsButton</tabstop>
<tabstop>language</tabstop>
<tabstop>updateTranslation</tabstop>
<tabstop>styleName</tabstop>

View file

@ -12,6 +12,9 @@
#include "resourceManager.h"
#include "musescore.h"
#include "extension.h"
#include "libmscore/utils.h"
#include "stringutils.h"
#include "ui_resourceManager.h"
#include "thirdparty/qzip/qzipreader_p.h"
@ -29,28 +32,130 @@ ResourceManager::ResourceManager(QWidget *parent) :
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
QDir dir;
dir.mkpath(dataPath + "/locale");
baseAddr = "http://extensions.musescore.org/2.3/";
displayPlugins();
displayExtensions();
displayLanguages();
languagesTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
languagesTable->verticalHeader()->hide();
tabs->removeTab(tabs->indexOf(plugins));
tabs->setCurrentIndex(tabs->indexOf(languages));
extensionsTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
extensionsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
extensionsTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
extensionsTable->verticalHeader()->hide();
extensionsTable->setColumnWidth(1, 50);
extensionsTable->setColumnWidth(1, 100);
MuseScore::restoreGeometry(this);
}
void ResourceManager::displayPlugins()
//---------------------------------------------------------
// selectLanguagesTab
//---------------------------------------------------------
void ResourceManager::selectLanguagesTab()
{
textBrowser->setText("hello");
tabs->setCurrentIndex(tabs->indexOf(languages));
}
//---------------------------------------------------------
// selectExtensionsTab
//---------------------------------------------------------
void ResourceManager::selectExtensionsTab()
{
tabs->setCurrentIndex(tabs->indexOf(extensions));
}
//---------------------------------------------------------
// displayExtensions
//---------------------------------------------------------
void ResourceManager::displayExtensions()
{
DownloadUtils js(this);
js.setTarget(baseAddr() + "extensions/details.json");
js.download();
QByteArray json = js.returnData();
// parse the json file
QJsonParseError err;
QJsonDocument result = QJsonDocument::fromJson(json, &err);
if (err.error != QJsonParseError::NoError || !result.isObject()) {
qDebug("An error occurred during parsing");
return;
}
int rowCount = result.object().keys().size();
rowCount -= 2; //version and type
extensionsTable->setRowCount(rowCount);
int row = 0;
int col = 0;
QPushButton* buttonInstall;
QPushButton* buttonUninstall;
extensionsTable->verticalHeader()->show();
QStringList extensions = result.object().keys();
for (QString key : extensions) {
if (!result.object().value(key).isObject())
continue;
QJsonObject value = result.object().value(key).toObject();
col = 0;
QString test = value.value("file_name").toString();
if (test.length() == 0)
continue;
QString filename = value.value("file_name").toString();
QString name = value.value("name").toString();
int fileSize = value.value("file_size").toInt();
QString hashValue = value.value("hash").toString();
QString version = value.value("version").toString();
extensionsTable->setItem(row, col++, new QTableWidgetItem(name));
extensionsTable->setItem(row, col++, new QTableWidgetItem(version));
extensionsTable->setItem(row, col++, new QTableWidgetItem(stringutils::convertFileSizeToHumanReadable(fileSize)));
buttonInstall = new QPushButton(tr("Install"));
buttonUninstall = new QPushButton(tr("Uninstall"));
connect(buttonInstall, SIGNAL(clicked()), this, SLOT(downloadExtension()));
connect(buttonUninstall, SIGNAL(clicked()), this, SLOT(uninstallExtension()));
buttonInstall->setProperty("path", "extensions/" + filename);
buttonInstall->setProperty("hash", hashValue);
buttonInstall->setProperty("rowId", row);
buttonUninstall->setProperty("extensionId", key);
buttonUninstall->setProperty("rowId", row);
// get the installed version of the extension if any
if (Extension::isInstalled(key)) {
buttonUninstall->setDisabled(false);
QString installedVersion = Extension::getLatestVersion(key);
if (compareVersion(installedVersion, version)) {
buttonInstall->setText(tr("Update"));
}
else {
buttonInstall->setText(tr("Updated"));
buttonInstall->setDisabled(true);
}
}
else {
buttonUninstall->setDisabled(true);
}
extensionsTable->setIndexWidget(extensionsTable->model()->index(row, col++), buttonInstall);
extensionsTable->setIndexWidget(extensionsTable->model()->index(row, col++), buttonUninstall);
row++;
}
}
//---------------------------------------------------------
// displayLanguages
//---------------------------------------------------------
void ResourceManager::displayLanguages()
{
// Download details.json
DownloadUtils *js = new DownloadUtils(this);
js->setTarget(baseAddr + "languages/details.json");
js->download();
QByteArray json = js->returnData();
DownloadUtils js(this);
js.download();
js.setTarget(baseAddr() + "languages/details.json");
QByteArray json = js.returnData();
qDebug() << json;
// parse the json file
QJsonParseError err;
@ -70,7 +175,7 @@ void ResourceManager::displayLanguages()
languagesTable->verticalHeader()->show();
// move current language to first row
QStringList languages = result.object().keys();
QStringList languages = result.object().keys();
QString lang = mscore->getLocaleISOCode();
int index = languages.indexOf(lang);
if (index < 0 && lang.size() > 2) {
@ -98,12 +203,12 @@ void ResourceManager::displayLanguages()
languagesTable->setItem(row, col++, new QTableWidgetItem(name));
languagesTable->setItem(row, col++, new QTableWidgetItem(filename));
languagesTable->setItem(row, col++, new QTableWidgetItem(tr("%1 KB").arg(fileSize)));
languagesTable->setItem(row, col++, new QTableWidgetItem(tr("%1 kB").arg(fileSize)));
updateButtons[row] = new QPushButton(tr("Update"));
temp = updateButtons[row];
buttonMap[temp] = "languages/" + filename;
buttonHashMap[temp] = hashValue;
languageButtonMap[temp] = "languages/" + filename;
languageButtonHashMap[temp] = hashValue;
languagesTable->setIndexWidget(languagesTable->model()->index(row, col++), temp);
@ -121,16 +226,20 @@ void ResourceManager::displayLanguages()
bool verifyInstruments = verifyLanguageFile(filenameInstruments, hashInstruments);
if (verifyMScore && verifyInstruments) { // compare local file with distant hash
temp->setText(tr("No update"));
temp->setText(tr("Updated"));
temp->setDisabled(1);
}
else {
connect(temp, SIGNAL(clicked()), this, SLOT(download()));
connect(temp, SIGNAL(clicked()), this, SLOT(downloadLanguage()));
}
row++;
}
}
//---------------------------------------------------------
// verifyLanguageFile
//---------------------------------------------------------
bool ResourceManager::verifyLanguageFile(QString filename, QString hash)
{
QString local = dataPath + "/locale/" + filename;
@ -143,21 +252,25 @@ bool ResourceManager::verifyLanguageFile(QString filename, QString hash)
return verifyFile(local, hash);
}
void ResourceManager::download()
//---------------------------------------------------------
// downloadLanguage
//---------------------------------------------------------
void ResourceManager::downloadLanguage()
{
QPushButton *button = qobject_cast<QPushButton*>( sender() );
QString data = buttonMap[button];
QString hash = buttonHashMap[button];
QPushButton *button = static_cast<QPushButton*>( sender() );
QString data = languageButtonMap[button];
QString hash = languageButtonHashMap[button];
button->setText(tr("Updating"));
button->setDisabled(1);
QString baseAddress = baseAddr + data;
DownloadUtils *dl = new DownloadUtils(this);
dl->setTarget(baseAddress);
qDebug() << baseAddress;
button->setDisabled(true);
QString baseAddress = baseAddr() + data;
DownloadUtils dl(this);
dl.setTarget(baseAddress);
QString localPath = dataPath + "/locale/" + data.split('/')[1];
dl->setLocalFile(localPath);
dl->download();
if( !dl->saveFile() || !verifyFile(localPath, hash)) {
dl.setLocalFile(localPath);
dl.download();
if (!dl.saveFile() || !verifyFile(localPath, hash)) {
button->setText(tr("Failed, try again"));
button->setEnabled(1);
}
@ -166,7 +279,7 @@ void ResourceManager::download()
MQZipReader zipFile(localPath);
QFileInfo zfi(localPath);
QString destinationDir(zfi.absolutePath());
QList<MQZipReader::FileInfo> allFiles = zipFile.fileInfoList();
QVector<MQZipReader::FileInfo> allFiles = zipFile.fileInfoList();
bool result = true;
foreach (MQZipReader::FileInfo fi, allFiles) {
const QString absPath = destinationDir + "/" + fi.filePath;
@ -186,7 +299,7 @@ void ResourceManager::download()
QFile::remove(localPath);
button->setText(tr("Updated"));
// retranslate the UI if current language is updated
if (data == buttonMap.first())
if (data == languageButtonMap.first())
setMscoreLocale(localeName);
}
else {
@ -196,6 +309,79 @@ void ResourceManager::download()
}
}
//---------------------------------------------------------
// downloadExtension
//---------------------------------------------------------
void ResourceManager::downloadExtension()
{
QPushButton* button = static_cast<QPushButton*>(sender());
QString data = button->property("path").toString();
QString hash = button->property("hash").toString();
button->setText(tr("Updating"));
button->setDisabled(true);
QString baseAddress = baseAddr() + data;
DownloadUtils dl(this);
dl.setTarget(baseAddress);
QString localPath = QDir::tempPath() + QDir::separator() + data.split('/')[1];
QFile::remove(localPath);
dl.setLocalFile(localPath);
dl.download(true);
bool saveFileRes = dl.saveFile();
bool verifyFileRes = saveFileRes && verifyFile(localPath, hash);
if(!verifyFileRes) {
QFile::remove(localPath);
button->setText(tr("Failed, try again"));
button->setEnabled(true);
QMessageBox msgBox;
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setTextFormat(Qt::RichText);
msgBox.setWindowTitle(tr("Extensions Installation Failed"));
if (!saveFileRes) //failed to save file on disk
msgBox.setText(tr("Unable to save the extension file on disk"));
else //failed to verify package, so size or hash sum is incorrect
msgBox.setText(tr("Unable to download, save and verify the package.\nCheck your internet connection."));
msgBox.exec();
}
else {
bool result = mscore->importExtension(localPath);
if (result) {
QFile::remove(localPath);
button->setText(tr("Updated"));
// find uninstall button and make it visible
int rowId = button->property("rowId").toInt();
QPushButton* uninstallButton = static_cast<QPushButton*>(extensionsTable->indexWidget(extensionsTable->model()->index(rowId, 4)));
uninstallButton->setDisabled(false);
}
else {
button->setText(tr("Failed, try again"));
button->setEnabled(1);
}
}
}
//---------------------------------------------------------
// uninstallExtension
//---------------------------------------------------------
void ResourceManager::uninstallExtension()
{
QPushButton* uninstallButton = static_cast<QPushButton*>(sender());
QString extensionId = uninstallButton->property("extensionId").toString();
if (mscore->uninstallExtension(extensionId)) {
// find uninstall button and make it visible
int rowId = uninstallButton->property("rowId").toInt();
QPushButton* installButton = static_cast<QPushButton*>(extensionsTable->indexWidget(extensionsTable->model()->index(rowId, 3)));
installButton->setText("Install");
installButton->setDisabled(false);
uninstallButton->setDisabled(true);
}
}
//---------------------------------------------------------
// verifyFile
//---------------------------------------------------------
bool ResourceManager::verifyFile(QString path, QString hash)
{

View file

@ -23,21 +23,27 @@ class ResourceManager : public QDialog, public Ui::Resource
Q_OBJECT
virtual void hideEvent(QHideEvent*);
public:
explicit ResourceManager(QWidget *parent = 0);
QByteArray txt;
void displayLanguages();
void displayPlugins();
void displayExtensions();
bool verifyFile(QString path, QString hash);
bool verifyLanguageFile(QString filename, QString hash);
private:
QMap <QPushButton *, QString> buttonMap; // QPushButton -> filename
QMap <QPushButton *, QString> buttonHashMap;// QPushButton -> hash of the file
QString baseAddr;
public:
explicit ResourceManager(QWidget *parent = 0);
void selectLanguagesTab();
void selectExtensionsTab();
public slots:
void download();
static inline QString baseAddr() { return "http://extensions.musescore.org/2.3/"; }
private:
QMap <QPushButton *, QString> languageButtonMap; // QPushButton -> filename
QMap <QPushButton *, QString> languageButtonHashMap;// QPushButton -> hash of the file
private slots:
void downloadLanguage();
void downloadExtension();
void uninstallExtension();
};
}

View file

@ -19,6 +19,71 @@
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="extensions">
<attribute name="title">
<string>Extensions</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<widget class="QTableWidget" name="extensionsTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="rowCount">
<number>2</number>
</property>
<property name="columnCount">
<number>5</number>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<row/>
<row/>
<column>
<property name="text">
<string>Extension</string>
</property>
</column>
<column>
<property name="text">
<string>Version</string>
</property>
</column>
<column>
<property name="text">
<string>File Size</string>
</property>
</column>
<column>
<property name="text">
<string>Install/Update</string>
</property>
</column>
<column>
<property name="text">
<string>Uninstall</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="languages">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
@ -57,7 +122,7 @@
<bool>true</bool>
</property>
<property name="rowCount">
<number>10</number>
<number>2</number>
</property>
<property name="columnCount">
<number>4</number>
@ -67,14 +132,6 @@
</attribute>
<row/>
<row/>
<row/>
<row/>
<row/>
<row/>
<row/>
<row/>
<row/>
<row/>
<column>
<property name="text">
<string>Language</string>
@ -99,21 +156,6 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="plugins">
<attribute name="title">
<string>Plugins</string>
</attribute>
<widget class="QTextBrowser" name="textBrowser">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>251</width>
<height>341</height>
</rect>
</property>
</widget>
</widget>
</widget>
</item>
</layout>

View file

@ -64,6 +64,7 @@ ScoreBrowser::ScoreBrowser(QWidget* parent)
scoreList->layout()->setMargin(0);
_noMatchedScoresLabel = new QLabel(tr("There are no templates matching the current search."));
_noMatchedScoresLabel->setHidden(true);
_noMatchedScoresLabel->setObjectName("noMatchedScoresLabel");
scoreList->layout()->addWidget(_noMatchedScoresLabel);
connect(preview, SIGNAL(doubleClicked(QString)), SIGNAL(scoreActivated(QString)));
if (!_showPreview)
@ -177,8 +178,16 @@ void ScoreBrowser::setScores(QFileInfoList& s)
scoreLists.clear();
QVBoxLayout* l = static_cast<QVBoxLayout*>(scoreList->layout());
while (l->count())
l->removeItem(l->itemAt(0));
QLayoutItem* child;
while (l->count()) {
child = l->takeAt(0);
if (child->widget() != 0) {
if (child->widget()->objectName() == "noMatchedScoresLabel") // do not delete
continue;
delete child->widget();
}
delete child;
}
ScoreListWidget* sl = 0;
@ -271,14 +280,14 @@ void ScoreBrowser::selectLast()
ScoreItem* item = static_cast<ScoreItem*>(w->item(w->count()-1));
w->setCurrentItem(item);
preview->setScore(item->info());
}
}
//---------------------------------------------------------
// filter
// filter which scores are visible based on searchString
//---------------------------------------------------------
void ScoreBrowser::filter(const QString &searchString)
{
{
int numCategoriesWithMathingScores = 0;
for (ScoreListWidget* list : scoreLists) {
@ -308,7 +317,7 @@ void ScoreBrowser::filter(const QString &searchString)
}
_noMatchedScoresLabel->setHidden(numCategoriesWithMathingScores > 0);
}
}
//---------------------------------------------------------
// scoreChanged

View file

@ -95,4 +95,72 @@ QString stringutils::removeDiacritics(const QString& pre)
return result;
}
//---------------------------------------------------------
// convertFileSizeToHumanReadable
//---------------------------------------------------------
QString stringutils::convertFileSizeToHumanReadable(const qlonglong& bytes)
{
QString number;
if (bytes < 0x400) {//If less than 1 KB, report in B
number = QLocale::system().toString(bytes);
number.append(" B");
return number;
}
else {
if (bytes >= 0x400 && bytes < 0x100000) { //If less than 1 MB, report in KB, unless rounded result is 1024 KB, then report in MB
qlonglong result = (bytes + (0x400 / 2)) / 0x400;
if(result < 0x400) {
number = QLocale::system().toString(result);
number.append(" kB");
return number;
}
else {
qlonglong result = (bytes + (0x100000 / 2)) / 0x100000;
number = QLocale::system().toString(result);
number.append(" MB");
return number;
}
}
else {
if (bytes >= 0x100000 && bytes < 0x40000000) { //If less than 1 GB, report in MB, unless rounded result is 1024 MB, then report in GB
qlonglong result = (bytes + (0x100000 / 2)) / 0x100000;
if (result < 0x100000) {
number = QLocale::system().toString(result);
number.append(" MB");
return number;
}
else {
qlonglong result = (bytes + (0x40000000 / 2)) / 0x40000000;
number = QLocale::system().toString(result);
number.append(" GB");
return number;
}
}
else {
if (bytes >= 0x40000000 && bytes < 0x10000000000) { //If less than 1 TB, report in GB, unless rounded result is 1024 GB, then report in TB
qlonglong result = (bytes + (0x40000000 / 2)) / 0x40000000;
if (result < 0x40000000) {
number = QLocale::system().toString(result);
number.append(" GB");
return number;
}
else {
qlonglong result = (bytes + (0x10000000000 / 2)) / 0x10000000000;
number = QLocale::system().toString(result);
number.append(" TB");
return number;
}
}
else {
qlonglong result = (bytes + (0x10000000000 / 2)) / 0x10000000000; //If more than 1 TB, report in TB
number = QLocale::system().toString(result);
number.append(" TB");
return number;
}
}
}
}
}
} // namespace Ms

View file

@ -30,6 +30,7 @@ class stringutils : public QObject
public:
static QString removeLigatures(const QString& pre);
static QString removeDiacritics(const QString& pre);
static QString convertFileSizeToHumanReadable(const qlonglong & bytes);
};
} // namespace Ms

View file

@ -320,16 +320,7 @@ void SynthControl::storeButtonClicked()
qDebug("no score");
return;
}
QString s(dataPath + "/synthesizer.xml");
QFile f(s);
if (!f.open(QIODevice::WriteOnly)) {
qDebug("cannot write synthesizer settings <%s>", qPrintable(s));
return;
}
XmlWriter xml(0, &f);
xml.header();
synti->state().write(xml);
synti->storeState();
storeButton->setEnabled(false);
recallButton->setEnabled(false);
}

View file

@ -21,20 +21,35 @@
#include "musescore.h"
#include "libmscore/mscore.h"
#include "preferences.h"
#include "resourceManager.h"
#include "extension.h"
#include "libmscore/utils.h"
namespace Ms {
UpdateChecker::UpdateChecker()
//---------------------------------------------------------
// default period
//---------------------------------------------------------
static int defaultPeriod()
{
int result = 24;
if(qApp->applicationName() == "MuseScore2"){ //avoid nightly cymbals
if (MuseScore::unstable())
result = 24;
else
result = 24; // yes, it's again the same but let's keep the logic for now
}
return result;
}
UpdateChecker::UpdateChecker(QObject* parent)
: UpdateCheckerBase(parent)
{
manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onRequestFinished(QNetworkReply*)));
}
UpdateChecker::~UpdateChecker()
{
delete manager;
}
void UpdateChecker::onRequestFinished(QNetworkReply* reply)
{
if (reply->error() != QNetworkReply::NoError) {
@ -54,7 +69,6 @@ void UpdateChecker::onRequestFinished(QNetworkReply* reply)
QString downloadUrl;
QString infoUrl;
QString description;
QString releaseType;
while (!reader.atEnd() && !reader.hasError()) {
QXmlStreamReader::TokenType token = reader.readNext();
@ -113,6 +127,16 @@ QString UpdateChecker::parseText(QXmlStreamReader& reader)
return result;
}
bool UpdateChecker::getUpdatePrefValue()
{
return preferences.getBool(PREF_UI_APP_STARTUP_CHECKUPDATE);
}
QString UpdateChecker::getUpdatePrefString()
{
return "lastUpdateDate";
}
void UpdateChecker::check(QString currentVersion, bool m)
{
manual = m;
@ -135,45 +159,110 @@ void UpdateChecker::check(QString currentVersion, bool m)
qDebug("release type: %s", release.toLatin1().constData());
if (!os.isEmpty() && !release.isEmpty() && release != "nightly") {
_currentVersion = currentVersion;
manager->get(QNetworkRequest(QUrl("http://update.musescore.org/update_"+os +"_" + release +".xml")));
manager->get(QNetworkRequest(QUrl("http://update.musescore.org/update_" + os +"_" + release +".xml")));
}
}
//---------------------------------------------------------
// default period
//---------------------------------------------------------
int UpdateChecker::defaultPeriod()
UpdateCheckerBase::UpdateCheckerBase(QObject* parent)
: QObject(parent)
{
int result = 24;
if (qApp->applicationName() == "MuseScore3") { //avoid nightly cymbals
if (MuseScore::unstable())
result = 24;
else
result = 24; // yes, it's again the same but let's keep the logic for now
}
return result;
}
//---------------------------------------------------------
// default hasToCheck
//---------------------------------------------------------
bool UpdateChecker::hasToCheck()
bool UpdateCheckerBase::hasToCheck()
{
if (!preferences.getBool(PREF_UI_APP_STARTUP_CHECKUPDATE))
if(!getUpdatePrefValue())
return false;
QSettings s;
s.beginGroup("Update");
QDateTime now = QDateTime::currentDateTime();
QDateTime lastUpdate = s.value("lastUpdateDate", now).value<QDateTime>();
QDateTime lastUpdate = s.value(getUpdatePrefString(), now).value<QDateTime>();
if (MScore::debugMode) {
qDebug("preferences.checkUpdateStartup: %d" , preferences.getBool(PREF_UI_APP_STARTUP_CHECKUPDATE));
qDebug("lastupdate: %s", qPrintable(lastUpdate.toString("dd.MM.yyyy hh:mm:ss.zzz")));
qDebug(QString("preferences." + getUpdatePrefString() + ": %d").toStdString().c_str() , getUpdatePrefValue());
qDebug(QString("last update for " + getUpdatePrefString() + ": %s").toStdString().c_str(), qPrintable(lastUpdate.toString("dd.MM.yyyy hh:mm:ss.zzz")));
}
s.endGroup();
return now == lastUpdate || now > lastUpdate.addSecs(3600 * defaultPeriod()) ;
}
ExtensionsUpdateChecker::ExtensionsUpdateChecker(QObject* parent)
: UpdateCheckerBase(parent)
{
}
void ExtensionsUpdateChecker::check()
{
DownloadUtils *js = new DownloadUtils();
js->setTarget(ResourceManager::baseAddr() + "extensions/details.json");
js->download();
QByteArray json = js->returnData();
// parse the json file
QJsonParseError err;
QJsonDocument result = QJsonDocument::fromJson(json, &err);
if (err.error != QJsonParseError::NoError || !result.isObject()) {
qDebug("An error occurred during parsing");
return;
}
QStringList extensions = result.object().keys();
for (QString key : extensions) {
if (!result.object().value(key).isObject())
continue;
QJsonObject value = result.object().value(key).toObject();
QString version = value.value("version").toString();
// get the installed version of the extension if any
if (Extension::isInstalled(key)) {
QString installedVersion = Extension::getLatestVersion(key);
if (compareVersion(installedVersion, version)) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Extension Updates Available"));
msgBox.setText(tr("One or more installed extensions have updates available in Help / Resource Manager..."));
msgBox.setTextFormat(Qt::RichText);
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.setEscapeButton(QMessageBox::Cancel);
int ret = msgBox.exec();
switch (ret) {
case QMessageBox::Ok: {
ResourceManager r(static_cast<QWidget*>(this->parent()));
r.selectExtensionsTab();
r.exec();
break;
}
case QMessageBox::Cancel: {
break;
}
default:
qWarning() << "undefined action in ExtensionsUpdateChecker::check" << ret;
}
QSettings s;
s.beginGroup("Update");
s.setValue(getUpdatePrefString(), QDateTime::currentDateTime());
s.endGroup();
break;
}
}
}
}
bool ExtensionsUpdateChecker::getUpdatePrefValue()
{
return preferences.getBool(PREF_UI_APP_STARTUP_CHECK_EXTENSIONS_UPDATE);
}
QString ExtensionsUpdateChecker::getUpdatePrefString()
{
return "lastExtensionsUpdateDate";
}
}

View file

@ -25,8 +25,19 @@ namespace Ms {
//---------------------------------------------------------
// UpdateChecker
//---------------------------------------------------------
class UpdateCheckerBase: public QObject {
Q_OBJECT
class UpdateChecker : public QObject{
public:
UpdateCheckerBase(QObject* parent);
virtual bool hasToCheck();
private:
virtual bool getUpdatePrefValue() = 0;
virtual QString getUpdatePrefString() = 0;
};
class UpdateChecker : public UpdateCheckerBase {
Q_OBJECT
QNetworkAccessManager* manager;
@ -37,20 +48,29 @@ class UpdateChecker : public QObject{
public:
void check(QString,bool);
static bool hasToCheck();
public slots:
void onRequestFinished(QNetworkReply*);
private:
QString parseText(QXmlStreamReader&);
static int defaultPeriod();
static int computeVersion(QString);
virtual bool getUpdatePrefValue();
virtual QString getUpdatePrefString();
public:
UpdateChecker();
~UpdateChecker();
UpdateChecker(QObject* parent);
};
class ExtensionsUpdateChecker : public UpdateCheckerBase {
Q_OBJECT
public:
ExtensionsUpdateChecker(QObject* parent);
void check();
private:
virtual bool getUpdatePrefValue();
virtual QString getUpdatePrefString();
};
}
#endif // UPDATECHECKER_H

View file

@ -27,6 +27,7 @@
#include "preferences.h"
#include "palette.h"
#include "palettebox.h"
#include "extension.h"
namespace Ms {
@ -458,9 +459,24 @@ void Workspace::save()
QList<Workspace*>& Workspace::workspaces()
{
if (!workspacesRead) {
// Remove all workspaces but Basic and Advanced
QMutableListIterator<Workspace*> i(_workspaces);
int index = 0;
while (i.hasNext()) {
Workspace* w = i.next();
if (index >= 2) {
delete w;
i.remove();
}
index++;
}
QStringList path;
path << mscoreGlobalShare + "workspaces";
path << dataPath + "/workspaces";
QStringList extensionsDir = Extension::getDirectoriesByType(Extension::workspacesDir);
path.append(extensionsDir);
QStringList nameFilters;
nameFilters << "*.workspace";
@ -491,6 +507,16 @@ QList<Workspace*>& Workspace::workspaces()
return _workspaces;
}
//---------------------------------------------------------
// refreshWorkspaces
//---------------------------------------------------------
QList<Workspace*>& Workspace::refreshWorkspaces()
{
workspacesRead = false;
return workspaces();
}
//---------------------------------------------------------
// createNewWorkspace
//---------------------------------------------------------

View file

@ -68,6 +68,7 @@ class Workspace : public QObject {
static Workspace* createNewWorkspace(const QString& name);
static bool workspacesRead;
static void writeBuiltinWorkspace();
static QList<Workspace*>& refreshWorkspaces();
};
}
#endif

View file

@ -101,6 +101,7 @@ add_library(
${PROJECT_SOURCE_DIR}/thirdparty/beatroot/AgentList.cpp # Required by importmidi.cpp
${PROJECT_SOURCE_DIR}/thirdparty/beatroot/BeatTracker.cpp # Required by importmidi.cpp
${PROJECT_SOURCE_DIR}/thirdparty/beatroot/Induction.cpp # Required by importmidi.cpp
${PROJECT_SOURCE_DIR}/mscore/extension.cpp # required by zerberus tests
${OMR_SRC}
omr
)
@ -188,6 +189,7 @@ subdirs (
libmscore/transpose
libmscore/tuplet
# libmscore/text work in progress...
libmscore/utils
importmidi
capella
biab

View file

@ -0,0 +1,17 @@
#=============================================================================
# MuseScore
# Music Composition & Notation
# $Id:$
#
# Copyright (C) 2011 Werner Schweer
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2
# as published by the Free Software Foundation and appearing in
# the file LICENSE.GPL
#=============================================================================
set(TARGET tst_utils)
include(${PROJECT_SOURCE_DIR}/mtest/cmake.inc)

View file

@ -0,0 +1,68 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
// $Id:$
//
// Copyright (C) 2018 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include <QtTest/QtTest>
#include "libmscore/utils.h"
#include "mtest/testutils.h"
#define DIR QString("libmscore/utils/")
using namespace Ms;
//---------------------------------------------------------
// TestNote
//---------------------------------------------------------
class TestUtils : public QObject, public MTest
{
Q_OBJECT
private slots:
void initTestCase();
void tst_compareVersion();
};
//---------------------------------------------------------
// initTestCase
//---------------------------------------------------------
void TestUtils::initTestCase()
{
initMTest();
}
//---------------------------------------------------------
/// test_version
//---------------------------------------------------------
void TestUtils::tst_compareVersion()
{
QVERIFY(compareVersion("0.22", "1.0") == true);
QVERIFY(compareVersion("1", "2") == true);
QVERIFY(compareVersion("1.0", "2.0") == true);
QVERIFY(compareVersion("1.14", "1.16") == true);
QVERIFY(compareVersion("1.16", "1.14") == false);
QVERIFY(compareVersion("2.1", "2.0") == false);
QVERIFY(compareVersion("2.0", "2.1") == true);
QVERIFY(compareVersion("2.1.1.2", "2.0") == false);
QVERIFY(compareVersion("2.0", "2.1.1.3") == true);
QVERIFY(compareVersion("2.1.1.2", "2.1.1.3") == true);
QVERIFY(compareVersion("test", "2.1") == true);
QVERIFY(compareVersion("test1", "test") == false);
}
QTEST_MAIN(TestUtils)
#include "tst_utils.moc"

View file

@ -19,4 +19,4 @@ include_directories(
${SNDFILE_INCDIR}
)
target_link_libraries(tst_sfzcomments zerberus synthesizer audiofile ${SNDFILE_LIB})
target_link_libraries(tst_sfzcomments zerberus synthesizer audiofile ${SNDFILE_LIB} testutils)

View file

@ -19,4 +19,4 @@ include_directories(
${SNDFILE_INCDIR}
)
target_link_libraries(tst_sfzenvelopes zerberus synthesizer audiofile ${SNDFILE_LIB})
target_link_libraries(tst_sfzenvelopes zerberus synthesizer audiofile ${SNDFILE_LIB} testutils)

View file

@ -19,4 +19,4 @@ include_directories(
${SNDFILE_INCDIR}
)
target_link_libraries(tst_sfzglobal zerberus synthesizer audiofile ${SNDFILE_LIB})
target_link_libraries(tst_sfzglobal zerberus synthesizer audiofile ${SNDFILE_LIB} testutils)

View file

@ -19,4 +19,4 @@ include_directories(
${SNDFILE_INCDIR}
)
target_link_libraries(tst_sfzincludes zerberus synthesizer audiofile ${SNDFILE_LIB})
target_link_libraries(tst_sfzincludes zerberus synthesizer audiofile ${SNDFILE_LIB} testutils)

View file

@ -19,4 +19,4 @@ include_directories(
${SNDFILE_INCDIR}
)
target_link_libraries(tst_sfzinputcontrols zerberus synthesizer audiofile ${SNDFILE_LIB})
target_link_libraries(tst_sfzinputcontrols zerberus synthesizer audiofile ${SNDFILE_LIB} testutils)

View file

@ -19,4 +19,4 @@ include_directories(
${SNDFILE_INCDIR}
)
target_link_libraries(tst_sfzloop zerberus synthesizer audiofile ${SNDFILE_LIB})
target_link_libraries(tst_sfzloop zerberus synthesizer audiofile ${SNDFILE_LIB} testutils)

View file

@ -19,4 +19,4 @@ include_directories(
${SNDFILE_INCDIR}
)
target_link_libraries(tst_sfzopcodes zerberus synthesizer audiofile ${SNDFILE_LIB})
target_link_libraries(tst_sfzopcodes zerberus synthesizer audiofile ${SNDFILE_LIB} testutils)

View file

@ -367,6 +367,24 @@ SynthesizerState MasterSynthesizer::state() const
return ss;
}
//---------------------------------------------------------
// storeState
//---------------------------------------------------------
bool MasterSynthesizer::storeState()
{
QString s(dataPath + "/synthesizer.xml");
QFile f(s);
if (!f.open(QIODevice::WriteOnly)) {
qDebug("cannot write synthesizer settings <%s>", qPrintable(s));
return false;
}
XmlWriter xml(0, &f);
xml.header();
state().write(xml);
return true;
}
//---------------------------------------------------------
// setGain
//---------------------------------------------------------

View file

@ -102,6 +102,8 @@ class MasterSynthesizer : public QObject {
float gain() const { return _gain; }
float boost() const { return _boost; }
void setBoost(float v) { _boost = v; }
bool storeState();
};
}

View file

@ -1,39 +1,37 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
@ -47,47 +45,9 @@
#include <zlib.h>
#if defined(Q_OS_WIN) or defined(Q_OS_ANDROID)
# undef S_IFREG
# define S_IFREG 0100000
# ifndef S_IFDIR
# define S_IFDIR 0040000
# endif
# ifndef S_ISDIR
# define S_ISDIR(x) ((x) & S_IFDIR) > 0
# endif
# ifndef S_ISREG
# define S_ISREG(x) ((x) & 0170000) == S_IFREG
# endif
# define S_IFLNK 020000
# define S_ISLNK(x) ((x) & S_IFLNK) > 0
# ifndef S_IRUSR
# define S_IRUSR 0400
# endif
# ifndef S_IWUSR
# define S_IWUSR 0200
# endif
# ifndef S_IXUSR
# define S_IXUSR 0100
# endif
# define S_IRGRP 0040
# define S_IWGRP 0020
# define S_IXGRP 0010
# define S_IROTH 0004
# define S_IWOTH 0002
# define S_IXOTH 0001
#else
// # ifndef S_IFDIR
// # define S_IFDIR 0040000
// # endif
# ifndef S_ISDIR
# define S_ISDIR(x) ((x) & S_IFDIR) > 0
# endif
# ifndef S_ISREG
# define S_ISREG(x) ((x) & 0170000) == S_IFREG
# endif
# define S_ISLNK(x) ((x) & S_IFLNK) > 0
#endif
// Zip standard version for archives handled by this API
// (actually, the only basic support of this version is implemented but it is enough for now)
#define ZIP_VERSION 20
#if 0
#define ZDEBUG qDebug
@ -161,42 +121,12 @@ static void writeMSDosDate(uchar *dest, const QDateTime& dt)
}
}
static quint32 permissionsToMode(QFile::Permissions perms)
{
quint32 mode = 0;
if (perms & QFile::ReadOwner)
mode |= S_IRUSR;
if (perms & QFile::WriteOwner)
mode |= S_IWUSR;
if (perms & QFile::ExeOwner)
mode |= S_IXUSR;
if (perms & QFile::ReadUser)
mode |= S_IRUSR;
if (perms & QFile::WriteUser)
mode |= S_IWUSR;
if (perms & QFile::ExeUser)
mode |= S_IXUSR;
if (perms & QFile::ReadGroup)
mode |= S_IRGRP;
if (perms & QFile::WriteGroup)
mode |= S_IWGRP;
if (perms & QFile::ExeGroup)
mode |= S_IXGRP;
if (perms & QFile::ReadOther)
mode |= S_IROTH;
if (perms & QFile::WriteOther)
mode |= S_IWOTH;
if (perms & QFile::ExeOther)
mode |= S_IXOTH;
return mode;
}
static int inflate(Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
{
z_stream stream;
int err;
stream.next_in = (Bytef*)source;
stream.next_in = const_cast<Bytef*>(source);
stream.avail_in = (uInt)sourceLen;
if ((uLong)stream.avail_in != sourceLen)
return Z_BUF_ERROR;
@ -231,7 +161,7 @@ static int deflate (Bytef *dest, ulong *destLen, const Bytef *source, ulong sour
z_stream stream;
int err;
stream.next_in = (Bytef*)source;
stream.next_in = const_cast<Bytef*>(source);
stream.avail_in = (uInt)sourceLen;
stream.next_out = dest;
stream.avail_out = (uInt)*destLen;
@ -255,36 +185,86 @@ static int deflate (Bytef *dest, ulong *destLen, const Bytef *source, ulong sour
return err;
}
namespace WindowsFileAttributes {
enum {
Dir = 0x10, // FILE_ATTRIBUTE_DIRECTORY
File = 0x80, // FILE_ATTRIBUTE_NORMAL
TypeMask = 0x90,
ReadOnly = 0x01, // FILE_ATTRIBUTE_READONLY
PermMask = 0x01
};
}
namespace UnixFileAttributes {
enum {
Dir = 0040000, // __S_IFDIR
File = 0100000, // __S_IFREG
SymLink = 0120000, // __S_IFLNK
TypeMask = 0170000, // __S_IFMT
ReadUser = 0400, // __S_IRUSR
WriteUser = 0200, // __S_IWUSR
ExeUser = 0100, // __S_IXUSR
ReadGroup = 0040, // __S_IRGRP
WriteGroup = 0020, // __S_IWGRP
ExeGroup = 0010, // __S_IXGRP
ReadOther = 0004, // __S_IROTH
WriteOther = 0002, // __S_IWOTH
ExeOther = 0001, // __S_IXOTH
PermMask = 0777
};
}
static QFile::Permissions modeToPermissions(quint32 mode)
{
QFile::Permissions ret;
if (mode & S_IRUSR)
ret |= QFile::ReadOwner;
if (mode & S_IWUSR)
ret |= QFile::WriteOwner;
if (mode & S_IXUSR)
ret |= QFile::ExeOwner;
if (mode & S_IRUSR)
ret |= QFile::ReadUser;
if (mode & S_IWUSR)
ret |= QFile::WriteUser;
if (mode & S_IXUSR)
ret |= QFile::ExeUser;
if (mode & S_IRGRP)
if (mode & UnixFileAttributes::ReadUser)
ret |= QFile::ReadOwner | QFile::ReadUser;
if (mode & UnixFileAttributes::WriteUser)
ret |= QFile::WriteOwner | QFile::WriteUser;
if (mode & UnixFileAttributes::ExeUser)
ret |= QFile::ExeOwner | QFile::ExeUser;
if (mode & UnixFileAttributes::ReadGroup)
ret |= QFile::ReadGroup;
if (mode & S_IWGRP)
if (mode & UnixFileAttributes::WriteGroup)
ret |= QFile::WriteGroup;
if (mode & S_IXGRP)
if (mode & UnixFileAttributes::ExeGroup)
ret |= QFile::ExeGroup;
if (mode & S_IROTH)
if (mode & UnixFileAttributes::ReadOther)
ret |= QFile::ReadOther;
if (mode & S_IWOTH)
if (mode & UnixFileAttributes::WriteOther)
ret |= QFile::WriteOther;
if (mode & S_IXOTH)
if (mode & UnixFileAttributes::ExeOther)
ret |= QFile::ExeOther;
return ret;
}
static quint32 permissionsToMode(QFile::Permissions perms)
{
quint32 mode = 0;
if (perms & (QFile::ReadOwner | QFile::ReadUser))
mode |= UnixFileAttributes::ReadUser;
if (perms & (QFile::WriteOwner | QFile::WriteUser))
mode |= UnixFileAttributes::WriteUser;
if (perms & (QFile::ExeOwner | QFile::ExeUser))
mode |= UnixFileAttributes::WriteUser;
if (perms & QFile::ReadGroup)
mode |= UnixFileAttributes::ReadGroup;
if (perms & QFile::WriteGroup)
mode |= UnixFileAttributes::WriteGroup;
if (perms & QFile::ExeGroup)
mode |= UnixFileAttributes::ExeGroup;
if (perms & QFile::ReadOther)
mode |= UnixFileAttributes::ReadOther;
if (perms & QFile::WriteOther)
mode |= UnixFileAttributes::WriteOther;
if (perms & QFile::ExeOther)
mode |= UnixFileAttributes::ExeOther;
return mode;
}
static QDateTime readMSDosDate(const uchar *src)
{
uint dosDate = readUInt(src);
@ -300,6 +280,71 @@ static QDateTime readMSDosDate(const uchar *src)
return QDateTime(QDate(tm_year, tm_mon, tm_mday), QTime(tm_hour, tm_min, tm_sec));
}
// for details, see http://www.pkware.com/documents/casestudies/APPNOTE.TXT
enum HostOS {
HostFAT = 0,
HostAMIGA = 1,
HostVMS = 2, // VAX/VMS
HostUnix = 3,
HostVM_CMS = 4,
HostAtari = 5, // what if it's a minix filesystem? [cjh]
HostHPFS = 6, // filesystem used by OS/2 (and NT 3.x)
HostMac = 7,
HostZ_System = 8,
HostCPM = 9,
HostTOPS20 = 10, // pkzip 2.50 NTFS
HostNTFS = 11, // filesystem used by Windows NT
HostQDOS = 12, // SMS/QDOS
HostAcorn = 13, // Archimedes Acorn RISC OS
HostVFAT = 14, // filesystem used by Windows 95, NT
HostMVS = 15,
HostBeOS = 16, // hybrid POSIX/database filesystem
HostTandem = 17,
HostOS400 = 18,
HostOSX = 19
};
Q_DECLARE_TYPEINFO(HostOS, Q_PRIMITIVE_TYPE);
enum GeneralPurposeFlag {
Encrypted = 0x01,
AlgTune1 = 0x02,
AlgTune2 = 0x04,
HasDataDescriptor = 0x08,
PatchedData = 0x20,
StrongEncrypted = 0x40,
Utf8Names = 0x0800,
CentralDirectoryEncrypted = 0x2000
};
Q_DECLARE_TYPEINFO(GeneralPurposeFlag, Q_PRIMITIVE_TYPE);
enum CompressionMethod {
CompressionMethodStored = 0,
CompressionMethodShrunk = 1,
CompressionMethodReduced1 = 2,
CompressionMethodReduced2 = 3,
CompressionMethodReduced3 = 4,
CompressionMethodReduced4 = 5,
CompressionMethodImploded = 6,
CompressionMethodReservedTokenizing = 7, // reserved for tokenizing
CompressionMethodDeflated = 8,
CompressionMethodDeflated64 = 9,
CompressionMethodPKImploding = 10,
CompressionMethodBZip2 = 12,
CompressionMethodLZMA = 14,
CompressionMethodTerse = 18,
CompressionMethodLz77 = 19,
CompressionMethodJpeg = 96,
CompressionMethodWavPack = 97,
CompressionMethodPPMd = 98,
CompressionMethodWzAES = 99
};
Q_DECLARE_TYPEINFO(CompressionMethod, Q_PRIMITIVE_TYPE);
struct LocalFileHeader
{
uchar signature[4]; // 0x04034b50
@ -313,6 +358,7 @@ struct LocalFileHeader
uchar file_name_length[2];
uchar extra_field_length[2];
};
Q_DECLARE_TYPEINFO(LocalFileHeader, Q_PRIMITIVE_TYPE);
struct DataDescriptor
{
@ -320,8 +366,9 @@ struct DataDescriptor
uchar compressed_size[4];
uchar uncompressed_size[4];
};
Q_DECLARE_TYPEINFO(DataDescriptor, Q_PRIMITIVE_TYPE);
struct MCentralFileHeader
struct CentralFileHeader
{
uchar signature[4]; // 0x02014b50
uchar version_made[2];
@ -341,6 +388,7 @@ struct MCentralFileHeader
uchar offset_local_header[4];
LocalFileHeader toLocalHeader() const;
};
Q_DECLARE_TYPEINFO(CentralFileHeader, Q_PRIMITIVE_TYPE);
struct EndOfDirectory
{
@ -353,46 +401,16 @@ struct EndOfDirectory
uchar dir_start_offset[4];
uchar comment_length[2];
};
Q_DECLARE_TYPEINFO(EndOfDirectory, Q_PRIMITIVE_TYPE);
struct FileHeader
{
MCentralFileHeader h;
CentralFileHeader h;
QByteArray file_name;
QByteArray extra_field;
QByteArray file_comment;
};
MQZipReader::FileInfo::FileInfo()
: isDir(false), isFile(false), isSymLink(false), crc32(0), size(0)
{
}
MQZipReader::FileInfo::~FileInfo()
{
}
MQZipReader::FileInfo::FileInfo(const FileInfo &other)
{
operator=(other);
}
MQZipReader::FileInfo& MQZipReader::FileInfo::operator=(const FileInfo &other)
{
filePath = other.filePath;
isDir = other.isDir;
isFile = other.isFile;
isSymLink = other.isSymLink;
permissions = other.permissions;
crc32 = other.crc32;
size = other.size;
lastModified = other.lastModified;
return *this;
}
bool MQZipReader::FileInfo::isValid() const
{
return isDir || isFile || isSymLink;
}
Q_DECLARE_TYPEINFO(FileHeader, Q_MOVABLE_TYPE);
class MQZipPrivate
{
@ -408,28 +426,78 @@ public:
delete device;
}
void fillFileInfo(int index, MQZipReader::FileInfo &fileInfo) const;
MQZipReader::FileInfo fillFileInfo(int index) const;
QIODevice *device;
bool ownDevice;
bool dirtyFileTree;
QList<FileHeader> fileHeaders;
QVector<FileHeader> fileHeaders;
QByteArray comment;
uint start_of_directory;
};
void MQZipPrivate::fillFileInfo(int index, MQZipReader::FileInfo &fileInfo) const
MQZipReader::FileInfo MQZipPrivate::fillFileInfo(int index) const
{
MQZipReader::FileInfo fileInfo;
FileHeader header = fileHeaders.at(index);
fileInfo.filePath = QString::fromLocal8Bit(header.file_name);
const quint32 mode = (qFromLittleEndian<quint32>(&header.h.external_file_attributes[0]) >> 16) & 0xFFFF;
fileInfo.isDir = S_ISDIR(mode);
fileInfo.isFile = S_ISREG(mode);
fileInfo.isSymLink = S_ISLNK(mode);
fileInfo.permissions = modeToPermissions(mode);
fileInfo.crc32 = readUInt(header.h.crc_32);
quint32 mode = readUInt(header.h.external_file_attributes);
const HostOS hostOS = HostOS(readUShort(header.h.version_made) >> 8);
switch (hostOS) {
case HostUnix:
mode = (mode >> 16) & 0xffff;
switch (mode & UnixFileAttributes::TypeMask) {
case UnixFileAttributes::SymLink:
fileInfo.isSymLink = true;
break;
case UnixFileAttributes::Dir:
fileInfo.isDir = true;
break;
case UnixFileAttributes::File:
default: // ### just for the case; should we warn?
fileInfo.isFile = true;
break;
}
fileInfo.permissions = modeToPermissions(mode);
break;
case HostFAT:
case HostNTFS:
case HostHPFS:
case HostVFAT:
switch (mode & WindowsFileAttributes::TypeMask) {
case WindowsFileAttributes::Dir:
fileInfo.isDir = true;
break;
case WindowsFileAttributes::File:
default:
fileInfo.isFile = true;
break;
}
fileInfo.permissions |= QFile::ReadOwner | QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther;
if ((mode & WindowsFileAttributes::ReadOnly) == 0)
fileInfo.permissions |= QFile::WriteOwner | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther;
if (fileInfo.isDir)
fileInfo.permissions |= QFile::ExeOwner | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther;
break;
default:
qWarning("QZip: Zip entry format at %d is not supported.", index);
return fileInfo; // we don't support anything else
}
ushort general_purpose_bits = readUShort(header.h.general_purpose_bits);
// if bit 11 is set, the filename and comment fields must be encoded using UTF-8
const bool inUtf8 = (general_purpose_bits & Utf8Names) != 0;
fileInfo.filePath = inUtf8 ? QString::fromUtf8(header.file_name) : QString::fromLocal8Bit(header.file_name);
fileInfo.crc = readUInt(header.h.crc_32);
fileInfo.size = readUInt(header.h.uncompressed_size);
fileInfo.lastModified = readMSDosDate(header.h.last_mod_file);
// fix the file path, if broken (convert separators, eat leading and trailing ones)
fileInfo.filePath = QDir::fromNativeSeparators(fileInfo.filePath);
while (!fileInfo.filePath.isEmpty() && (fileInfo.filePath.at(0) == QLatin1Char('.') || fileInfo.filePath.at(0) == QLatin1Char('/')))
fileInfo.filePath = fileInfo.filePath.mid(1);
while (!fileInfo.filePath.isEmpty() && fileInfo.filePath.at(fileInfo.filePath.size() - 1) == QLatin1Char('/'))
fileInfo.filePath.chop(1);
return fileInfo;
}
class MQZipReaderPrivate : public MQZipPrivate
@ -465,7 +533,7 @@ public:
void addEntry(EntryType type, const QString &fileName, const QByteArray &contents);
};
LocalFileHeader MCentralFileHeader::toLocalHeader() const
LocalFileHeader CentralFileHeader::toLocalHeader() const
{
LocalFileHeader h;
writeUInt(h.signature, 0x04034b50);
@ -500,7 +568,7 @@ void MQZipReaderPrivate::scanFiles()
uchar tmp[4];
device->read((char *)tmp, 4);
if (readUInt(tmp) != 0x04034b50) {
qWarning() << "QZip: not a zip file!";
qWarning("QZip: not a zip file!");
return;
}
@ -510,9 +578,9 @@ void MQZipReaderPrivate::scanFiles()
int num_dir_entries = 0;
EndOfDirectory eod;
while (start_of_directory == -1) {
int pos = device->size() - sizeof(EndOfDirectory) - i;
const int pos = device->size() - int(sizeof(EndOfDirectory)) - i;
if (pos < 0 || i > 65535) {
qWarning() << "QZip: EndOfDirectory not found";
qWarning("QZip: EndOfDirectory not found");
return;
}
@ -529,39 +597,39 @@ void MQZipReaderPrivate::scanFiles()
ZDEBUG("start_of_directory at %d, num_dir_entries=%d", start_of_directory, num_dir_entries);
int comment_length = readUShort(eod.comment_length);
if (comment_length != i)
qWarning() << "QZip: failed to parse zip file.";
qWarning("QZip: failed to parse zip file.");
comment = device->read(qMin(comment_length, i));
device->seek(start_of_directory);
for (i = 0; i < num_dir_entries; ++i) {
FileHeader header;
int read = device->read((char *) &header.h, sizeof(MCentralFileHeader));
if (read < (int)sizeof(MCentralFileHeader)) {
qWarning() << "QZip: Failed to read complete header, index may be incomplete";
int read = device->read((char *) &header.h, sizeof(CentralFileHeader));
if (read < (int)sizeof(CentralFileHeader)) {
qWarning("QZip: Failed to read complete header, index may be incomplete");
break;
}
if (readUInt(header.h.signature) != 0x02014b50) {
qWarning() << "QZip: invalid header signature, index may be incomplete";
qWarning("QZip: invalid header signature, index may be incomplete");
break;
}
int l = readUShort(header.h.file_name_length);
header.file_name = device->read(l);
if (header.file_name.length() != l) {
qWarning() << "QZip: Failed to read filename from zip index, index may be incomplete";
qWarning("QZip: Failed to read filename from zip index, index may be incomplete");
break;
}
l = readUShort(header.h.extra_field_length);
header.extra_field = device->read(l);
if (header.extra_field.length() != l) {
qWarning() << "QZip: Failed to read extra field in zip file, skipping file, index may be incomplete";
qWarning("QZip: Failed to read extra field in zip file, skipping file, index may be incomplete");
break;
}
l = readUShort(header.h.file_comment_length);
header.file_comment = device->read(l);
if (header.file_comment.length() != l) {
qWarning() << "QZip: Failed to read read file comment, index may be incomplete";
qWarning("QZip: Failed to read read file comment, index may be incomplete");
break;
}
@ -573,7 +641,7 @@ void MQZipReaderPrivate::scanFiles()
void MQZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const QByteArray &contents/*, QFile::Permissions permissions, QZip::Method m*/)
{
#ifndef NDEBUG
static const char *entryTypes[] = {
static const char *const entryTypes[] = {
"directory",
"file ",
"symlink " };
@ -596,15 +664,15 @@ void MQZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const
}
FileHeader header;
memset(&header.h, 0, sizeof(MCentralFileHeader));
memset(&header.h, 0, sizeof(CentralFileHeader));
writeUInt(header.h.signature, 0x02014b50);
writeUShort(header.h.version_needed, 0x14);
writeUShort(header.h.version_needed, ZIP_VERSION);
writeUInt(header.h.uncompressed_size, contents.length());
writeMSDosDate(header.h.last_mod_file, QDateTime::currentDateTime());
QByteArray data = contents;
if (compression == MQZipWriter::AlwaysCompress) {
writeUShort(header.h.compression_method, 8);
writeUShort(header.h.compression_method, CompressionMethodDeflated);
ulong len = contents.length();
// shamelessly copied form zlib
@ -634,22 +702,40 @@ void MQZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const
crc_32 = ::crc32(crc_32, (const uchar *)contents.constData(), contents.length());
writeUInt(header.h.crc_32, crc_32);
header.file_name = fileName.toUtf8();
// if bit 11 is set, the filename and comment fields must be encoded using UTF-8
ushort general_purpose_bits = Utf8Names; // always use utf-8
writeUShort(header.h.general_purpose_bits, general_purpose_bits);
const bool inUtf8 = (general_purpose_bits & Utf8Names) != 0;
header.file_name = inUtf8 ? fileName.toUtf8() : fileName.toLocal8Bit();
if (header.file_name.size() > 0xffff) {
qWarning("QZip: Filename too long, chopping it to 65535 characters");
header.file_name = header.file_name.left(0xffff);
qWarning("QZip: Filename is too long, chopping it to 65535 bytes");
header.file_name = header.file_name.left(0xffff); // ### don't break the utf-8 sequence, if any
}
if (header.file_comment.size() + header.file_name.size() > 0xffff) {
qWarning("QZip: File comment is too long, chopping it to 65535 bytes");
header.file_comment.truncate(0xffff - header.file_name.size()); // ### don't break the utf-8 sequence, if any
}
writeUShort(header.h.file_name_length, header.file_name.length());
//h.extra_field_length[2];
writeUShort(header.h.version_made, 3 << 8);
writeUShort(header.h.version_made, HostUnix << 8);
//uchar internal_file_attributes[2];
//uchar external_file_attributes[4];
quint32 mode = permissionsToMode(permissions);
switch (type) {
case File: mode |= S_IFREG; break;
case Directory: mode |= S_IFDIR; break;
case Symlink: mode |= S_IFLNK; break;
case Symlink:
mode |= UnixFileAttributes::SymLink;
break;
case Directory:
mode |= UnixFileAttributes::Dir;
break;
case File:
mode |= UnixFileAttributes::File;
break;
default:
Q_UNREACHABLE();
break;
}
writeUInt(header.h.external_file_attributes, mode << 16);
writeUInt(header.h.offset_local_header, start_of_directory);
@ -699,8 +785,8 @@ void MQZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const
*/
/*!
\variable FileInfo::crc32
The calculated checksum as a crc32 type.
\variable FileInfo::crc
The calculated checksum as a crc type.
*/
/*!
@ -708,12 +794,6 @@ void MQZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const
The total size of the unpacked content.
*/
/*!
\variable FileInfo::d
\internal
private pointer.
*/
/*!
\class QZipReader
\internal
@ -736,16 +816,17 @@ void MQZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const
MQZipReader::MQZipReader(const QString &archive, QIODevice::OpenMode mode)
{
QScopedPointer<QFile> f(new QFile(archive));
f->open(mode);
const bool result = f->open(mode);
MQZipReader::Status status;
if (f->error() == QFile::NoError)
const QFileDevice::FileError error = f->error();
if (result && error == QFile::NoError) {
status = NoError;
else {
if (f->error() == QFile::ReadError)
} else {
if (error == QFile::ReadError)
status = FileReadError;
else if (f->error() == QFile::OpenError)
else if (error == QFile::OpenError)
status = FileOpenError;
else if (f->error() == QFile::PermissionsError)
else if (error == QFile::PermissionsError)
status = FilePermissionsError;
else
status = FileError;
@ -785,7 +866,7 @@ QIODevice* MQZipReader::device() const
}
/*!
Returns true if the user can read the file; otherwise returns false.
Returns \c true if the user can read the file; otherwise returns \c false.
*/
bool MQZipReader::isReadable() const
{
@ -793,7 +874,7 @@ bool MQZipReader::isReadable() const
}
/*!
Returns true if the file exists; otherwise returns false.
Returns \c true if the file exists; otherwise returns \c false.
*/
bool MQZipReader::exists() const
{
@ -806,15 +887,14 @@ bool MQZipReader::exists() const
/*!
Returns the list of files the archive contains.
*/
QList<MQZipReader::FileInfo> MQZipReader::fileInfoList() const
QVector<MQZipReader::FileInfo> MQZipReader::fileInfoList() const
{
d->scanFiles();
QList<MQZipReader::FileInfo> files;
for (int i = 0; i < d->fileHeaders.size(); ++i) {
MQZipReader::FileInfo fi;
d->fillFileInfo(i, fi);
files.append(fi);
}
QVector<FileInfo> files;
const int numFileHeaders = d->fileHeaders.size();
files.reserve(numFileHeaders);
for (int i = 0; i < numFileHeaders; ++i)
files.append(d->fillFileInfo(i));
return files;
}
@ -838,10 +918,9 @@ int MQZipReader::count() const
MQZipReader::FileInfo MQZipReader::entryInfoAt(int index) const
{
d->scanFiles();
MQZipReader::FileInfo fi;
if (index >= 0 && index < d->fileHeaders.count())
d->fillFileInfo(index, fi);
return fi;
return d->fillFileInfo(index);
return MQZipReader::FileInfo();
}
/*!
@ -860,6 +939,13 @@ QByteArray MQZipReader::fileData(const QString &fileName) const
FileHeader header = d->fileHeaders.at(i);
ushort version_needed = readUShort(header.h.version_needed);
if (version_needed > ZIP_VERSION) {
qWarning("QZip: .ZIP specification version %d implementationis needed to extract the data.", version_needed);
return QByteArray();
}
ushort general_purpose_bits = readUShort(header.h.general_purpose_bits);
int compressed_size = readUInt(header.h.compressed_size);
int uncompressed_size = readUInt(header.h.uncompressed_size);
int start = readUInt(header.h.offset_local_header);
@ -874,13 +960,18 @@ QByteArray MQZipReader::fileData(const QString &fileName) const
int compression_method = readUShort(lh.compression_method);
//qDebug("file=%s: compressed_size=%d, uncompressed_size=%d", fileName.toLocal8Bit().data(), compressed_size, uncompressed_size);
if ((general_purpose_bits & Encrypted) != 0) {
qWarning("QZip: Unsupported encryption method is needed to extract the data.");
return QByteArray();
}
//qDebug("file at %lld", d->device->pos());
QByteArray compressed = d->device->read(compressed_size);
if (compression_method == 0) {
if (compression_method == CompressionMethodStored) {
// no compression
compressed.truncate(uncompressed_size);
return compressed;
} else if (compression_method == 8) {
} else if (compression_method == CompressionMethodDeflated) {
// Deflate
//qDebug("compressed=%d", compressed.size());
compressed.truncate(compressed_size);
@ -890,7 +981,7 @@ QByteArray MQZipReader::fileData(const QString &fileName) const
do {
baunzip.resize(len);
res = inflate((uchar*)baunzip.data(), &len,
(uchar*)compressed.constData(), compressed_size);
(const uchar*)compressed.constData(), compressed_size);
switch (res) {
case Z_OK:
@ -910,7 +1001,8 @@ QByteArray MQZipReader::fileData(const QString &fileName) const
} while (res == Z_BUF_ERROR);
return baunzip;
}
qWarning() << "QZip: Unknown compression method";
qWarning("QZip: Unsupported compression method %d is needed to extract the data.", compression_method);
return QByteArray();
}
@ -924,8 +1016,8 @@ bool MQZipReader::extractAll(const QString &destinationDir) const
QDir baseDir(destinationDir);
// create directories first
QList<FileInfo> allFiles = fileInfoList();
foreach (FileInfo fi, allFiles) {
const QVector<FileInfo> allFiles = fileInfoList();
for (const FileInfo &fi : allFiles) {
const QString absPath = destinationDir + QDir::separator() + fi.filePath;
if (fi.isDir) {
if (!baseDir.mkpath(fi.filePath))
@ -936,7 +1028,7 @@ bool MQZipReader::extractAll(const QString &destinationDir) const
}
// set up symlinks
foreach (FileInfo fi, allFiles) {
for (const FileInfo &fi : allFiles) {
const QString absPath = destinationDir + QDir::separator() + fi.filePath;
if (fi.isSymLink) {
QString destination = QFile::decodeName(fileData(fi.filePath));
@ -954,7 +1046,7 @@ bool MQZipReader::extractAll(const QString &destinationDir) const
}
}
foreach (FileInfo fi, allFiles) {
for (const FileInfo &fi : allFiles) {
const QString absPath = destinationDir + QDir::separator() + fi.filePath;
if (fi.isFile) {
QFile f(absPath);
@ -1021,9 +1113,8 @@ void MQZipReader::close()
MQZipWriter::MQZipWriter(const QString &fileName, QIODevice::OpenMode mode)
{
QScopedPointer<QFile> f(new QFile(fileName));
f->open(mode);
MQZipWriter::Status status;
if (f->error() == QFile::NoError)
if (f->open(mode) && f->error() == QFile::NoError)
status = MQZipWriter::NoError;
else {
if (f->error() == QFile::WriteError)
@ -1067,7 +1158,7 @@ QIODevice* MQZipWriter::device() const
}
/*!
Returns true if the user can write to the archive; otherwise returns false.
Returns \c true if the user can write to the archive; otherwise returns \c false.
*/
bool MQZipWriter::isWritable() const
{
@ -1075,7 +1166,7 @@ bool MQZipWriter::isWritable() const
}
/*!
Returns true if the file exists; otherwise returns false.
Returns \c true if the file exists; otherwise returns \c false.
*/
bool MQZipWriter::exists() const
{
@ -1175,7 +1266,7 @@ QFile::Permissions MQZipWriter::creationPermissions() const
*/
void MQZipWriter::addFile(const QString &fileName, const QByteArray &data)
{
d->addEntry(MQZipWriterPrivate::File, fileName, data);
d->addEntry(MQZipWriterPrivate::File, QDir::fromNativeSeparators(fileName), data);
}
/*!
@ -1197,7 +1288,7 @@ void MQZipWriter::addFile(const QString &fileName, QIODevice *device)
return;
}
}
d->addEntry(MQZipWriterPrivate::File, fileName, device->readAll());
d->addEntry(MQZipWriterPrivate::File, QDir::fromNativeSeparators(fileName), device->readAll());
if (opened)
device->close();
}
@ -1208,10 +1299,10 @@ void MQZipWriter::addFile(const QString &fileName, QIODevice *device)
*/
void MQZipWriter::addDirectory(const QString &dirName)
{
QString name = dirName;
QString name(QDir::fromNativeSeparators(dirName));
// separator is mandatory
if (!name.endsWith(QDir::separator()))
name.append(QDir::separator());
if (!name.endsWith(QLatin1Char('/')))
name.append(QLatin1Char('/'));
d->addEntry(MQZipWriterPrivate::Directory, name, QByteArray());
}
@ -1222,7 +1313,7 @@ void MQZipWriter::addDirectory(const QString &dirName)
*/
void MQZipWriter::addSymLink(const QString &fileName, const QString &destination)
{
d->addEntry(MQZipWriterPrivate::Symlink, fileName, QFile::encodeName(destination));
d->addEntry(MQZipWriterPrivate::Symlink, QDir::fromNativeSeparators(fileName), QFile::encodeName(destination));
}
/*!
@ -1240,7 +1331,7 @@ void MQZipWriter::close()
// write new directory
for (int i = 0; i < d->fileHeaders.size(); ++i) {
const FileHeader &header = d->fileHeaders.at(i);
d->device->write((const char *)&header.h, sizeof(MCentralFileHeader));
d->device->write((const char *)&header.h, sizeof(CentralFileHeader));
d->device->write(header.file_name);
d->device->write(header.extra_field);
d->device->write(header.file_comment);

View file

@ -78,23 +78,23 @@ public:
struct FileInfo
{
FileInfo();
FileInfo(const FileInfo &other);
~FileInfo();
FileInfo &operator=(const FileInfo &other);
bool isValid() const;
FileInfo() Q_DECL_NOTHROW
: isDir(false), isFile(false), isSymLink(false), crc(0), size(0)
{}
bool isValid() const Q_DECL_NOTHROW { return isDir || isFile || isSymLink; }
QString filePath;
uint isDir : 1;
uint isFile : 1;
uint isSymLink : 1;
QFile::Permissions permissions;
uint crc32;
uint crc;
qint64 size;
QDateTime lastModified;
void *d;
};
QList<FileInfo> fileInfoList() const;
QVector<FileInfo> fileInfoList() const;
int count() const;
FileInfo entryInfoAt(int index) const;

View file

@ -43,7 +43,7 @@ Sample::~Sample()
Sample* ZInstrument::readSample(const QString& s, MQZipReader* uz)
{
if (uz) {
QList<MQZipReader::FileInfo> fi = uz->fileInfoList();
QVector<MQZipReader::FileInfo> fi = uz->fileInfoList();
buf = uz->fileData(s);
if (buf.isEmpty()) {

View file

@ -446,8 +446,8 @@ void SfzRegion::readOp(const QString& b, const QString& data, SfzControl &c)
loop_mode = LoopMode::CONTINUOUS;
else if (opcode_data == "loop_sustain")
loop_mode = LoopMode::SUSTAIN;
if (loop_mode != LoopMode::ONE_SHOT)
qDebug("SfzRegion: loop_mode <%s>", qPrintable(opcode_data));
//if (loop_mode != LoopMode::ONE_SHOT)
// qDebug("SfzRegion: loop_mode <%s>", qPrintable(opcode_data));
}
else if(opcode == "loop_start")
readLongLong(opcode_data, loopStart);

View file

@ -47,8 +47,8 @@ Zerberus::Zerberus()
initialized = true;
Voice::init();
}
for (int i = 0; i < MAX_VOICES; ++i)
freeVoices.push(new Voice(this));
freeVoices.init(this);
for (int i = 0; i < MAX_CHANNEL; ++i)
_channel[i] = new Channel(this, i);
busy = true; // no sf loaded yet

View file

@ -17,11 +17,14 @@
#include <atomic>
// #include <mutex>
#include <list>
#include <memory>
#include <queue>
#include "synthesizer/synthesizer.h"
#include "synthesizer/event.h"
#include "voice.h"
class Channel;
class ZInstrument;
enum class Trigger : char;
@ -35,32 +38,33 @@ static const int MAX_TRIGGER = 512;
//---------------------------------------------------------
class VoiceFifo {
Voice* buffer[MAX_VOICES];
std::atomic<int> n;
int writeIdx = 0; // index of next slot to write
int readIdx = 0; // index of slot to read
std::queue<Voice*> buffer;
std::vector< std::unique_ptr<Voice> > voices;
public:
VoiceFifo() {
n = 0;
voices.resize(MAX_VOICES);
}
~VoiceFifo() {
for (Voice* v : buffer)
delete v;
void init(Zerberus* z) {
for (int i = 0; i < MAX_VOICES; ++i) {
voices.push_back(std::unique_ptr<Voice>(new Voice(z)));
buffer.push(voices.back().get());
}
}
void push(Voice* v) {
buffer[writeIdx++] = v;
writeIdx %= MAX_VOICES;
++n;
buffer.push(v);
}
Voice* pop() {
Q_ASSERT(n != 0);
--n;
Voice* v = buffer[readIdx++];
readIdx %= MAX_VOICES;
Voice* pop() {
Q_ASSERT(!buffer.empty());
Voice* v = buffer.front();
buffer.pop();
return v;
}
bool empty() const { return n == 0; }
bool empty() const { return buffer.empty(); }
};
//---------------------------------------------------------

View file

@ -13,6 +13,7 @@
#include "zerberusgui.h"
#include "mscore/preferences.h"
#include "mscore/extension.h"
//---------------------------------------------------------
// SfzListDialog
@ -137,6 +138,10 @@ QFileInfoList Zerberus::sfzFiles()
QStringList pl = Ms::preferences.getString(PREF_APP_PATHS_MYSOUNDFONTS).split(";");
pl.prepend(QFileInfo(QString("%1%2").arg(Ms::mscoreGlobalShare).arg("sound")).absoluteFilePath());
// append extensions directory
QStringList extensionsDir = Ms::Extension::getDirectoriesByType(Ms::Extension::sfzsDir);
pl.append(extensionsDir);
foreach (const QString& s, pl) {
QString ss(s);
if (!s.isEmpty() && s[0] == '~')