From 73a8c03fa13f1001309c306e9af8a699ab3a678b Mon Sep 17 00:00:00 2001 From: Maurice O'Neal Date: Sun, 16 Oct 2016 17:04:21 -0400 Subject: [PATCH] Added a logo to the project and changed the "settings" button to "menu" and added some options to the menu that changes the behaviour of the app. such as turning on/off the auto playing of files and the directory and volume control. this is all kept persistent using an IDM formated file in the app's config directory. still having issues with it not calculating the durations properly but i now know why. it's because QMediaPlayer can't calculate the durations of VBR files (variable bit- rate) properly. other than that, everything seems to be working perfectly. i'll add more feasures such as a next/previous file button and a stop button. --- JustAudio.pro | 15 +- app_logo.ico | Bin 0 -> 23230 bytes gui/icon.cpp | 70 +++---- gui/icon.h | 11 +- gui/ui.cpp | 108 ++++++----- gui/ui.h | 31 ++-- icon_files.qrc | 7 +- io/aud_file.cpp | 17 +- io/aud_file.h | 7 +- io/conf.cpp | 150 +++++++++++++++ io/conf.h | 74 ++++++++ io/idm.cpp | 432 ++++++++++++++++++++++++++++++++++++++++++++ io/idm.h | 92 ++++++++++ io/int.cpp | 58 ++++++ io/int.h | 15 ++ logo.rc | 1 + svg/eject.svg | 46 ----- svg/list.svg | 46 ----- svg/menu.svg | 12 ++ svg/open.svg | 10 + svg/pause.svg | 52 ++---- svg/play-button.svg | 40 ---- svg/play.svg | 10 + 23 files changed, 1029 insertions(+), 275 deletions(-) create mode 100644 app_logo.ico create mode 100644 io/conf.cpp create mode 100644 io/conf.h create mode 100644 io/idm.cpp create mode 100644 io/idm.h create mode 100644 io/int.cpp create mode 100644 io/int.h create mode 100644 logo.rc delete mode 100644 svg/eject.svg delete mode 100644 svg/list.svg create mode 100644 svg/menu.svg create mode 100644 svg/open.svg delete mode 100644 svg/play-button.svg create mode 100644 svg/play.svg diff --git a/JustAudio.pro b/JustAudio.pro index 87d0795..0ea749c 100644 --- a/JustAudio.pro +++ b/JustAudio.pro @@ -16,10 +16,21 @@ TEMPLATE = app SOURCES += main.cpp\ gui/ui.cpp\ gui/icon.cpp \ - io/aud_file.cpp + io/aud_file.cpp \ + io/conf.cpp \ + io/idm.cpp \ + io/int.cpp HEADERS += gui/ui.h\ gui/icon.h \ - io/aud_file.h + io/aud_file.h \ + io/conf.h \ + io/idm.h \ + io/int.h RESOURCES += icon_files.qrc + +RC_FILE = logo.rc + +DISTFILES += app_logo.ico \ + logo.rc diff --git a/app_logo.ico b/app_logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..72d8b75b85834ee580c5f6794e128e190b0b8401 GIT binary patch literal 23230 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 literal 0 HcmV?d00001 diff --git a/gui/icon.cpp b/gui/icon.cpp index 4e0b8b2..d8146bd 100644 --- a/gui/icon.cpp +++ b/gui/icon.cpp @@ -1,56 +1,64 @@ #include "icon.h" -Icon::Icon(IconType t, QWidget *parent) : QLabel(parent) +Icon::Icon(IconType t, QWidget *parent) : QToolButton(parent) { QRect rect = QApplication::desktop()->screenGeometry(); setMinimumHeight(rect.height() / 40); setMinimumWidth(rect.height() / 40); setCursor(Qt::PointingHandCursor); + setToolButtonStyle(Qt::ToolButtonIconOnly); + setPopupMode(QToolButton::InstantPopup); switch (t) { - case PAUSE_PLAY: stateChanged(QMediaPlayer::StoppedState); break; - case SETTINGS: loadImg(":/settings"); break; - case OPEN: loadImg(":/open"); break; + case PAUSE_PLAY: + { + stateChanged(QMediaPlayer::StoppedState); + setToolTip(tr("Pause/Play")); + + connect(this, SIGNAL(clicked()), this, SLOT(buttonClicked())); + + break; + } + case MENU: + { + loadImg(":/menu"); + setToolTip(tr("Menu")); + + break; + } + case OPEN: + { + loadImg(":/open"); + setToolTip(tr("Open File")); + + connect(this, SIGNAL(clicked()), this, SLOT(buttonClicked())); + + break; + } } type = t; } -void Icon::mouseReleaseEvent(QMouseEvent *event) +void Icon::buttonClicked() { - if (event->button() == Qt::LeftButton) + if (type == PAUSE_PLAY) { - switch (type) + if (playerState == QMediaPlayer::PlayingState) { - case PAUSE_PLAY: + emit pause(); + } + else { - if (playerState == QMediaPlayer::PlayingState) - { - emit pause(); - } - else - { - emit play(); - } - - break; - } - case SETTINGS: - { - emit settings(); - - break; - } - case OPEN: - { - emit open(); - - break; - } + emit play(); } } + else if (type == OPEN) + { + emit open(); + } } void Icon::paintEvent(QPaintEvent *) diff --git a/gui/icon.h b/gui/icon.h index 0c47c57..f0f56f8 100644 --- a/gui/icon.h +++ b/gui/icon.h @@ -2,7 +2,7 @@ #define ICON_H #include -#include +#include #include #include #include @@ -13,7 +13,7 @@ #include #include -class Icon : public QLabel +class Icon : public QToolButton { Q_OBJECT @@ -22,7 +22,7 @@ public: enum IconType { PAUSE_PLAY, - SETTINGS, + MENU, OPEN }; @@ -38,10 +38,13 @@ private: QMediaPlayer::State playerState; QString svgFile; - void mouseReleaseEvent(QMouseEvent *event); void paintEvent(QPaintEvent *); void loadImg(const QString &path); +private slots: + + void buttonClicked(); + signals: void pause(); diff --git a/gui/ui.cpp b/gui/ui.cpp index 8731e5f..c51e9ce 100644 --- a/gui/ui.cpp +++ b/gui/ui.cpp @@ -8,35 +8,34 @@ Ui::Ui(const QStringList &args, QWidget *parent) : QWidget(parent) fileName = new QLabel(this); slider = new QSlider(this); + menu = new QMenu(this); + trayIcon = new QSystemTrayIcon(QIcon(":/logo"), this); + settings = new Icon(Icon::MENU, this); pausePlay = new Icon(Icon::PAUSE_PLAY, this); - settings = new Icon(Icon::SETTINGS, this); open = new Icon(Icon::OPEN, this); player = new QMediaPlayer(this); ioDev = new AudFile(this); + conf = new Conf(this); pressed = false; - QFont fnt = fileName->font(); - QRect rect = QApplication::desktop()->screenGeometry(); + QFont fnt = fileName->font(); fnt.setBold(true); - setMinimumHeight(rect.height() / 6); - setMinimumWidth(rect.width() / 5); - setMaximumHeight(rect.height() / 6); - setMaximumWidth(rect.width() / 5); setStyleSheet("background-color:white;"); - fileName->setText(QApplication::applicationName()); + settings->setMenu(menu); + fileName->setText(tr("Ready")); fileName->setFont(fnt); slider->setOrientation(Qt::Horizontal); - slider->setMinimumWidth((rect.width() / 5) - 100); - btnLayout->addWidget(pausePlay, 0, Qt::AlignCenter); - btnLayout->addSpacing(rect.height() / 40); - btnLayout->addWidget(open, 0, Qt::AlignCenter); - btnLayout->addSpacing(rect.height() / 40); - btnLayout->addWidget(settings, 0, Qt::AlignCenter); - mainLayout->addWidget(fileName, 0, Qt::AlignCenter); - mainLayout->addWidget(slider, 0, Qt::AlignCenter); + trayIcon->show(); + conf->populateOptionsMenu(menu, this); + player->setVolume(conf->getVolume()); + btnLayout->addWidget(pausePlay); + btnLayout->addWidget(open); + btnLayout->addWidget(settings); + mainLayout->addWidget(fileName); + mainLayout->addWidget(slider); mainLayout->addWidget(btnWid, 0, Qt::AlignCenter); connect(pausePlay, SIGNAL(pause()), player, SLOT(pause())); @@ -44,10 +43,11 @@ Ui::Ui(const QStringList &args, QWidget *parent) : QWidget(parent) connect(open, SIGNAL(open()), this, SLOT(openDialog())); connect(player, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(error(QMediaPlayer::Error))); connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), pausePlay, SLOT(stateChanged(QMediaPlayer::State))); - connect(player, SIGNAL(durationChanged(qint64)), this, SLOT(durationChanged(qint64))); connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(posChanged(qint64))); + connect(player, SIGNAL(durationChanged(qint64)), this, SLOT(durChanged(qint64))); connect(slider, SIGNAL(sliderPressed()), this, SLOT(sliderPressed())); connect(slider, SIGNAL(sliderReleased()), this, SLOT(sliderReleased())); + connect(conf, SIGNAL(volume(int)), player, SLOT(setVolume(int))); if (args.size() > 1) { @@ -57,17 +57,30 @@ Ui::Ui(const QStringList &args, QWidget *parent) : QWidget(parent) void Ui::play(const QString &path) { - QFileInfo info(path); - - fileDir = info.path(); - - slider->setMinimum(0); - fileName->setText(info.fileName()); + player->blockSignals(true); player->stop(); - ioDev->close(); - ioDev->openFile(path); - player->setMedia(0, ioDev); - player->play(); + pausePlay->stateChanged(QMediaPlayer::StoppedState); + + if (ioDev->openFile(path)) + { + QFileInfo info(path); + + conf->setLastPath(info.path()); + fileName->setText(info.fileName()); + player->setMedia(0, ioDev); + slider->setValue(0); + slider->setMinimum(0); + pausePlay->stateChanged(QMediaPlayer::PlayingState); + player->play(); + + adjustSize(); + } + else + { + error(QMediaPlayer::ResourceError); + } + + player->blockSignals(false); } void Ui::openDialog() @@ -76,6 +89,7 @@ void Ui::openDialog() fileDialog.setFileMode(QFileDialog::ExistingFile); fileDialog.setNameFilter(tr("Audio Files (*.mp3 *.ogg *.wav *.flac)")); + fileDialog.setDirectory(conf->getLastPath()); if (fileDialog.exec()) { @@ -90,18 +104,26 @@ void Ui::error(QMediaPlayer::Error error) if (error == QMediaPlayer::NoError) { box.setText(tr("An unknown error has occured")); - } - else - { - box.setText(player->errorString()); - } - - if (error == QMediaPlayer::FormatError) - { box.setIcon(QMessageBox::Warning); } - else + else if (error == QMediaPlayer::FormatError) { + box.setText(tr("The format of the media resource isn't fully supported. Playback may still be possible.")); + box.setIcon(QMessageBox::Warning); + } + else if (error == QMediaPlayer::AccessDeniedError) + { + box.setText(tr("The appropriate permissions to play the media resource are not present")); + box.setIcon(QMessageBox::Critical); + } + else if (error == QMediaPlayer::ServiceMissingError) + { + box.setText(tr("A valid playback service was not found, playback cannot proceed.")); + box.setIcon(QMessageBox::Critical); + } + else if (error == QMediaPlayer::ResourceError) + { + box.setText(tr("Media resource couldn't be resolved.")); box.setIcon(QMessageBox::Critical); } @@ -124,20 +146,20 @@ void Ui::posChanged(qint64 pos) { if (!pressed) slider->setSliderPosition(pos); -// if (slider->sliderPosition() == slider->maximum()) -// { -// QTimer::singleShot(750, this, SLOT(nextFile())); -// } + if ((slider->sliderPosition() == slider->maximum()) && conf->nextFile()) + { + QTimer::singleShot(1000, this, SLOT(nextFile())); + } } -void Ui::durationChanged(qint64 len) +void Ui::durChanged(qint64 len) { slider->setMaximum(len); } void Ui::nextFile() { - QDir dir(fileDir); + QDir dir(conf->getLastPath()); QStringList filterList; @@ -155,7 +177,7 @@ void Ui::nextFile() if (pos < list.size()) { - play(fileDir + list[pos]); + play(conf->getLastPath() + QDir::separator() + list[pos]); } } } diff --git a/gui/ui.h b/gui/ui.h index 24534a1..e3b5b95 100644 --- a/gui/ui.h +++ b/gui/ui.h @@ -2,6 +2,7 @@ #define UI_H #include +#include #include #include #include @@ -9,18 +10,20 @@ #include #include #include -#include #include #include #include #include #include -#include -#include #include +#include +#include +#include +#include #include "icon.h" #include "../io/aud_file.h" +#include "../io/conf.h" class Ui : public QWidget { @@ -28,15 +31,17 @@ class Ui : public QWidget private: - QLabel *fileName; - QSlider *slider; - QMediaPlayer *player; - AudFile *ioDev; - Icon *pausePlay; - Icon *settings; - Icon *open; - QString fileDir; - bool pressed; + QLabel *fileName; + QSlider *slider; + QMediaPlayer *player; + AudFile *ioDev; + QToolButton *settings; + QSystemTrayIcon *trayIcon; + Conf *conf; + QMenu *menu; + Icon *pausePlay; + Icon *open; + bool pressed; private slots: @@ -44,7 +49,7 @@ private slots: void sliderPressed(); void sliderReleased(); void posChanged(qint64 pos); - void durationChanged(qint64 len); + void durChanged(qint64 len); void openDialog(); void nextFile(); void play(const QString &path); diff --git a/icon_files.qrc b/icon_files.qrc index e0580bf..df5f091 100644 --- a/icon_files.qrc +++ b/icon_files.qrc @@ -1,8 +1,9 @@ - svg/play-button.svg + svg/open.svg svg/pause.svg - svg/list.svg - svg/eject.svg + svg/play.svg + svg/menu.svg + app_logo.ico diff --git a/io/aud_file.cpp b/io/aud_file.cpp index e6d9727..50fa46b 100644 --- a/io/aud_file.cpp +++ b/io/aud_file.cpp @@ -12,12 +12,16 @@ AudFile::~AudFile() bool AudFile::openFile(const QString &path) { + close(); + setFileName(path); bool ret = open(QFile::ReadOnly); if (ret) { + offset = 0; + if (peek(3) == "ID3") { QByteArray header = read(10); @@ -43,15 +47,20 @@ bool AudFile::openFile(const QString &path) } offset += num; - - seek(0); } + + seek(0); } return ret; } -bool AudFile::seek(qint64 pos) +bool AudFile::seek(qint64 off) { - return QFile::seek(pos + offset); + return QFile::seek(offset + off); +} + +qint64 AudFile::size() const +{ + return QFile::size() - offset; } diff --git a/io/aud_file.h b/io/aud_file.h index 5e69d53..5ea927a 100644 --- a/io/aud_file.h +++ b/io/aud_file.h @@ -1,9 +1,11 @@ #ifndef AUD_FILE_H #define AUD_FILE_H +#include #include #include #include +#include class AudFile : public QFile { @@ -17,8 +19,9 @@ public: AudFile(QObject *parent = 0); - bool openFile(const QString &path); - bool seek(qint64 pos); + bool openFile(const QString &path); + bool seek(qint64 off); + qint64 size() const; ~AudFile(); }; diff --git a/io/conf.cpp b/io/conf.cpp new file mode 100644 index 0000000..fe62c99 --- /dev/null +++ b/io/conf.cpp @@ -0,0 +1,150 @@ +#include "io/conf.h" + +MenuItem::MenuItem(const QString &label, QWidget *widget, QObject *parent) : QWidgetAction(parent) +{ + str = label; + wid = widget; +} + +QWidget *MenuItem::createWidget(QWidget *parent) +{ + QWidget *widget = new QWidget(parent); + QHBoxLayout *layout = new QHBoxLayout(widget); + + layout->addWidget(new QLabel(str, parent)); + layout->addStretch(); + layout->addWidget(wid, 0, Qt::AlignRight); + + return widget; +} + +Conf::Conf(QObject *parent) : QObject(parent) +{ + QFile file(confPath(), this); + + QByteArray data; + + if (file.open(QFile::ReadOnly)) + { + data = file.readAll(); + } + + file.close(); + + Idm idm(data, this); + + idm.rdHeader(); + + QByteArray pathBa = idm.value(LAST_PATH); + QByteArray nextBa = idm.value(NEXT_FILE); + QByteArray volBa = idm.value(VOLUME); + + if (!pathBa.isEmpty()) lastPath = pathBa; + else lastPath = QDir::homePath(); + if (!nextBa.isEmpty()) nextFileState = (nextBa == "Y"); + else nextFileState = true; + if (!volBa.isEmpty()) volumeValue = rdInt(volBa); + else volumeValue = 50; +} + +void Conf::sync() +{ + Idm idm(this); + + idm.wrHeader(); + idm.insert(LAST_PATH, lastPath.toUtf8()); + idm.insert(VOLUME, wrInt(volumeValue)); + + if (nextFileState) idm.insert(NEXT_FILE, "Y"); + else idm.insert(NEXT_FILE, "N"); + + QFile file(confPath(), this); + + file.open(QFile::WriteOnly | QFile::Truncate); + file.write(idm.getBuff()); +} + +void Conf::populateOptionsMenu(QMenu *menu, QWidget *parent) +{ + QWidget *volWidget = new QWidget(parent); + QHBoxLayout *volLayout = new QHBoxLayout(volWidget); + QSlider *volSlider = new QSlider(parent); + QCheckBox *nextBox = new QCheckBox(parent); + + volSlider->setMinimum(0); + volSlider->setMaximum(100); + volSlider->setValue(getVolume()); + volSlider->setOrientation(Qt::Horizontal); + volLayout->addWidget(new QLabel("-")); + volLayout->addWidget(volSlider); + volLayout->addWidget(new QLabel("+")); + nextBox->setText(""); + nextBox->setChecked(nextFile()); + + connect(volSlider, SIGNAL(valueChanged(int)), this, SLOT(setVolume(int))); + connect(volSlider, SIGNAL(valueChanged(int)), this, SIGNAL(volume(int))); + connect(nextBox, SIGNAL(clicked(bool)), this, SLOT(setNextFile(bool))); + + menu->addAction(new MenuItem(tr("Volume"), volWidget, this)); + menu->addAction(new MenuItem(tr("Auto play next file in directory"), nextBox, this)); +} + +void Conf::setLastPath(const QString &path) +{ + lastPath = path; sync(); +} + +void Conf::setNextFile(bool state) +{ + nextFileState = state; sync(); +} + +void Conf::setVolume(int value) +{ + emit volume(value); + + volumeValue = value; sync(); +} + +QString Conf::getLastPath() +{ + if (!QDir(lastPath).exists()) + { + lastPath = QDir::homePath(); + } + + return lastPath; +} + +bool Conf::nextFile() +{ + return nextFileState; +} + +int Conf::getVolume() +{ + return volumeValue; +} + +QString Conf::confPath() +{ + QString path = QDir::homePath() + '/'; + +#ifndef Q_OS_WIN32 + + path.append("/.config/"); + +#else + + path.append("AppData/Local/"); + +#endif + + QDir dir; + + path.append(QApplication::applicationName() + '/'); + + dir.mkpath(path); + + return path + "conf.idm"; +} diff --git a/io/conf.h b/io/conf.h new file mode 100644 index 0000000..97e6ba9 --- /dev/null +++ b/io/conf.h @@ -0,0 +1,74 @@ +#ifndef CONF_H +#define CONF_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "idm.h" + +class MenuItem : public QWidgetAction +{ + Q_OBJECT + +private: + + QString str; + QWidget *wid; + + QWidget *createWidget(QWidget *parent); + +public: + + MenuItem(const QString &label, QWidget *widget, QObject *parent = 0); +}; + +class Conf : public QObject +{ + Q_OBJECT + +private: + + enum DataType + { + LAST_PATH = 1, + NEXT_FILE, + VOLUME + }; + + QString lastPath; + bool nextFileState; + int volumeValue; + + void sync(); + QString confPath(); + +public slots: + + void setLastPath(const QString &path); + void setNextFile(bool state); + void setVolume(int value); + +public: + + QString getLastPath(); + bool nextFile(); + void populateOptionsMenu(QMenu *menu, QWidget *parent); + int getVolume(); + + Conf(QObject *parent = 0); + +signals: + + void volume(int); +}; + +#endif // CONF_H diff --git a/io/idm.cpp b/io/idm.cpp new file mode 100644 index 0000000..7bd6794 --- /dev/null +++ b/io/idm.cpp @@ -0,0 +1,432 @@ +#include "idm.h" + +const QByteArray Idm::TAG = "IDM"; +const int Idm::MAX_MAJOR = 1; +const int Idm::MAX_MINOR = 0; + int Idm::DEFAULT_INT_SIZE = 3; + int Idm::DEFAULT_FLAGS = 1; // FIXED_KEY_LEN + int Idm::DEFAULT_KEY_LEN = 1; + int Idm::CURRENT_MAJOR = Idm::MAX_MAJOR; + int Idm::CURRENT_MINOR = Idm::MAX_MINOR; + +Idm::Idm(const QByteArray &data, QObject *parent) : QObject(parent) +{ + buffPtr = data; + + init(); +} + +Idm::Idm(QObject *parent) : QObject(parent) +{ + init(); +} + +void Idm::init() +{ + header = 0; + intSize = DEFAULT_INT_SIZE; + keyLen = DEFAULT_KEY_LEN; + flags = DEFAULT_FLAGS; +} + +void Idm::loadData(const QByteArray &data) +{ + op(RELOAD, data); +} + +QByteArray &Idm::getBuff() +{ + op(GET_BUFF, QByteArray(), QByteArray(), new QByteArray()); + + return buffPtr; +} + +bool Idm::rdHeader() +{ + return rdExHeader(buffPtr); +} + +bool Idm::rdExHeader(const QByteArray &data) +{ + bool ret; + + op(RD_HEADER, data, QByteArray(), 0, 0, 0, &ret); + + return ret; +} + +bool Idm::rdExHeader(const QByteArray &data, int &major, int &minor, int &intSz, int &flgs, int &keyLen) +{ + bool ret = false; + + if (data.startsWith(TAG)) + { + int pos = TAG.size(); + + major = rdInt(data.mid(pos++, 1)); + minor = rdInt(data.mid(pos++, 1)); + intSz = rdInt(data.mid(pos++, 1)); + keyLen = rdInt(data.mid(pos++, 1)); + flgs = rdInt(data.mid(pos, 4)); + + if ((major <= MAX_MAJOR) && (minor <= MAX_MINOR)) + { + ret = true; + } + } + + return ret; +} + +bool Idm::verifyExHeader(const QByteArray &data, int major, int minor) +{ + bool ret = false; + + if (data.startsWith(TAG) && (data.size() == 11)) + { + int pos = TAG.size(); + int ma = rdInt(data.mid(pos++, 1)); + int mi = rdInt(data.mid(pos++, 1)); + + ret = ((ma <= major) && (mi <= minor)); + } + + return ret; +} + +QByteArray Idm::buildHeader(int major, int minor, int intSz, int flgs, int keyLen) +{ + QByteArray ret = TAG; + + ret.append(wrInt(major, 1)); + ret.append(wrInt(minor, 1)); + ret.append(wrInt(intSz, 1)); + ret.append(wrInt(keyLen, 1)); + ret.append(wrInt(flgs, 4)); + + return ret; +} + +void Idm::setKeyLen(int len) +{ + op(SET_KEY_LEN, QByteArray(), QByteArray(), 0, len); +} + +void Idm::setFlags(int flgs) +{ + op(SET_FLAGS, QByteArray(), QByteArray(), 0, flgs); +} + +void Idm::wrHeader() +{ + op(WR_HEADER); +} + +void Idm::setIntSize(int bytes) +{ + op(SET_INT_SIZE, QByteArray(), QByteArray(), 0, bytes); +} + +void Idm::setVersion(int major, int minor) +{ + op(SET_VERSION, QByteArray(), QByteArray(), 0, major, minor); +} + +QByteArray Idm::rdData(int addr, int len, bool *ret) +{ + bool ok = false; + + if ((addr < buffPtr.size()) && (addr >= 0)) + { + if ((len + addr) <= buffPtr.size()) + { + ok = true; + } + } + + if (ret) *ret = ok; + + if (ok) + { + return QByteArray::fromRawData(buffPtr.data() + addr, len); + } + else + { + return QByteArray(); + } +} + +bool Idm::findKey(int &addr, int &dataLen, const QByteArray &key) +{ + bool ok = true; + bool found = false; + + while(addr < buffPtr.size() && ok) + { + int keySize; + + if (flags & FIXED_KEY_LEN) + { + keySize = keyLen; + } + else + { + keySize = rdInt(rdData(addr, intSize, &ok)); + + addr += intSize; + } + + if (ok) + { + if (key == rdData(addr, keySize, &ok)) + { + found = true; + } + + addr += keySize; + + if (ok) + { + dataLen = rdInt(rdData(addr, intSize, &ok)); + + addr += intSize; + + if (found && ok) + { + break; + } + else + { + addr += dataLen; + } + } + } + } + + return found; +} + +QByteArray Idm::value(const QByteArray &key) +{ + QByteArray ret; + + op(RD_VALUE, key, QByteArray(), &ret); + + return ret; +} + +QByteArray Idm::value(quint64 key) +{ + int keySize; + + if (flags & FIXED_KEY_LEN) keySize = keyLen; + else keySize = intSize; + + return value(wrInt(key, keySize)); +} + +quint64 Idm::intValue(const QByteArray &key) +{ + return rdInt(value(key)); +} + +quint64 Idm::intValue(quint64 key) +{ + return rdInt(value(key)); +} + +bool Idm::remove(const QByteArray &key) +{ + bool ret; + + op(RM_VALUE, key, QByteArray(), 0, 0, 0, &ret); + + return ret; +} + +bool Idm::remove(quint64 key) +{ + int keySize; + + if (flags & FIXED_KEY_LEN) keySize = keyLen; + else keySize = intSize; + + return remove(wrInt(key, keySize)); +} + +bool Idm::insert(const QByteArray &key, const QByteArray &data) +{ + bool ret; + + op(WR_VALUE, key, data, 0, 0, 0, &ret); + + return ret; +} + +bool Idm::insert(quint64 key, quint64 num) +{ + int keySize; + + if (flags & FIXED_KEY_LEN) keySize = keyLen; + else keySize = intSize; + + return insert(wrInt(key, keySize), wrInt(num, intSize)); +} + +bool Idm::insert(quint64 key, const QByteArray &data) +{ + int keySize; + + if (flags & FIXED_KEY_LEN) keySize = keyLen; + else keySize = intSize; + + return insert(wrInt(key, keySize), data); +} + +void Idm::op(Operation opr, + const QByteArray &inA, + const QByteArray &inB, + QByteArray *out, + int intA, + int intB, + bool *ok) +{ + QMutex m; + + m.lock(); + + switch(opr) + { + case SET_KEY_LEN: + { + keyLen = intA; break; + } + case SET_FLAGS: + { + flags = intA; break; + } + case GET_BUFF: + { + out = &buffPtr; break; + } + case RELOAD: + { + buffPtr = inA; break; + } + case SET_INT_SIZE: + { + intSize = intA; break; + } + case SET_VERSION: + { + CURRENT_MAJOR = intA; + CURRENT_MINOR = intB; + + break; + } + case WR_HEADER: + { + if (buffPtr.startsWith(TAG)) + { + int pos = TAG.size(); + + buffPtr.replace(pos++, 1, wrInt(CURRENT_MAJOR, 1)); + buffPtr.replace(pos++, 1, wrInt(CURRENT_MINOR, 1)); + buffPtr.replace(pos++, 1, wrInt(intSize, 1)); + buffPtr.replace(pos++, 1, wrInt(keyLen, 1)); + buffPtr.replace(pos++, 4, wrInt(flags, 4)); + } + else + { + buffPtr.insert(0, wrInt(flags, 4)); + buffPtr.insert(0, wrInt(keyLen, 1)); + buffPtr.insert(0, wrInt(intSize, 1)); + buffPtr.insert(0, wrInt(CURRENT_MINOR, 1)); + buffPtr.insert(0, wrInt(CURRENT_MAJOR, 1)); + buffPtr.insert(0, TAG); + } + + header = 8 + TAG.size(); + + break; + } + case RD_HEADER: + { + *ok = rdExHeader(inA, CURRENT_MAJOR, CURRENT_MINOR, intSize, flags, keyLen); + + if (*ok) header = 8 + TAG.size(); + + break; + } + case RD_VALUE: + { + int pos = header; + int len = 0; + + if (findKey(pos, len, inA)) + { + out->clear(); + out->append(buffPtr.mid(pos, len)); + } + + break; + } + case RM_VALUE: + { + int pos = header; + int len = 0; + + *ok = false; + + if (findKey(pos, len, inA)) + { + if (flags & FIXED_KEY_LEN) + { + pos -= keyLen + intSize; + len += keyLen + intSize; + } + else + { + pos -= inA.size() + (intSize * 2); + len += inA.size() + (intSize * 2); + } + + buffPtr.remove(pos, len); + + *ok = true; + } + + break; + } + case WR_VALUE: + { + *ok = false; + + remove(inA); + + if (flags & FIXED_KEY_LEN) + { + if (inA.size() == keyLen) + { + buffPtr.append(inA + wrInt(inB.size(), intSize) + inB); + + *ok = true; + } + } + else + { + if (!inA.isEmpty()) + { + buffPtr.append(wrInt(inA.size(), intSize) + inA); + buffPtr.append(wrInt(inB.size(), intSize) + inB); + + *ok = true; + } + } + + break; + } + } + + m.unlock(); +} diff --git a/io/idm.h b/io/idm.h new file mode 100644 index 0000000..67050f5 --- /dev/null +++ b/io/idm.h @@ -0,0 +1,92 @@ +#ifndef IDM_H +#define IDM_H + +#include +#include +#include + +#include "int.h" + +class Idm : public QObject +{ + Q_OBJECT + +public: + + enum Flags + { + FIXED_KEY_LEN = 1 + }; + + static const QByteArray TAG; + static const int MAX_MAJOR; + static const int MAX_MINOR; + static int CURRENT_MAJOR; + static int CURRENT_MINOR; + static int DEFAULT_INT_SIZE; + static int DEFAULT_FLAGS; + static int DEFAULT_KEY_LEN; + + static bool verifyExHeader(const QByteArray &data, int major, int minor); + static bool rdExHeader(const QByteArray &data, int &major, int &minor, int &intSz, int &flgs, int &keyLen); + static QByteArray buildHeader(int major, int minor, int intSz, int flgs, int keyLen); + + explicit Idm(const QByteArray &data, QObject *parent = 0); + explicit Idm(QObject *parent = 0); + + void setVersion(int major, int minor); + void setFlags(int flgs); + void setIntSize(int bytes); + void wrHeader(); + void setKeyLen(int len); + void loadData(const QByteArray &data); + bool insert(const QByteArray &key, const QByteArray &data); + bool insert(quint64 key, quint64 num); + bool insert(quint64 key, const QByteArray &data); + bool remove(const QByteArray &key); + bool remove(quint64 key); + bool rdHeader(); + bool rdExHeader(const QByteArray &data); + quint64 intValue(quint64 key); + quint64 intValue(const QByteArray &key); + QByteArray value(const QByteArray &key); + QByteArray value(quint64 key); + QByteArray &getBuff(); + +private: + + enum Operation + { + RELOAD, + GET_BUFF, + RD_HEADER, + WR_HEADER, + SET_KEY_LEN, + SET_FLAGS, + SET_INT_SIZE, + SET_VERSION, + RD_VALUE, + WR_VALUE, + RM_VALUE + }; + + QByteArray buffPtr; + int header; + int intSize; + int keyLen; + int flags; + + void op(Operation opr, + const QByteArray &inA = QByteArray(), + const QByteArray &inB = QByteArray(), + QByteArray *out = 0, + int intA = 0, + int intB = 0, + bool *ok = 0); + + void init(); + bool findKey(int &addr, int &dataLen, const QByteArray &key); + QByteArray rdData(int, int, bool *ok = 0); +}; + +#endif // IDM_H diff --git a/io/int.cpp b/io/int.cpp new file mode 100644 index 0000000..51b2f12 --- /dev/null +++ b/io/int.cpp @@ -0,0 +1,58 @@ +#include "int.h" + +quint64 rdInt(const QByteArray &data) +{ + quint64 out = 0; + quint64 outBit = 1; + + for (int i = data.size() - 1; i >= 0; --i) + { + int byte = data[i]; + + for (int inBit = 1; inBit <= 128; inBit *= 2, outBit *= 2) + { + if ((byte & inBit) != 0) out |= outBit; + } + } + + return out; +} + +float rdFloat(const QByteArray &data) +{ + return data.toFloat(); +} + +QByteArray wrInt(quint64 value, int intSize) +{ + QByteArray out; + quint64 inBit = 1; + + while (value != 0) + { + int byte = 0; + + for (int outBit = 1; outBit <= 128; outBit *= 2, inBit *= 2) + { + if (value & inBit) + { + byte |= outBit; + value ^= inBit; + } + } + + out.insert(0, byte); + } + + return out.rightJustified(intSize, 0); +} + +QByteArray wrInt(quint64 value) +{ + return wrInt(value, INT_SIZE); +} + +QByteArray wrFloat(float value) +{ + return QByteArray().setNum(value); +} diff --git a/io/int.h b/io/int.h new file mode 100644 index 0000000..c18ad02 --- /dev/null +++ b/io/int.h @@ -0,0 +1,15 @@ +#ifndef INT_H +#define INT_H + +#include +#include + +#define INT_SIZE 6 + +float rdFloat(const QByteArray &data); +quint64 rdInt(const QByteArray &data); +QByteArray wrInt(quint64 value); +QByteArray wrInt(quint64 value, int); +QByteArray wrFloat(float value); + +#endif // INT_H diff --git a/logo.rc b/logo.rc new file mode 100644 index 0000000..725c431 --- /dev/null +++ b/logo.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "app_logo.ico" diff --git a/svg/eject.svg b/svg/eject.svg deleted file mode 100644 index 55b047a..0000000 --- a/svg/eject.svg +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/svg/list.svg b/svg/list.svg deleted file mode 100644 index f35883c..0000000 --- a/svg/list.svg +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/svg/menu.svg b/svg/menu.svg new file mode 100644 index 0000000..f435b85 --- /dev/null +++ b/svg/menu.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/svg/open.svg b/svg/open.svg new file mode 100644 index 0000000..9406a3a --- /dev/null +++ b/svg/open.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/svg/pause.svg b/svg/pause.svg index fad0570..e7b21b1 100644 --- a/svg/pause.svg +++ b/svg/pause.svg @@ -1,41 +1,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/svg/play-button.svg b/svg/play-button.svg deleted file mode 100644 index 32fad65..0000000 --- a/svg/play-button.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/svg/play.svg b/svg/play.svg new file mode 100644 index 0000000..bbdf169 --- /dev/null +++ b/svg/play.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file