diff --git a/corax/CMakeLists.txt b/corax/CMakeLists.txt index b0c6a34..a257c53 100644 --- a/corax/CMakeLists.txt +++ b/corax/CMakeLists.txt @@ -23,6 +23,7 @@ set(SOURCES ) add_executable(corax ${HEADERS} ${SOURCES}) +add_subdirectory(models) target_link_libraries(corax Qt5::Core) target_link_libraries(corax Qt5::Network) @@ -31,10 +32,10 @@ target_link_libraries(corax wSocket) target_link_libraries(corax wDispatcher) target_link_libraries(corax utils) target_link_libraries(corax wModel) -target_link_libraries(corax wController) -target_link_libraries(corax wServerUtils) target_link_libraries(corax wDatabase) target_link_libraries(corax tag) target_link_libraries(corax tools) +target_link_libraries(corax coraxModels) +target_link_libraries(corax wServerUtils) install(TARGETS corax RUNTIME DESTINATION bin) diff --git a/corax/corax.cpp b/corax/corax.cpp index 053b391..1365ba3 100644 --- a/corax/corax.cpp +++ b/corax/corax.cpp @@ -9,6 +9,7 @@ Corax* Corax::corax = 0; Corax::Corax(QObject *parent): QObject(parent), + started(false), server(new W::Server(W::String(u"Corax"), this)), logger(new W::Logger()), parentReporter(new W::ParentReporter()), @@ -17,7 +18,8 @@ Corax::Corax(QObject *parent): connector(0), dispatcher(new W::Dispatcher()), caches(), - parsers() + parsers(), + players() { if (corax != 0) { @@ -41,7 +43,8 @@ Corax::Corax(QObject *parent): attributes->addAttribute(W::String(u"connectionsCount"), new M::String(W::String(u"0"), W::Address({u"attributes", u"connectionCount"}))); attributes->addAttribute(W::String(u"name"), new M::String(W::String(u"Corax"), W::Address({u"attributes", u"name"}))); - attributes->addAttribute(W::String(u"version"), new M::String(W::String(u"0.0.2"), W::Address({u"attributes", u"version"}))); + attributes->addAttribute(W::String(u"version"), new M::String(W::String(u"0.0.3"), W::Address({u"attributes", u"version"}))); + attributes->addAttribute(W::String(u"players"), new M::String(W::String(u"0"), W::Address({u"attributes", u"players"}))); createCaches(); createHandlers(); @@ -49,20 +52,27 @@ Corax::Corax(QObject *parent): Corax::~Corax() { - std::map::iterator pbeg = parsers.begin(); - std::map::iterator pend = parsers.end(); + std::map::const_iterator pbeg = parsers.begin(); + std::map::const_iterator pend = parsers.end(); for (; pbeg != pend; ++pbeg) { delete pbeg->second; } - std::map::iterator beg = caches.begin(); - std::map::iterator end = caches.end(); + std::map::const_iterator beg = caches.begin(); + std::map::const_iterator end = caches.end(); for (; beg != end; ++beg) { delete beg->second; } + std::map::const_iterator plit = players.begin(); + std::map::const_iterator plend = players.end(); + + for (; plit != plend; ++plit) { + delete plit->second; + } + delete connector; dispatcher->unregisterDefaultHandler(logger); @@ -83,6 +93,9 @@ void Corax::onConnectionCountChanged(uint64_t count) void Corax::start() { + if (started) { + throw 3; + } std::map::iterator beg = caches.begin(); std::map::iterator end = caches.end(); @@ -90,11 +103,18 @@ void Corax::start() server->listen(8080); cout << "Registering models..." << endl; - attributes->registerModel(dispatcher, server); - commands->registerModel(dispatcher, server); + attributes->getRegistered(connector); + commands->getRegistered(connector); for (; beg != end; ++beg) { - beg->second->registerModel(dispatcher, server); + beg->second->getRegistered(connector); + } + + std::map::const_iterator plit = players.begin(); + std::map::const_iterator plend = players.end(); + + for (; plit != plend; ++plit) { + plit->second->getRegistered(connector);; } cout << "Opening caches..." << endl; @@ -105,24 +125,40 @@ void Corax::start() } commands->enableCommand(W::String(u"clearCache"), true); + commands->enableCommand(W::String(u"givePlayer"), true); + started = true; cout << "Corax is ready" << endl; } void Corax::stop() { + if (!started) { + throw 3; + } + std::map::iterator beg = caches.begin(); std::map::iterator end = caches.end(); cout << "Stopping corax..." << endl; - commands->unregisterModel(); - attributes->unregisterModel(); + commands->getUnregistered(); + attributes->getUnregistered(); for (; beg != end; ++beg) { - beg->second->unregisterModel(); + beg->second->getUnregistered(); + } + + std::map::const_iterator plit = players.begin(); + std::map::const_iterator plend = players.end(); + + for (; plit != plend; ++plit) { + plit->second->getUnregistered(); } server->stop(); + + started = false; + cout << "Corax is stopped" << endl; } void Corax::onModelServiceMessage(const QString& msg) @@ -169,10 +205,10 @@ void Corax::h_parseDirectory(const W::Event& ev) if (itr != parsers.end()) { cout << "directory " << path.toString() << " is already being parsed" << endl; } else { - const W::Socket& socket = connector->getNodeSocket(W::String(u"Perturabo")); + const W::Socket* socket = connector->getNodeSocket(W::String(u"Perturabo")); ResourceCache* music = caches.at(W::String(u"music")); ResourceCache* images = caches.at(W::String(u"images")); - Parser* parser = new Parser(&socket, dispatcher, music, images); + Parser* parser = new Parser(socket, dispatcher, music, images); parsers.insert(std::make_pair(path, parser)); connect(parser, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); @@ -202,6 +238,9 @@ void Corax::createHandlers() W::Vocabulary parseArgs; parseArgs.insert(u"path", W::Uint64(W::Object::string)); commands->addCommand(W::String(u"parseDirectory"), parseDirectory, parseArgs); + + W::Handler* givePlayer = W::Handler::create(W::Address({u"management", u"givePlayer"}), this, &Corax::_h_givePlayer); + commands->addCommand(W::String(u"givePlayer"), givePlayer, W::Vocabulary()); } void Corax::onParserDone(const W::String& path) @@ -234,3 +273,28 @@ void Corax::onNodeDisconnected(const W::String& name) commands->enableCommand(W::String(u"parseDirectory"), false); } } + +void Corax::h_givePlayer(const W::Event& ev) +{ + W::String num(std::to_string(players.size())); + W::Address addr{u"players", num}; + M::Player* pl = new M::Player(addr); + connect(pl, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); + players.insert(std::make_pair(addr, pl)); + + attributes->setAttribute(W::String(u"players"), W::String(std::to_string(players.size()))); + pl->getRegistered(connector); //it's a handler, so I assume corax is started here; + + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"address", addr); + + const W::Vocabulary& svc = static_cast(ev.getData()); + const W::Address& source = static_cast(svc.at(u"source")); + uint64_t id = ev.getSenderId(); + vc->insert(u"source", W::Address{u"management"}); //TODO think about it, may be Corax should be a model? + + W::Event res(source + W::Address{u"getPlayer"}, vc); + res.setSenderId(id); + connector->getConnection(id)->send(res); +} + diff --git a/corax/corax.h b/corax/corax.h index 179df14..aa5cf9c 100644 --- a/corax/corax.h +++ b/corax/corax.h @@ -30,6 +30,7 @@ #include #include "tools/parser.h" +#include "models/player.h" class Corax: public QObject { @@ -42,6 +43,7 @@ public: static Corax* corax; private: + bool started; W::Server *server; W::Logger *logger; W::ParentReporter* parentReporter; @@ -53,9 +55,11 @@ private: std::map caches; std::map parsers; + std::map players; handler(clearCache); handler(parseDirectory); + handler(givePlayer); public slots: void start(); diff --git a/corax/models/CMakeLists.txt b/corax/models/CMakeLists.txt new file mode 100644 index 0000000..fe7c92e --- /dev/null +++ b/corax/models/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 2.8.12) +project(coraxModels) + +find_package(Qt5Core REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + player.h + proxysong.h +) + +set(SOURCES + player.cpp + proxysong.cpp +) + +add_library(coraxModels STATIC ${HEADERS} ${SOURCES}) + +target_link_libraries(coraxModels wModel) diff --git a/corax/models/player.cpp b/corax/models/player.cpp new file mode 100644 index 0000000..0b92806 --- /dev/null +++ b/corax/models/player.cpp @@ -0,0 +1,313 @@ +#include "player.h" + +M::Player::Player(const W::Address& address, QObject* parent): + M::Model(address, parent), + controls(), + views(), + playPauseBtn(new M::Button(address + W::Address{u"play"})), + nextBtn(new M::Button(address + W::Address{u"next"})), + prevBtn(new M::Button(address + W::Address{u"prev"})), + _queueView(new M::List(address + W::Address{u"queueView"})), + _queue(), + current(0), + counter(0), + currentIndex(0), + mode(playBack), + playing(false), + scheduledToplay(false) +{ + W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &M::Player::_h_get); + W::Handler* hqueue = W::Handler::create(address + W::Address({u"queue"}), this, &M::Player::_h_queue); + W::Handler* hplay = W::Handler::create(address + W::Address({u"play"}), this, &M::Player::_h_play); + addHandler(get); + addHandler(hqueue); + addHandler(hplay); + + playPauseBtn->setLabel(W::String(u"Play")); + playPauseBtn->setEnabled(false); + connect(playPauseBtn, SIGNAL(activated()), this, SLOT(onPlayPauseBtn())); + + nextBtn->setLabel(W::String(u"Next")); + nextBtn->setEnabled(false); + connect(nextBtn, SIGNAL(activated()), this, SLOT(onNextBtn())); + + prevBtn->setLabel(W::String(u"Prev")); + prevBtn->setEnabled(false); + connect(prevBtn, SIGNAL(activated()), this, SLOT(onPrevBtn())); + + addModel(playPauseBtn); + addModel(nextBtn); + addModel(prevBtn); + addModel(_queueView); + + controls.insert(std::make_pair(playPause, playPauseBtn->getAddress())); + controls.insert(std::make_pair(next, nextBtn->getAddress())); + controls.insert(std::make_pair(prev, prevBtn->getAddress())); + views.insert(std::make_pair(queue, _queueView->getAddress())); +} + +M::Player::~Player() +{ + +} + +void M::Player::set(const W::Object& value) +{ + throw 14; //what do you expect here? not implemented, and not sure it ever would be +} + +void M::Player::set(W::Object* value) +{ + set(*value); +} + +M::Model::ModelType M::Player::getType() const +{ + return M::Model::player; +} + +void M::Player::h_subscribe(const W::Event& ev) +{ + M::Model::h_subscribe(ev); + + h_get(ev); +} + +void M::Player::h_get(const W::Event& ev) +{ + W::Vector* ctrls = new W::Vector(); + ItemMap::const_iterator citr = controls.begin(); + ItemMap::const_iterator cend = controls.end(); + + for (; citr != cend; ++citr) { + W::Vocabulary* cvc = new W::Vocabulary(); + cvc->insert(u"type", new W::Uint64(citr->first)); + cvc->insert(u"address", citr->second); + + ctrls->push(cvc); + } + + W::Vector* vws = new W::Vector(); + ItemMap::const_iterator vitr = views.begin(); + ItemMap::const_iterator vend = views.end(); + + for (; vitr != vend; ++vitr) { + W::Vocabulary* vvc = new W::Vocabulary(); + vvc->insert(u"type", new W::Uint64(vitr->first)); + vvc->insert(u"address", vitr->second); + + vws->push(vvc); + } + + W::Vocabulary* res = new W::Vocabulary(); + + res->insert(u"controls", ctrls); + res->insert(u"views", vws); + res->insert(u"mode", new W::Uint64(mode)); + + response(res, W::Address({u"get"}), ev); +} + +void M::Player::onPlayPauseBtn() +{ + if (playing) { + pause(); + } else { + play(); + } +} + +void M::Player::h_queue(const W::Event& ev) +{ + const W::Vocabulary& data = static_cast(ev.getData()); + + const W::Uint64& id = static_cast(data.at(u"id")); + ProxySong* song = new ProxySong(id, address + W::Address{W::String(W::Uint64(counter++).toString())}); + addModel(song); + _queue.push_back(song); + _queueView->push(song->getAddress()); + + if (current == 0) { + setActive(song); + } + + if (currentIndex + 1 < _queue.size()) { + nextBtn->setEnabled(true); + } +} + +void M::Player::play() +{ + if (!playing) { + playPauseBtn->setLabel(W::String(u"Pause")); + playing = true; + + switch (mode) { + case playBack: + if (current == 0) { + scheduledToplay = true; + } else { + if (current->isReady()) { + scheduledToplay = false; + broadcast(new W::Vocabulary(), W::Address{u"play"}); + break; + } else { + scheduledToplay = true; + } + } + } + } +} + +void M::Player::pause() +{ + if (playing) { + playPauseBtn->setLabel(W::String(u"Play")); + playing = false; + + switch (mode) { + case playBack: + scheduledToplay = false; + broadcast(new W::Vocabulary(), W::Address{u"pause"}); + break; + } + } +} + +void M::Player::onSongReady() +{ + emit serviceMessage("Song is ready"); + playPauseBtn->setEnabled(true); + if (scheduledToplay) { + scheduledToplay = false; + if (playing) { + scheduledToplay = false; + broadcast(new W::Vocabulary(), W::Address{u"play"}); + } else { + play(); + } + } +} + +void M::Player::onSongNotReady() +{ + playPauseBtn->setEnabled(false); + emit serviceMessage("Something happend to the current song, not sure yet what to do"); +} + +void M::Player::onNextBtn() +{ + if (currentIndex + 1 < _queue.size()) { + if (playing) { + pause(); + scheduledToplay = true; + } + setActive(currentIndex + 1); + } +} + +void M::Player::onPrevBtn() +{ + if (currentIndex > 0) { + if (playing) { + pause(); + scheduledToplay = true; + } + setActive(currentIndex - 1); + } +} + +void M::Player::setActive(ProxySong* song) +{ + if (current == song) { + return; + } + + bool found = false; + int index; + for (index = 0; index < _queue.size(); ++index) { + if (_queue.at(index) == song) { + found = true; + break; + } + } + + if (found) { + setActive(index); + } else { + emit serviceMessage("An attempt to set active a song which is no in the queue, not supposed to happen"); + return; + } +} + +void M::Player::setActive(uint64_t index) +{ + if (index >= _queue.size()) { + emit serviceMessage("An attempt to set active a song which is no in the queue, not supposed to happen"); + return; + } + + ProxySong* song = _queue.at(index); + currentIndex = index; + if (currentIndex + 1 < _queue.size()) { + nextBtn->setEnabled(true); + } else { + nextBtn->setEnabled(false); + } + + if (currentIndex > 0) { + prevBtn->setEnabled(true); + } else { + prevBtn->setEnabled(false); + } + + W::Vocabulary* res = new W::Vocabulary(); + W::Vector* add = new W::Vector(); + W::Vector* remove = new W::Vector(); + if (current != 0) { + disconnect(current, SIGNAL(ready()), this, SLOT(onSongReady())); + disconnect(current, SIGNAL(notReady()), this, SLOT(onSongNotReady())); + remove->push(new W::Uint64(currentPlayback)); + } + current = song; + connect(song, SIGNAL(ready()), this, SLOT(onSongReady())); + connect(song, SIGNAL(notReady()), this, SLOT(onSongNotReady())); + views.insert(std::make_pair(currentPlayback, song->getAddress())); + W::Vocabulary* avc = new W::Vocabulary(); + avc->insert(u"type", new W::Uint64(currentPlayback)); + avc->insert(u"address", song->getAddress()); + + add->push(avc); + + res->insert(u"add", add); + res->insert(u"remove", remove); + + + broadcast(res, W::Address{u"viewsChange"}); + if (song->isReady()) { + playPauseBtn->setEnabled(true); + if (scheduledToplay) { + play(); + } + } else { + playPauseBtn->setEnabled(false); + } +} + +void M::Player::h_play(const W::Event& ev) +{ + const W::Vocabulary& data = static_cast(ev.getData()); + + const W::Uint64& id = static_cast(data.at(u"id")); + ProxySong* song = new ProxySong(id, address + W::Address{W::String(W::Uint64(counter++).toString())}); + addModel(song); + _queue.push_back(song); + _queueView->push(song->getAddress()); + + scheduledToplay = true; + setActive(song); + + if (currentIndex + 1 < _queue.size()) { + nextBtn->setEnabled(true); + } +} diff --git a/corax/models/player.h b/corax/models/player.h new file mode 100644 index 0000000..dfa016c --- /dev/null +++ b/corax/models/player.h @@ -0,0 +1,85 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "proxysong.h" +/** + * @todo write docs + */ +namespace M { + class Player : public M::Model { + Q_OBJECT + public: + Player(const W::Address& address, QObject* parent = 0); + ~Player(); + + void set(const W::Object & value) override; + void set(W::Object * value) override; + M::Model::ModelType getType() const override; + + enum ItemType { + playPause, + currentPlayback, + queue, + picture, + prev, + next + }; + + enum Mode { + playBack + }; + + void play(); + void pause(); + + protected: + void h_subscribe(const W::Event & ev) override; + + handler(get); + handler(queue); + handler(play); + + private: + typedef std::map ItemMap; + typedef std::deque Queue; + + ItemMap controls; + ItemMap views; + M::Button* playPauseBtn; + M::Button* nextBtn; + M::Button* prevBtn; + M::List* _queueView; + Queue _queue; + ProxySong* current; + uint64_t counter; + uint64_t currentIndex; + Mode mode; + bool playing; + bool scheduledToplay; + + void setActive(ProxySong* song); + void setActive(uint64_t index); + + private slots: + void onPlayPauseBtn(); + void onNextBtn(); + void onPrevBtn(); + void onSongReady(); + void onSongNotReady(); + + }; +} + +#endif // PLAYER_H diff --git a/corax/models/proxysong.cpp b/corax/models/proxysong.cpp new file mode 100644 index 0000000..d8d1377 --- /dev/null +++ b/corax/models/proxysong.cpp @@ -0,0 +1,138 @@ +#include "proxysong.h" + +ProxySong::ProxySong(const W::Uint64& p_id, const W::Address& p_address, QObject* parent): + M::Vocabulary(p_address, parent), + songCtrl(new C::Vocabulary(W::Address{u"songs", W::String(p_id.toString())})), + albumCtrl(0), + artistCtrl(0), + fileId(0), + _ready(false) +{ + addController(songCtrl, W::String(u"Perturabo")); + connect(songCtrl, SIGNAL(newElement(const W::String&, const W::Object&)), SLOT(onSongNewElement(const W::String&, const W::Object&))); + connect(songCtrl, SIGNAL(removeElement(const W::String&)), SLOT(onSongRemoveElement(const W::String&))); + + insert(W::String(u"id"), p_id); + insert(W::String(u"artist"), new W::String(u"undefined")); + insert(W::String(u"album"), new W::String(u"undefined")); + insert(W::String(u"song"), new W::String(u"undefined")); + insert(W::String(u"image"), new W::Uint64(0)); + insert(W::String(u"audio"), new W::Uint64(0)); +} + +bool ProxySong::isReady() const +{ + return _ready; +} + +ProxySong::~ProxySong() +{ +} + +void ProxySong::onSongNewElement(const W::String& key, const W::Object& element) +{ + if (key == u"name") { + insert(W::String(u"song"), element); + } else if (key == u"audio") { + if (_ready) { + _ready = false; + emit notReady(); + } + + fileId = static_cast(element); + insert(key, element); + _ready = true; + emit ready(); + } else if (key == u"artist") { + if (artistCtrl != 0) { + removeController(artistCtrl); + disconnect(artistCtrl, SIGNAL(newElement(const W::String&, const W::Object&)), this, SLOT(onArtistNewElement(const W::String&, const W::Object&))); + disconnect(artistCtrl, SIGNAL(removeElement(const W::String&)), this, SLOT(onAtristRemoveElement(const W::String&))); + artistCtrl->deleteLater(); + } + const W::Uint64& aid = static_cast(element); + artistCtrl = new C::Vocabulary(W::Address{u"artists", W::String(aid.toString())}); + addController(artistCtrl, W::String(u"Perturabo")); + connect(artistCtrl, SIGNAL(newElement(const W::String&, const W::Object&)), SLOT(onArtistNewElement(const W::String&, const W::Object&))); + connect(artistCtrl, SIGNAL(removeElement(const W::String&)), SLOT(onAtristRemoveElement(const W::String&))); + } else if (key == u"album") { + if (albumCtrl != 0) { + removeController(albumCtrl); + disconnect(albumCtrl, SIGNAL(newElement(const W::String&, const W::Object&)), this, SLOT(onAlbumNewElement(const W::String&, const W::Object&))); + disconnect(albumCtrl, SIGNAL(removeElement(const W::String&)), this, SLOT(onAlbumRemoveElement(const W::String&))); + albumCtrl->deleteLater(); + } + const W::Uint64& aid = static_cast(element); + albumCtrl = new C::Vocabulary(W::Address{u"albums", W::String(aid.toString())}); + addController(albumCtrl, W::String(u"Perturabo")); + connect(albumCtrl, SIGNAL(newElement(const W::String&, const W::Object&)), SLOT(onAlbumNewElement(const W::String&, const W::Object&))); + connect(albumCtrl, SIGNAL(removeElement(const W::String&)), SLOT(onAlbumRemoveElement(const W::String&))); + } +} + +void ProxySong::onSongRemoveElement(const W::String& key) +{ + if (key == u"name") { + insert(key, new W::String(u"undefined")); + } else if (key == u"audio") { + insert(key, new W::Uint64(0)); + if (_ready) { + _ready = false; + fileId = W::Uint64(0); + emit notReady(); + } + } else if (key == u"artist") { + if (artistCtrl != 0) { + removeController(artistCtrl); + disconnect(artistCtrl, SIGNAL(newElement(const W::String&, const W::Object&)), this, SLOT(onArtistNewElement(const W::String&, const W::Object&))); + disconnect(artistCtrl, SIGNAL(removeElement(const W::String&)), this, SLOT(onAtristRemoveElement(const W::String&))); + artistCtrl->deleteLater(); + artistCtrl = 0; + } + } else if (key == u"album") { + if (albumCtrl != 0) { + removeController(albumCtrl); + disconnect(albumCtrl, SIGNAL(newElement(const W::String&, const W::Object&)), this, SLOT(onAlbumNewElement(const W::String&, const W::Object&))); + disconnect(albumCtrl, SIGNAL(removeElement(const W::String&)), this, SLOT(onAlbumRemoveElement(const W::String&))); + albumCtrl->deleteLater(); + albumCtrl = 0; + } + } +} + +void ProxySong::onAlbumNewElement(const W::String& key, const W::Object& element) +{ + if (key == u"name") { + insert(W::String(u"album"), element); + } else if (key == u"image") { + insert(key, element); + } +} + +void ProxySong::onAlbumRemoveElement(const W::String& key) +{ + if (key == u"name") { + insert(W::String(u"album"), new W::String(u"undefined")); + } else if (key == u"image") { + insert(key, new W::Uint64(0)); + } +} + +void ProxySong::onArtistNewElement(const W::String& key, const W::Object& element) +{ + if (key == u"name") { + insert(W::String(u"artist"), element); + } +} + +void ProxySong::onArtistRemoveElement(const W::String& key) +{ + if (key == u"name") { + insert(W::String(u"artist"), new W::String(u"undefined")); + } +} + +const W::Uint64 & ProxySong::getFileId() const +{ + return fileId; +} diff --git a/corax/models/proxysong.h b/corax/models/proxysong.h new file mode 100644 index 0000000..48aba44 --- /dev/null +++ b/corax/models/proxysong.h @@ -0,0 +1,47 @@ +#ifndef PROXYSONG_H +#define PROXYSONG_H + +/** + * @todo write docs + */ +#include +#include + +#include + +#include + +class ProxySong : public M::Vocabulary { + Q_OBJECT +public: + ProxySong(const W::Uint64& p_id, const W::Address& p_address, QObject* parent = 0); + ~ProxySong(); + + const W::Uint64& getFileId() const; + bool isReady() const; + +signals: + void ready(); + void notReady(); + +private: + C::Vocabulary* songCtrl; + C::Vocabulary* albumCtrl; + C::Vocabulary* artistCtrl; + + W::Uint64 fileId; + bool _ready; + +private slots: + void onSongNewElement(const W::String& key, const W::Object& element); + void onSongRemoveElement(const W::String& key); + + void onAlbumNewElement(const W::String& key, const W::Object& element); + void onAlbumRemoveElement(const W::String& key); + + void onArtistNewElement(const W::String& key, const W::Object& element); + void onArtistRemoveElement(const W::String& key); + +}; + +#endif // PROXYSONG_H diff --git a/lib/wController/controller.cpp b/lib/wController/controller.cpp index 5fe4572..6ac7c61 100644 --- a/lib/wController/controller.cpp +++ b/lib/wController/controller.cpp @@ -276,3 +276,8 @@ bool C::Controller::isSubscribed() { return subscribed; } + +const W::Address & C::Controller::getAddress() const +{ + return address; +} diff --git a/lib/wController/controller.h b/lib/wController/controller.h index 1749093..7372a2d 100644 --- a/lib/wController/controller.h +++ b/lib/wController/controller.h @@ -40,6 +40,7 @@ namespace C { void subscribe(); void unsubscribe(); bool isSubscribed(); + const W::Address& getAddress() const; void removeHandler(W::Handler* handler); void removeController(C::Controller* ctrl); diff --git a/lib/wDatabase/database.cpp b/lib/wDatabase/database.cpp index 0a230e8..05d0307 100644 --- a/lib/wDatabase/database.cpp +++ b/lib/wDatabase/database.cpp @@ -175,6 +175,8 @@ void Database::clear() lmdb::txn transaction = lmdb::txn::begin(environment); dbi.drop(transaction); transaction.commit(); + + elements.clear(); } void Database::addModel(M::Model* model) diff --git a/lib/wDatabase/resourcecache.cpp b/lib/wDatabase/resourcecache.cpp index 64d383b..14014d6 100644 --- a/lib/wDatabase/resourcecache.cpp +++ b/lib/wDatabase/resourcecache.cpp @@ -215,7 +215,7 @@ void ResourceCache::h_subscribeMember(const W::Event& ev) M::File* modelRecord = M::File::create(readFile(*record), address + lastHops >> 1); delete record; addModel(modelRecord); - passToHandler(ev); + passToLocalHandler(ev); } catch (int err) { if (err == 3) { emit serviceMessage(QString("An attempt to create and subscribe record model in resourcecache, but it is not found. Event: ") + ev.toString().c_str()); diff --git a/lib/wModel/CMakeLists.txt b/lib/wModel/CMakeLists.txt index cd37fb7..cc83c8f 100644 --- a/lib/wModel/CMakeLists.txt +++ b/lib/wModel/CMakeLists.txt @@ -14,7 +14,9 @@ set(HEADERS attributes.h icatalogue.h catalogue.h + button.h file/file.h + file/audio.h ) set(SOURCES @@ -25,12 +27,15 @@ set(SOURCES attributes.cpp icatalogue.cpp catalogue.cpp + button.cpp file/file.cpp + file/audio.cpp ) add_library(wModel STATIC ${HEADERS} ${SOURCES}) target_link_libraries(wModel Qt5::Core) -target_link_libraries(wModel wSocket) -target_link_libraries(wModel wDispatcher) +target_link_libraries(wModel wServerUtils) target_link_libraries(wModel wType) +target_link_libraries(wModel wController) +target_link_libraries(wModel mad) diff --git a/lib/wModel/button.cpp b/lib/wModel/button.cpp new file mode 100644 index 0000000..9f22ea4 --- /dev/null +++ b/lib/wModel/button.cpp @@ -0,0 +1,121 @@ +#include "button.h" + +M::Button::Button(const W::Address& address, QObject* parent): + M::Model(address, parent), + enabled(true), + hasImage(false), + hasLabel(false), + imageName(0), + label(0) +{ + W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &M::Button::_h_get); + W::Handler* activate = W::Handler::create(address + W::Address({u"activate"}), this, &M::Button::_h_activate); + + addHandler(get); + addHandler(activate); +} + +M::Button::~Button() +{ + if (hasImage) { + delete imageName; + } +} + +void M::Button::setImage(const W::String& p_image) +{ + + if (hasImage) { + if (*imageName != p_image) { + imageName = static_cast(p_image.copy()); + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"image", p_image); + broadcast(vc, W::Address{u"changeImage"}); + } + } + } else { + imageName = static_cast(p_image.copy()); + hasImage = true; + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"image", p_image); + broadcast(vc, W::Address{u"setImage"}); + } + } + hasImage = true; +} + +void M::Button::setEnabled(bool p_enabled) +{ + if (enabled != p_enabled) { + enabled = p_enabled; + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"enabled", new W::Boolean(enabled)); + broadcast(vc, W::Address{u"setEnabled"}); + } + } +} + +void M::Button::setLabel(const W::String& p_label) +{ + if (hasLabel) { + label->set(p_label); + } else { + label = new M::String(p_label, address + W::Address{u"label"}); + addModel(label); + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"hasLabel", new W::Boolean(true)); + vc->insert(u"label", label->getAddress()); + broadcast(vc, W::Address{u"setLabel"}); + } + hasLabel = true; + } +} + +void M::Button::h_subscribe(const W::Event& ev) +{ + M::Model::h_subscribe(ev); + + h_get(ev); +} + +void M::Button::h_get(const W::Event& ev) +{ + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"hasImage", new W::Boolean(hasImage)); + if (hasImage) { + vc->insert(u"image", imageName->copy()); + } + vc->insert(u"hasLabel", new W::Boolean(hasLabel)); + if (hasLabel) { + vc->insert(u"label", label->getAddress()); + } + vc->insert(u"enabled", new W::Boolean(enabled)); + + response(vc, W::Address({u"get"}), ev); +} + +void M::Button::h_activate(const W::Event& ev) +{ + if (enabled) { + emit activated(); + } +} + +M::Model::ModelType M::Button::getType() const +{ + return M::Model::button; +} + +void M::Button::set(const W::Object& value) +{ + throw 14; //what do you expect here? not implemented, and not sure it ever would be +} + +void M::Button::set(W::Object* value) +{ + set(*value); +} diff --git a/lib/wModel/button.h b/lib/wModel/button.h new file mode 100644 index 0000000..d73046d --- /dev/null +++ b/lib/wModel/button.h @@ -0,0 +1,52 @@ +#ifndef BUTTON_H +#define BUTTON_H + +#include +#include + +#include + +#include +#include +#include +#include + +/** + * @todo write docs + */ +namespace M { + class Button : public Model { + Q_OBJECT + public: + Button(const W::Address& address, QObject* parent = 0); + ~Button(); + + void setImage(const W::String& p_image); + void setLabel(const W::String& p_label); + void setEnabled(bool p_enabled); + + M::Model::ModelType getType() const override; + void set(const W::Object & value) override; + void set(W::Object * value) override; + + signals: + void activated() const; + + protected: + void h_subscribe(const W::Event & ev) override; + + handler(get); + handler(activate); + + protected: + bool enabled; + bool hasImage; + bool hasLabel; + + W::String* imageName; + M::String* label; + }; +} + + +#endif // BUTTON_H diff --git a/lib/wModel/file/audio.cpp b/lib/wModel/file/audio.cpp new file mode 100644 index 0000000..d018231 --- /dev/null +++ b/lib/wModel/file/audio.cpp @@ -0,0 +1,45 @@ +#include "audio.h" + +#include + +M::Audio::Audio(W::Blob* p_file, const W::Address& addr, QObject* parent): + File(p_file, addr, parent) +{ +} + +M::Audio::~Audio() +{} + +M::Model::ModelType M::Audio::getType() const +{ + return type; +} + +void M::Audio::initAdditional(const W::String& p_mime) +{ + File::initAdditional(p_mime); //TODO handle other than mp3 formats + + mad_stream stream; + mad_header header; + mad_stream_init(&stream); + mad_header_init(&header); + + mad_stream_buffer(&stream, file->uchar(), file->size()); + + uint64_t length = 0; + uint64_t tBits = 0; + uint64_t amount = 0; + while(stream.error != MAD_ERROR_BUFLEN) { //TODO handle other errors; + + int success = mad_header_decode(&header, &stream); + if (success == 0) { + + amount++; + length += header.duration.seconds * MAD_TIMER_RESOLUTION + header.duration.fraction; + tBits += header.bitrate; + } + } + + additional.insert(u"duration", new W::Uint64(length * 1000 / MAD_TIMER_RESOLUTION)); + additional.insert(u"bitrate", new W::Uint64(tBits / amount)); +} diff --git a/lib/wModel/file/audio.h b/lib/wModel/file/audio.h new file mode 100644 index 0000000..805e0eb --- /dev/null +++ b/lib/wModel/file/audio.h @@ -0,0 +1,31 @@ +#ifndef M_AUDIO_H +#define M_AUDIO_H + +#include + +#include +#include + +namespace M { + + /** + * @todo write docs + */ + class Audio : public File { + friend class File; + protected: + Audio(W::Blob* p_file, const W::Address& addr, QObject* parent = 0); + + public: + ~Audio(); + + M::Model::ModelType getType() const override; + static const M::Model::ModelType type = audio; + + protected: + void initAdditional(const W::String& p_mime) override; + }; + +} + +#endif // M_AUDIO_H diff --git a/lib/wModel/file/file.cpp b/lib/wModel/file/file.cpp index 083ec62..e28caf4 100644 --- a/lib/wModel/file/file.cpp +++ b/lib/wModel/file/file.cpp @@ -1,7 +1,13 @@ #include "file.h" #include +#include "audio.h" + QMimeDatabase M::File::mimeDB; +const std::map M::File::mimeMap = { + {"image/jpeg", M::Model::file}, + {"audio/mpeg", M::Model::audio} +}; M::File::File(W::Blob* p_file, const W::Address& addr, QObject* parent): M::Model(addr, parent), @@ -10,9 +16,11 @@ M::File::File(W::Blob* p_file, const W::Address& addr, QObject* parent): { W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &M::File::_h_get); W::Handler* getAdditional = W::Handler::create(address + W::Address({u"getAdditional"}), this, &M::File::_h_getAdditional); + W::Handler* getSlice = W::Handler::create(address + W::Address({u"getSlice"}), this, &M::File::_h_getSlice); addHandler(get); addHandler(getAdditional); + addHandler(getSlice); } M::File::~File() @@ -29,7 +37,7 @@ void M::File::initAdditional(const W::String& p_mime) { additional.clear(); - additional.insert(u"size", new W::Uint64(file->size())); + additional.insert(u"size", new W::Uint64(file->length())); additional.insert(u"mimeType", p_mime); } @@ -79,8 +87,42 @@ M::File * M::File::create(W::Blob* blob, const W::Address& addr, QObject* parent M::File* out; QMimeType mt = mimeDB.mimeTypeForData(blob->byteArray()); - out = new File(blob, addr, parent); - out->initAdditional(W::String(mt.name().toStdString())); + const QString& mime = mt.name(); + std::map::const_iterator itr = mimeMap.find(mime); + + M::Model::ModelType modelType = M::Model::file; + if (itr != mimeMap.end()) { + modelType = itr->second; + } + + switch (modelType) { + case Model::audio: + out = new Audio(blob, addr, parent); + break; + default: + out = new File(blob, addr, parent); + break; + } + + out->initAdditional(W::String(mime.toStdString())); return out; } + +void M::File::h_getSlice(const W::Event& ev) +{ + const W::Vocabulary& vc = static_cast(ev.getData()); + const W::Uint64& begin = static_cast(vc.at(u"begin")); + const W::Uint64& size = static_cast(vc.at(u"size")); + + W::Vocabulary* evc = new W::Vocabulary(); + if (begin > file->length() || begin + size > file->length()) { + evc->insert(u"result", new W::Uint64(1)); + } else { + evc->insert(u"result", new W::Uint64(0)); + evc->insert(u"slice", file->slice(begin, size)); + } + + response(evc, W::Address{u"getSlice"}, ev); +} + diff --git a/lib/wModel/file/file.h b/lib/wModel/file/file.h index 7a3d79d..5eeab4e 100644 --- a/lib/wModel/file/file.h +++ b/lib/wModel/file/file.h @@ -32,12 +32,14 @@ namespace M { handler(get); handler(getAdditional); + handler(getSlice); protected: W::Vocabulary additional; W::Blob* file; static QMimeDatabase mimeDB; + static const std::map mimeMap; }; } diff --git a/lib/wModel/icatalogue.cpp b/lib/wModel/icatalogue.cpp index 3eb4401..2733c07 100644 --- a/lib/wModel/icatalogue.cpp +++ b/lib/wModel/icatalogue.cpp @@ -44,6 +44,15 @@ void M::ICatalogue::clear() broadcast(new W::Vocabulary(), W::Address{u"clear"}); } + + std::map::iterator aItr = activeChildren.begin(); + std::map::iterator aEnd = activeChildren.end(); + for (; aItr != aEnd; ++aItr) { + removeModel(aItr->second); + aItr->second->deleteLater(); + } + activeChildren.clear(); + emit countChange(0); } diff --git a/lib/wModel/model.cpp b/lib/wModel/model.cpp index d1168cd..dc1522e 100644 --- a/lib/wModel/model.cpp +++ b/lib/wModel/model.cpp @@ -5,12 +5,12 @@ M::Model::Model(const W::Address p_address, QObject* parent): address(p_address), registered(false), subscribers(new Map()), - dispatcher(0), - server(0), + connector(0), subscribersCount(0), handlers(new HList()), properties(new W::Vector()), - models(new MList()) + models(new MList()), + controllers(new Controllers()) { W::Handler* subscribe = W::Handler::create(address + W::Address({u"subscribe"}), this, &M::Model::_h_subscribe); W::Handler* unsubscribe = W::Handler::create(address + W::Address({u"unsubscribe"}), this, &M::Model::_h_unsubscribe); @@ -21,7 +21,7 @@ M::Model::Model(const W::Address p_address, QObject* parent): M::Model::~Model() { if (registered) { - unregisterModel(); + getUnregistered(); } MList::iterator itr = models->begin(); @@ -37,11 +37,19 @@ M::Model::~Model() for (; hItr != hEnd; ++hItr) { delete *hItr; } + + Controllers::iterator cItr = controllers->begin(); + Controllers::iterator cEnd = controllers->end(); + + for (; cItr != cEnd; ++cItr) { + delete cItr->first; + } delete subscribers; delete properties; delete handlers; delete models; + delete controllers; } void M::Model::addModel(M::Model* model) @@ -49,7 +57,7 @@ void M::Model::addModel(M::Model* model) models->push_back(model); connect(model, SIGNAL(serviceMessage(const QString&)), SIGNAL(serviceMessage(const QString&))); if (registered) { - model->registerModel(dispatcher, server); + model->getRegistered(connector); } } @@ -57,7 +65,7 @@ void M::Model::addHandler(W::Handler* handler) { handlers->push_back(handler); if (registered) { - dispatcher->registerHandler(handler); + connector->registerHandler(handler); } } @@ -83,21 +91,20 @@ W::Address M::Model::getAddress() const } -void M::Model::registerModel(W::Dispatcher* dp, W::Server* srv) +void M::Model::getRegistered(U::Connector* cn) { if (registered) { emit serviceMessage(QString("Model ") + address.toString().c_str() + " is already registered"); throw 1; } else { - dispatcher = dp; - server = srv; + connector = cn; MList::iterator itr = models->begin(); MList::iterator end = models->end(); for (; itr != end; ++itr) { M::Model* model = *itr; - model->registerModel(dispatcher, server); + model->getRegistered(connector); } HList::iterator hItr = handlers->begin(); @@ -105,14 +112,21 @@ void M::Model::registerModel(W::Dispatcher* dp, W::Server* srv) for (; hItr != hEnd; ++hItr) { W::Handler* handler = *hItr; - dispatcher->registerHandler(handler); + connector->registerHandler(handler); + } + + Controllers::iterator cItr = controllers->begin(); + Controllers::iterator cEnd = controllers->end(); + + for (; cItr != cEnd; ++cItr) { + connector->registerController(cItr->first, cItr->second); } registered = true; } } -void M::Model::unregisterModel() +void M::Model::getUnregistered() { if (!registered) { emit serviceMessage(QString("Model ") + address.toString().c_str() + " is not registered"); @@ -123,7 +137,7 @@ void M::Model::unregisterModel() for (; itr != end; ++itr) { Model* model = *itr; - model->unregisterModel(); + model->getUnregistered(); } HList::iterator hItr = handlers->begin(); @@ -131,21 +145,27 @@ void M::Model::unregisterModel() for (; hItr != hEnd; ++hItr) { W::Handler* handler = *hItr; - dispatcher->unregisterHandler(handler); + connector->unregisterHandler(handler); } Map::iterator sItr = subscribers->begin(); Map::iterator sEnd = subscribers->end(); for (; sItr != sEnd; ++sItr) { - const W::Socket& socket = server->getConnection(sItr->first); - disconnect(&socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + const W::Socket* socket = connector->getConnection(sItr->first); + disconnect(socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); } subscribers->clear(); subscribersCount = 0; - dispatcher = 0; - server = 0; + Controllers::iterator cItr = controllers->begin(); + Controllers::iterator cEnd = controllers->end(); + + for (; cItr != cEnd; ++cItr) { + connector->unregisterController(cItr->first, cItr->second); + } + + connector = 0; registered = false; } @@ -162,7 +182,6 @@ void M::Model::h_subscribe(const W::Event& ev) params = static_cast(vc.at(u"params")); } - Map::iterator sItr = subscribers->find(id); if (sItr == subscribers->end()) { @@ -171,8 +190,8 @@ void M::Model::h_subscribe(const W::Event& ev) emit serviceMessage(QString("Model ") + address.toString().c_str() + ": something completely wrong happened"); throw 3; } - const W::Socket& socket = server->getConnection(id); - connect(&socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + const W::Socket* socket = connector->getConnection(id); + connect(socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); sItr = pair.first; } SMap::const_iterator oItr = sItr->second.find(source); @@ -238,8 +257,8 @@ void M::Model::h_unsubscribe(const W::Event& ev) smap.erase(sItr); if (smap.size() == 0) { - const W::Socket& socket = server->getConnection(itr->first); - disconnect(&socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + const W::Socket* socket = connector->getConnection(itr->first); + disconnect(socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); subscribers->erase(itr); } --subscribersCount; @@ -256,7 +275,7 @@ void M::Model::send(W::Vocabulary* vc, const W::Address& destination, uint64_t c } W::Event ev(destination, vc); ev.setSenderId(connectionId); - server->getConnection(connectionId).send(ev); + connector->getConnection(connectionId)->send(ev); } void M::Model::response(W::Vocabulary* vc, const W::Address& handlerAddress, const W::Event& src) @@ -272,7 +291,7 @@ void M::Model::response(W::Vocabulary* vc, const W::Address& handlerAddress, con W::Event ev(source + handlerAddress, vc); ev.setSenderId(id); - server->getConnection(id).send(ev); + connector->getConnection(id)->send(ev); } void M::Model::fakeResponse(W::Vocabulary* vc, const W::Address& handlerAddress, const W::Address& sourceAddress, const W::Event& src) @@ -288,7 +307,7 @@ void M::Model::fakeResponse(W::Vocabulary* vc, const W::Address& handlerAddress, W::Event ev(source + handlerAddress, vc); ev.setSenderId(id); - server->getConnection(id).send(ev); + connector->getConnection(id)->send(ev); } void M::Model::broadcast(W::Vocabulary* vc, const W::Address& handlerAddress) @@ -307,7 +326,7 @@ void M::Model::broadcast(W::Vocabulary* vc, const W::Address& handlerAddress) for (;oItr != oEnd; ++oItr) { W::Event ev(oItr->first + handlerAddress, vc->copy()); ev.setSenderId(itr->first); - server->getConnection(itr->first).send(ev); + connector->getConnection(itr->first)->send(ev); } } delete vc; @@ -317,23 +336,54 @@ void M::Model::removeHandler(W::Handler* handler) { handlers->erase(handler); if (registered) { - dispatcher->unregisterHandler(handler); + connector->unregisterHandler(handler); } } void M::Model::removeModel(M::Model* model) { models->erase(model); + disconnect(model, SIGNAL(serviceMessage(const QString&)), this, SIGNAL(serviceMessage(const QString&))); if (registered) { - model->unregisterModel(); + model->getUnregistered(); } } -void M::Model::passToHandler(const W::Event& event) const +void M::Model::passToLocalHandler(const W::Event& event) const { if (registered) { - dispatcher->pass(event); + connector->passThroughDispatcher(event); } else { emit serviceMessage(QString("An attempt to pass event to dispatcher from unregistered model\nModel address ") + address.toString().c_str()); } } + +void M::Model::addController(C::Controller* ctrl, const W::String& nodeName) +{ + Controllers::const_iterator itr = controllers->find(ctrl); + if (itr != controllers->end()) { + emit serviceMessage(QString("An attempt to add controller ") + ctrl->getAddress().toString().c_str() + QString(" for the second time in model ") + address.toString().c_str()); + throw 9; + } + controllers->insert(std::make_pair(ctrl, nodeName)); + connect(ctrl, SIGNAL(serviceMessage(const QString&)), SIGNAL(serviceMessage(const QString&))); + if (registered) { + connector->registerController(ctrl, nodeName);; + } +} + +void M::Model::removeController(C::Controller* ctrl) +{ + Controllers::const_iterator itr = controllers->find(ctrl); + if (itr == controllers->end()) { + emit serviceMessage(QString("An attempt to remove absent controller ") + ctrl->getAddress().toString().c_str() + QString(" from the model ") + address.toString().c_str()); + throw 10; + } + + disconnect(ctrl, SIGNAL(serviceMessage(const QString&)), this, SIGNAL(serviceMessage(const QString&))); + if (registered) { + connector->unregisterController(itr->first, itr->second); + } + controllers->erase(itr); +} + diff --git a/lib/wModel/model.h b/lib/wModel/model.h index 8b44fc9..e7f9742 100644 --- a/lib/wModel/model.h +++ b/lib/wModel/model.h @@ -13,10 +13,10 @@ #include #include #include -#include -#include #include #include +#include +#include namespace M { @@ -29,10 +29,16 @@ namespace M { list, vocabulary, catalogue, + image, + button, + model, attributes = 50, file, - resourceCache + resourceCache, + audio, + + player = 107 }; Model(const W::Address p_address, QObject* parent = 0); @@ -46,13 +52,15 @@ namespace M { void addModel(M::Model* model); void addHandler(W::Handler* handler); void addProperty(const W::String& value, const W::String& name); + void addController(C::Controller* ctrl, const W::String& nodeName); W::Address getAddress() const; - void registerModel(W::Dispatcher* dp, W::Server* srv); - void unregisterModel(); + void getRegistered(U::Connector* connector); + void getUnregistered(); void removeHandler(W::Handler* handler); void removeModel(M::Model* model); - void passToHandler(const W::Event& event) const; + void removeController(C::Controller* ctrl); + void passToLocalHandler(const W::Event& event) const; signals: void serviceMessage(const QString& msg) const; @@ -76,13 +84,14 @@ namespace M { private: typedef W::Order HList; typedef W::Order MList; + typedef std::map Controllers; - W::Dispatcher* dispatcher; - W::Server* server; + U::Connector* connector; uint64_t subscribersCount; HList* handlers; W::Vector* properties; MList* models; + Controllers* controllers; private slots: void onSocketDisconnected(); diff --git a/lib/wModel/vocabulary.h b/lib/wModel/vocabulary.h index 5ddb250..bcbb08d 100644 --- a/lib/wModel/vocabulary.h +++ b/lib/wModel/vocabulary.h @@ -12,8 +12,7 @@ namespace M { class ICatalogue; - class Vocabulary : public M::Model - { + class Vocabulary : public M::Model { friend class ICatalogue; public: Vocabulary(const W::Address p_address, QObject* parent = 0); diff --git a/lib/wServerUtils/connector.cpp b/lib/wServerUtils/connector.cpp index 77b1211..724df6e 100644 --- a/lib/wServerUtils/connector.cpp +++ b/lib/wServerUtils/connector.cpp @@ -1,4 +1,5 @@ #include "connector.h" +#include "commands.h" U::Connector::Connector(W::Dispatcher* dp, W::Server* srv, U::Commands* cmds, QObject* parent): QObject(parent), @@ -6,7 +7,8 @@ U::Connector::Connector(W::Dispatcher* dp, W::Server* srv, U::Commands* cmds, QO server(srv), commands(cmds), nodes(), - ignoredNodes() + ignoredNodes(), + controllers() { connect(server, SIGNAL(newConnection(const W::Socket&)), SLOT(onNewConnection(const W::Socket&))); connect(server, SIGNAL(closedConnection(const W::Socket&)), SLOT(onClosedConnection(const W::Socket&))); @@ -30,6 +32,17 @@ U::Connector::~Connector() for (; itr != end; ++itr) { commands->removeCommand(dc + itr->first); } + + std::multimap::const_iterator cbeg = controllers.begin(); + std::multimap::const_iterator cend = controllers.end(); + + for (; cbeg != cend; ++cbeg) { + C::Controller* ctrl = cbeg->second; + if (ctrl->isSubscribed()) { + ctrl->unsubscribe(); + ctrl->unregisterController(); + } + } } void U::Connector::addIgnoredNode(const W::String& name) @@ -43,7 +56,7 @@ void U::Connector::sendTo(const W::String& name, const W::Event& event) if (itr != nodes.end()) { throw new NodeAccessError(name); } else { - server->getConnection(itr->second).send(event); + server->getConnection(itr->second)->send(event); } } @@ -69,6 +82,14 @@ void U::Connector::onNewConnection(const W::Socket& socket) emit serviceMessage(QString("New connection, id: ") + socket.getId().toString().c_str()); connect(&socket, SIGNAL(message(const W::Event&)), dispatcher, SLOT(pass(const W::Event&))); + std::multimap::const_iterator beg = controllers.lower_bound(name); + std::multimap::const_iterator end = controllers.upper_bound(name); + + for (; beg != end; ++beg) { + beg->second->registerController(dispatcher, &socket); + beg->second->subscribe(); + } + emit nodeConnected(name); } } else { @@ -90,6 +111,14 @@ void U::Connector::onClosedConnection(const W::Socket& socket) if (ign == ignoredNodes.end()) { Map::const_iterator itr = nodes.find(name); if (itr != nodes.end()) { + std::multimap::const_iterator beg = controllers.lower_bound(name); + std::multimap::const_iterator end = controllers.upper_bound(name); + + for (; beg != end; ++beg) { + beg->second->unsubscribe(); + beg->second->unregisterController(); + } + emit nodeDisconnected(name); commands->removeCommand(W::String(u"disconnect") + name); nodes.erase(itr); @@ -114,7 +143,7 @@ void U::Connector::h_disconnect(const W::Event& ev) server->closeConnection(itr->second); } -const W::Socket& U::Connector::getNodeSocket(const W::String& name) +const W::Socket* U::Connector::getNodeSocket(const W::String& name) { Map::const_iterator itr = nodes.find(name); if (itr == nodes.end()) { @@ -122,3 +151,62 @@ const W::Socket& U::Connector::getNodeSocket(const W::String& name) } return server->getConnection(itr->second); } + +void U::Connector::registerHandler(W::Handler* handler) +{ + dispatcher->registerHandler(handler); +} + +void U::Connector::unregisterHandler(W::Handler* handler) +{ + dispatcher->unregisterHandler(handler); +} + +const W::Socket * U::Connector::getConnection(uint64_t p_id) const +{ + return server->getConnection(p_id); +} + +void U::Connector::passThroughDispatcher(const W::Event& ev) const +{ + dispatcher->pass(ev); +} + +void U::Connector::registerController(C::Controller* ctrl, const W::String& node) +{ + std::set::const_iterator iitr = ignoredNodes.find(node); + if (iitr != ignoredNodes.end()) { + throw 3; //this means you're trying to receive something from one of ignored nodes, which never going to be handled in connector, most probably it's a mistake + } + controllers.insert(std::make_pair(node, ctrl)); + Map::const_iterator itr = nodes.find(node); + if (itr != nodes.end()) { + ctrl->registerController(dispatcher, server->getConnection(itr->second)); + ctrl->subscribe(); //let's say I always need them subscribed, for now at least + } +} + +void U::Connector::unregisterController(C::Controller* ctrl, const W::String& node) +{ + bool found = false; + std::multimap::const_iterator beg = controllers.lower_bound(node); + std::multimap::const_iterator end = controllers.upper_bound(node); + + for (; beg != end; ++beg) { + if (beg->second == ctrl) { + found = true; //TODO make a proper way to store 'em + break; + } + } + + if (!found) { + throw 4; + } + + if (ctrl->isSubscribed()) { + ctrl->unsubscribe(); + ctrl->unregisterController(); + } + controllers.erase(beg); +} + diff --git a/lib/wServerUtils/connector.h b/lib/wServerUtils/connector.h index 4ba0a48..e6ff2f8 100644 --- a/lib/wServerUtils/connector.h +++ b/lib/wServerUtils/connector.h @@ -1,11 +1,14 @@ #ifndef CONNECTOR_H #define CONNECTOR_H +#include + #include #include #include #include +#include #include #include @@ -16,9 +19,11 @@ #include -#include "commands.h" +#include namespace U { + class Commands; + class Connector : public QObject { Q_OBJECT @@ -29,7 +34,13 @@ namespace U { void addIgnoredNode(const W::String& name); void sendTo(const W::String& name, const W::Event& event); - const W::Socket& getNodeSocket(const W::String& name); + const W::Socket* getNodeSocket(const W::String& name); + void registerHandler(W::Handler* handler); + void unregisterHandler(W::Handler* handler); + const W::Socket* getConnection(uint64_t p_id) const; + void passThroughDispatcher(const W::Event& ev) const; + void registerController(C::Controller* ctrl, const W::String& node); + void unregisterController(C::Controller* ctrl, const W::String& node); signals: void serviceMessage(const QString& msg); @@ -42,6 +53,7 @@ namespace U { U::Commands* commands; Map nodes; std::set ignoredNodes; + std::multimap controllers; protected: handler(connect); diff --git a/lib/wSocket/server.cpp b/lib/wSocket/server.cpp index ec44bc3..9b7f6ad 100644 --- a/lib/wSocket/server.cpp +++ b/lib/wSocket/server.cpp @@ -44,14 +44,14 @@ void W::Server::stop() } } -const W::Socket& W::Server::getConnection(uint64_t p_id) const +const W::Socket* W::Server::getConnection(uint64_t p_id) const { std::map::const_iterator itr = connections.find(p_id); if (itr == connections.end()) { throw new SocketAccessError(); } - return *(itr->second); + return itr->second; } uint64_t W::Server::getConnectionsCount() const diff --git a/lib/wSocket/server.h b/lib/wSocket/server.h index 7981a71..eeeb7b1 100644 --- a/lib/wSocket/server.h +++ b/lib/wSocket/server.h @@ -27,7 +27,7 @@ namespace W void listen(uint16_t port); void stop(); - const Socket& getConnection(uint64_t p_id) const; + const Socket* getConnection(uint64_t p_id) const; uint64_t getConnectionsCount() const; void closeConnection(uint64_t p_id); void openConnection(const String& addr, const Uint64& port); diff --git a/lib/wSsh/CMakeLists.txt b/lib/wSsh/CMakeLists.txt index 167eb7d..fa56fa8 100644 --- a/lib/wSsh/CMakeLists.txt +++ b/lib/wSsh/CMakeLists.txt @@ -20,4 +20,4 @@ add_library(wSsh ${HEADERS} ${SOURCES}) target_link_libraries(wSsh Qt5::Core) target_link_libraries(wSsh ssh) -target_link_libraries(wSsh ssh_threads) +#target_link_libraries(wSsh ssh_threads) diff --git a/lib/wType/blob.cpp b/lib/wType/blob.cpp index 02991c2..9c08382 100644 --- a/lib/wType/blob.cpp +++ b/lib/wType/blob.cpp @@ -144,3 +144,17 @@ const QByteArray & W::Blob::byteArray() const return qDataView; } +const unsigned char * W::Blob::uchar() const +{ + return (unsigned char*) data; +} + +W::Blob* W::Blob::slice(uint64_t start, uint64_t length) const +{ + char* n_data = new char[length]; + for (int i = 0; i < length; ++i) { + n_data[i] = data[start + i]; + } + + return new W::Blob(length, n_data); +} diff --git a/lib/wType/blob.h b/lib/wType/blob.h index e8fdb55..8995974 100644 --- a/lib/wType/blob.h +++ b/lib/wType/blob.h @@ -22,6 +22,7 @@ namespace W size_type size() const override; objectType getType() const override; + Blob* slice(uint64_t start, uint64_t length = 0) const; bool operator==(const W::Object & other) const override; @@ -33,6 +34,7 @@ namespace W static const objectType type = blob; const QByteArray& byteArray() const; + const unsigned char* uchar() const; protected: bool hasData; diff --git a/lib/wType/object.cpp b/lib/wType/object.cpp index d567867..40507ec 100644 --- a/lib/wType/object.cpp +++ b/lib/wType/object.cpp @@ -102,5 +102,7 @@ W::Object::StdStr W::Object::getTypeName(W::Object::objectType type) case blob: return "Blob"; } + + throw 5; } diff --git a/libjs/utils/CMakeLists.txt b/libjs/utils/CMakeLists.txt index 2d5d83b..7ec6696 100644 --- a/libjs/utils/CMakeLists.txt +++ b/libjs/utils/CMakeLists.txt @@ -2,4 +2,6 @@ cmake_minimum_required(VERSION 2.8.12) configure_file(class.js class.js) configure_file(subscribable.js subscribable.js) -configure_file(globalMethods.js globalMethods.js) \ No newline at end of file +configure_file(globalMethods.js globalMethods.js) +configure_file(enum.js enum.js) +configure_file(stateMachine.js stateMachine) diff --git a/libjs/utils/enum.js b/libjs/utils/enum.js new file mode 100644 index 0000000..c843529 --- /dev/null +++ b/libjs/utils/enum.js @@ -0,0 +1,69 @@ +"use strict"; + +var Class = require("./class"); + +var Enum = Class.inherit({ + className: "Enum", + constructor: function(name, additional) { + if (typeof name !== "string" || name.length === 0) { + throw new Error("An attempt to register enum with wrong or empty name"); + } + + if (storage[name]) { + throw new Error("An attempt to register enum " + name + " for the second time"); + } + + Class.fn.constructor.call(this); + + this.name = name; + + this.straight = Object.create(null); + this.reversed = Object.create(null); + + this.additional = Object.create(null); + this._additionals = additional || []; + + this._lastId = -1; + + storage[name] = this; + }, + add: function(name, additional, id) { + if (typeof name !== "string" || name.length === 0) { + throw new Error("An attempt to add an entry with invalid name to enum " + name); + } + + if (!id) { + id = this._lastId + 1; + } + if (this.straight[id] !== undefined) { + throw new Error("Id duplication in enum " + this.name + " during an attempt to add entry " + name); + } + + if (this.reversed[name] !== undefined) { + throw new Error("Name duplication in enum " + this.name + " during an attempt to add entry " + name); + } + + this.straight[name] = id; + this.reversed[id] = name; + this.additional[id] = Object.create(null); + + for (var i = 0; i < this._additionals.length; ++i) { + var key = this._additionals[i]; + var aVal = additional[key]; + if (aVal === undefined) { + throw new Error("An attempt to add an entry " + name + " into enum " + this.name + " without providing additional value " + key); + } + this.additional[id][key] = aVal; + } + + this._lastId = id; + }, + hasAdditional: function(name) { + return this._additionals.indexOf(name) !== -1; + } +}); + +var storage = Object.create(null); +Enum.storage = storage; + +module.exports = Enum; diff --git a/libjs/utils/globalMethods.js b/libjs/utils/globalMethods.js index c104d9a..da47d1e 100644 --- a/libjs/utils/globalMethods.js +++ b/libjs/utils/globalMethods.js @@ -65,5 +65,40 @@ global.W = { // Return the modified object return lTarget; + }, + touchToMouse: function (e) { + e.preventDefault(); + if (e.touches.length > 1 || (e.type == "touchend" && e.touches.length > 0)) + return; + + var type = null; + var touch = null; + var src = e.currentTarget; + switch (e.type) { + case "touchstart": + type = "mousedown"; + touch = e.changedTouches[0]; + + break; + case "touchmove": + type = "mousemove"; + touch = e.changedTouches[0]; + src = window; + break; + case "touchend": + type = "mouseup"; + touch = e.changedTouches[0]; + src = window; + break; + } + + var event = new MouseEvent(type, { + button: 0, + screenX: touch.screenX, + screenY: touch.screenY, + clientX: touch.clientX, + clientY: touch.clientY + }); + src.dispatchEvent(event); } }; diff --git a/libjs/utils/stateMachine.js b/libjs/utils/stateMachine.js new file mode 100644 index 0000000..b15f0cd --- /dev/null +++ b/libjs/utils/stateMachine.js @@ -0,0 +1,33 @@ +"use strict"; + +var Subscribable = require("./subscribable"); + +var StateMachine = Subscribable.inherit({ + className: "StateMachine", + constructor: function (initialState, graph) { + Subscribable.fn.constructor.call(this); + + this._state = initialState; + this._graph = graph; + }, + manipulation: function (name) { + var newState = this._graph[this._state][name]; + if (newState) { + var oldState = this._state; + this._state = newState; + + this.trigger("stateChanged", { + newState: newState, + manipulation: name, + oldState: oldState + }); + } else { + this.trigger("stateMissed"); + } + }, + state: function () { + return this._state; + } +}); + +module.exports = StateMachine; diff --git a/libjs/wController/CMakeLists.txt b/libjs/wController/CMakeLists.txt index 96b4f71..f51dfbb 100644 --- a/libjs/wController/CMakeLists.txt +++ b/libjs/wController/CMakeLists.txt @@ -17,3 +17,6 @@ configure_file(localModel.js localModel.js) configure_file(catalogue.js catalogue.js) configure_file(imagePane.js imagePane.js) configure_file(file/file.js file/file.js) +configure_file(file/audio.js file/audio.js) +configure_file(player.js player.js) +configure_file(imageById.js imageById.js) diff --git a/libjs/wController/button.js b/libjs/wController/button.js new file mode 100644 index 0000000..2124928 --- /dev/null +++ b/libjs/wController/button.js @@ -0,0 +1,71 @@ +"use strict"; + +var Controller = require("./controller"); +var String = require("./string"); +var Vocabulary = require("../wType/vocabulary"); + +var Button = Controller.inherit({ + "className": "Button", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + this.enabled = false; + this.hasLabel = false; + + this.addHandler("get"); + this.addHandler("setLabel"); + this.addHandler("setImage"); + this.addHandler("setEnabled"); + this.addHandler("changeImage"); + }, + "destructor": function() { + + Controller.fn.destructor.call(this); + }, + "activate": function() { + if (this.enabled) { + this.send(new Vocabulary, "activate"); + } + }, + "_h_changeImage": function(ev) { + + }, + "_h_get": function(ev) { + this._h_setLabel(ev); + this._h_setImage(ev); + this._h_setEnabled(ev); + + this.initialized = true; + }, + "_h_setEnabled": function(ev) { + var data = ev.getData(); + + var enabled = data.at("enabled").valueOf(); + if (this.enabled !== enabled) { + this.enabled = enabled; + this.trigger("setEnabled", this.enabled); + } + }, + "_h_setLabel": function(ev) { + var data = ev.getData(); + var hasLabel = data.at("hasLabel").valueOf(); + + if (hasLabel !== this.hasLabel) { + this.hasLabel = hasLabel; + if (hasLabel) { + this.label = new String(data.at("label").clone()); + this.addController(this.label); + this.trigger("setLabel", true, this.label); + } else { + this.trigger("setLabel", false); + this.removeController(this.label); + this.label.destructor(); + } + } + }, + "_h_setImage": function(ev) { + + } +}); + +module.exports = Button; diff --git a/libjs/wController/controller.js b/libjs/wController/controller.js index ece32b7..acaf370 100644 --- a/libjs/wController/controller.js +++ b/libjs/wController/controller.js @@ -188,7 +188,7 @@ var Controller = Subscribable.inherit({ } if (this._registered) { - global.registerForeignController(pair.n, pair.c); + global.unregisterForeignController(pair.n, pair.c); } pair.c.off("serviceMessage", this._onControllerServiceMessage, this); @@ -328,8 +328,10 @@ Controller.ModelType = { String: 0, List: 1, Vocabulary: 2, - Image: 3, - Controller: 4, + Catalogue: 3, + Image: 4, + Button: 5, + Controller: 6, Attributes: 50, @@ -339,15 +341,18 @@ Controller.ModelType = { PageStorage: 103, PanesList: 104, Theme: 105, - ThemeStorage: 106 + ThemeStorage: 106, + Player: 107 }; Controller.ReversedModelType = { "0": "String", "1": "List", "2": "Vocabulary", - "3": "Image", - "4": "Controller", + "3": "Catalogue", + "4": "Image", + "5": "Button", + "6": "Controller", "50": "Attributes", @@ -357,7 +362,8 @@ Controller.ReversedModelType = { "103": "PageStorage", "104": "PanesList", "105": "Theme", - "106": "ThemeStorage" + "106": "ThemeStorage", + "107": "Player" }; Controller.ModelTypesPaths = { @@ -372,7 +378,10 @@ Controller.ModelTypesPaths = { PanesList: "./panesList", //resolve as dependency Theme: "./theme", //resolve as dependency ThemeStorage: "./themeStorage", //resolve as dependency - Image: "./image" //resolve as dependency + Image: "./image", //resolve as dependency + Button: "./button", //resolve as dependency + Catalogue: "./catalogue", //resolve as dependency + Player: "./player" //resolve as dependency }; Controller.constructors = { diff --git a/libjs/wController/file/audio.js b/libjs/wController/file/audio.js new file mode 100644 index 0000000..77b4d1b --- /dev/null +++ b/libjs/wController/file/audio.js @@ -0,0 +1,24 @@ +"use strict"; + +var File = require("./file"); +var Vocabulary = require("../../wType/vocabulary"); +var Vector = require("../../wType/vector"); +var Uint64 = require("../../wType/uint64"); + +var Audio = File.inherit({ + className: "Audio", + constructor: function Audio(addr) { + File.fn.constructor.call(this, addr); + }, + hasMore: function() { + return this.getSize() > this.data.length(); + }, + getBitrate: function() { + return this._additional.at("bitrate").valueOf(); + }, + getDuration: function() { + return this._additional.at("duration").valueOf() / 1000; + } +}); + +module.exports = Audio; diff --git a/libjs/wController/file/file.js b/libjs/wController/file/file.js index c922e4e..57e9941 100644 --- a/libjs/wController/file/file.js +++ b/libjs/wController/file/file.js @@ -2,6 +2,8 @@ var Controller = require("../controller"); var WVocabulary = require("../../wType/vocabulary"); +var Uint64 = require("../../wType/uint64"); +var Blob = require("../../wType/blob"); var File = Controller.inherit({ "className": "File", @@ -10,17 +12,17 @@ var File = Controller.inherit({ this._hasData = false; this._hasAdditional = false; - this.data = null; + this.data = new Blob(); this._additional = null; + this._waitingForSlice = false; this._need = 0; this.addHandler("get"); this.addHandler("getAdditional"); + this.addHandler("getSlice"); }, "destructor": function() { - if (this._hasData) { - this.data.destructor(); - } + this.data.destructor(); if (this._hasAdditional) { this._additional.destructor(); } @@ -46,6 +48,9 @@ var File = Controller.inherit({ "getMimeType": function() { return this._additional.at("mimeType").toString(); }, + "getSize": function() { + return this._additional.at("size").valueOf(); + }, "_h_get": function(ev) { var dt = ev.getData(); @@ -55,7 +60,9 @@ var File = Controller.inherit({ } this._hasData = true; + var oldData = this.data; this.data = dt.at("data").clone(); + oldData.destructor(); this.trigger("data"); }, "_h_getAdditional": function(ev) { @@ -63,6 +70,29 @@ var File = Controller.inherit({ if (ac) { this.trigger("additionalChange"); } + + if (!this.initialized) { + this.initialized = true; + this.trigger("ready"); + } + }, + "_h_getSlice": function(ev) { + if (this._waitingForSlice) { + this._waitingForSlice = false; + var vc = ev.getData(); + if (vc.at("result").valueOf() == 0) { + var slice = vc.at("slice"); + this.data["+="](slice); + this.trigger("slice", slice); + + if (this.getSize() === this.data.length()) { + this._hasData = true; + this.trigger("data"); + } + } else { + this.trigger("serviceMessage", "Error receiving slice from " + this._pairAddress.toString(), 1); + } + } }, "needData": function() { if (this._need === 0) { @@ -72,6 +102,30 @@ var File = Controller.inherit({ } ++this._need; }, + "requestSlice": function(size) { + if (this._hasAdditional) { + if (this._waitingForSlice) { + throw new Error("An attempt to request a slice of data from " + this._pairAddress.toString() + " while another request is in progress"); + } + var begin = this.data.length(); + var newSize = Math.min(size, this.getSize() - begin); + if (newSize !== size) { + //TODO may be inform the developer about that? + } + if (newSize === 0) { + return; //TODO may be inform the developer about that? + } + var vc = new WVocabulary(); + vc.insert("begin", new Uint64(begin)); + vc.insert("size", new Uint64(newSize)); + + this.send(vc, "getSlice"); + this._waitingForSlice = true; + + } else { + throw new Error("An attempt to request a slice of data from " + this._pairAddress.toString() + " before controller got initialized"); + } + }, "subscribe": function() { Controller.fn.subscribe.call(this); diff --git a/libjs/wController/imageById.js b/libjs/wController/imageById.js new file mode 100644 index 0000000..54598af --- /dev/null +++ b/libjs/wController/imageById.js @@ -0,0 +1,46 @@ +"use strict"; + +var LocalModel = require("./localModel"); +var File = require("./file/file"); + +var Address = require("../wType/address"); + +var ImageById = LocalModel.inherit({ + className: "ImageById", + constructor: function(properties, id) { + LocalModel.fn.constructor.call(this, properties); + + this._imageId = id; + this._fileCtrl = new File(new Address(["images", this._imageId.toString()])); + this._need = 0; + + this._fileCtrl.on("data", this._onControllerData, this); + + this.addForeignController("Corax", this._fileCtrl); + }, + dontNeedData: function() { + --this._need; + if (this._need === 0) { + this._fileCtrl.dontNeedData(); + } + }, + getMimeType: function () { + return this._fileCtrl.getMimeType(); + }, + hasData: function() { + return this._fileCtrl.hasData(); + }, + needData: function() { + if (this._need === 0) { + this._fileCtrl.needData(); + } + ++this._need; + }, + _onControllerData: function() { + this.data = this._fileCtrl.data; + + this.trigger("data"); + } +}); + +module.exports = ImageById; diff --git a/libjs/wController/imagePane.js b/libjs/wController/imagePane.js index 077e2c1..91360ad 100644 --- a/libjs/wController/imagePane.js +++ b/libjs/wController/imagePane.js @@ -4,26 +4,97 @@ var Address = require("../wType/address"); var Vocabulary = require("./vocabulary"); var File = require("./file/file"); +var LM = require("./localModel"); var ImagePane = Vocabulary.inherit({ "className": "ImagePane", "constructor": function(addr) { Vocabulary.fn.constructor.call(this, addr); + this._actions = []; + this._hasPageLink = false; this._hasImage = false; this.image = null; }, - "addElement": function(key, element) { - if (key === "image" && !this._hasImage) { - this._hasImage = true; - this.image = new File(new Address(["images", element.toString()])); - this.addForeignController("Corax", this.image); + "_actionActivate": function(name) { + var actObj = standardActions[name]; + + actObj.handler(this); + }, + "addAction": function(action) { + var type = action.at("type").valueOf(); + var obj = Object.create(null); + var supported = true; + + obj.type = type; + + switch (type) { + case 0: + var act = action.at("action").toString(); + var actObj = standardActions[act]; + if (actObj === undefined) { + this.trigger("seviceMessage", "Action " + act + " is not supported in ImagePanel, skipping"); + supported = false; + } + var model = new LM(actionProps, ""); + model.enabled = true; + model.hasLabel = true; + model.label = new LM(actionLabelProps, actObj.name); + model.addController(model.label); + model.activate = this._actionActivate.bind(this, act); + + obj.model = model; + obj.action = act; + break; } + + if (supported) { + this._actions.push(obj); + this.trigger("addAction", obj.model); + } + }, + "addElement": function(key, element) { + switch (key) { + case "image": + if (!this._hasImage) { + this._hasImage = true; + this.image = new File(new Address(["images", element.toString()])); + this.addForeignController("Corax", this.image); + } + break; + case "hasPageLink": + this._hasPageLink = element.valueOf(); + break; + case "actions": + var size = element.length(); + for (var i = 0; i < size; ++i) { + this.addAction(element.at(i)); + } + } + Vocabulary.fn.addElement.call(this, key, element); }, + "getActions": function() { + var models = []; + for (var i = 0; i < this._actions.length; ++i) { + models.push(this._actions[i].model); + } + + return models; + }, + "getPageLink": function() { + if (this._hasPageLink) { + return this.data.at("pageLink").clone(); + } else { + throw new Error("An attempt to request a page link from pane which doesn't have it"); + } + }, "hasImage": function() { return this._hasImage; }, + "hasPageLink": function() { + return this._hasPageLink; + }, "removeElement": function(key) { Vocabulary.fn.removeElement.call(this, key); @@ -37,4 +108,32 @@ var ImagePane = Vocabulary.inherit({ } }); +var standardActions = { + "play": { + handler: function (obj) { + var id = obj._pairAddress.back(); //todo it's a kind of crutch, need to do something about it in the future + window.play(id); + id.destructor(); + }, + name: "Play" + }, + "scheduledToPlay": { + handler: function(obj) { + var id = obj._pairAddress.back(); //todo it's a kind of crutch, need to do something about it in the future + window.scheduleToPlay(id); + id.destructor(); + }, + name: "Schedule" + } +}; + +var actionProps = { + "backgroundColor": "primaryColor" +}; +var actionLabelProps = { + "fontFamily": "casualFont", + "fontSize": "casualFontSize", + "color": "primaryFontColor" +} + module.exports = ImagePane; diff --git a/libjs/wController/link.js b/libjs/wController/link.js index 1ba0ee2..2babdb3 100644 --- a/libjs/wController/link.js +++ b/libjs/wController/link.js @@ -10,16 +10,12 @@ var Link = Controller.inherit({ "constructor": function(addr) { Controller.fn.constructor.call(this, addr); - var hop = new Address(["label"]); - this.targetAddress = new Address([]); this.label = new String(addr['+'](hop)); this.addController(this.label); this.addHandler("get"); - - hop.destructor(); }, "destructor": function() { this.targetAddress.destructor(); @@ -35,4 +31,6 @@ var Link = Controller.inherit({ } }); +var hop = new Address(["label"]); + module.exports = Link; diff --git a/libjs/wController/localModel.js b/libjs/wController/localModel.js index 8b1b786..4a67050 100644 --- a/libjs/wController/localModel.js +++ b/libjs/wController/localModel.js @@ -1,14 +1,16 @@ "use strict"; var counter = 0; var Subscribable = require("../utils/subscribable"); +var Controller = require("./controller"); var LocalModel = Subscribable.inherit({ "className": "LocalModel", - "constructor": function(properties) { + "constructor": function(properties, data) { Subscribable.fn.constructor.call(this); this.properties = []; this._controllers = []; + this._foreignControllers = []; if (properties) { for (var key in properties) { @@ -18,9 +20,67 @@ var LocalModel = Subscribable.inherit({ } } } + + if (data !== undefined) { + this.data = data; + this.initialized = true; + } + }, + "destructor": function() { + for (i = 0; i < this._foreignControllers.length; ++i) { + var pair = this._foreignControllers[i]; + global.unsubscribeForeignController(pair.n, pair.c); + global.unregisterForeignController(pair.n, pair.c); + + pair.c.destructor(); + } + + for (var i = 0; i < this._controllers.length; ++i) { + this._controllers[i].destructor(); + } + + Subscribable.fn.destructor.call(this); + }, + "addController": function(ctrl) { + this._controllers.push(ctrl); + }, + "addForeignController": function(nodeName, ctrl) { + if (!(ctrl instanceof Controller)) { + throw new Error("An attempt to add not a controller into " + this.className); + } + + this._foreignControllers.push({n: nodeName, c: ctrl}); + ctrl.on("serviceMessage", this._onControllerServiceMessage, this); + + global.registerForeignController(nodeName, ctrl); + global.subscribeForeignController(nodeName, ctrl); + }, + "_onControllerServiceMessage": Controller.fn._onControllerServiceMessage, + "removeForeignController": function(ctrl) { + if (!(ctrl instanceof Controller)) { + throw new Error("An attempt to remove not a controller from " + this.className); + } + for (var i = 0; i < this._foreignControllers.length; ++i) { + if (this._foreignControllers[i].c === ctrl) { + break; + } + } + + if (i === this._foreignControllers.length) { + throw new Error("An attempt to remove not not existing controller from " + this.className); + } else { + var pair = this._foreignControllers[i]; + global.unsubscribeForeignController(pair.n, pair.c); + global.registerForeignController(pair.n, pair.c); + + pair.c.off("serviceMessage", this._onControllerServiceMessage, this); + + this._foreignControllers.splice(i, 1); + } }, "setData": function(data) { this.data = data; + this.initialized = true; this.trigger("data"); } }); diff --git a/libjs/wController/panesList.js b/libjs/wController/panesList.js index 00a8213..1454e4a 100644 --- a/libjs/wController/panesList.js +++ b/libjs/wController/panesList.js @@ -9,15 +9,28 @@ var PanesList = List.inherit({ "constructor": function PanesListModel(addr) { List.fn.constructor.call(this, addr); + this.actions = null; + this._subscriptionStart = 0; this._subscriptionEnd = Infinity; }, + "destructor": function() { + if (this.initialized) { + this.actions.destructor(); + } + + List.fn.destructor.call(this); + }, "addElement": function(element) { var size = this.data.length(); List.fn.addElement.call(this, element); if (size >= this._subscriptionStart && size < this._subscriptionEnd) { var controller = new ImagePane(this._pairAddress["+"](new Address([element.toString()]))); + var size = this.actions.length(); + for (var i = 0; i < size; ++i) { + controller.addAction(this.actions.at(i)); + } this.addController(controller); } }, @@ -25,6 +38,24 @@ var PanesList = List.inherit({ List.fn.clear.call(this); this.clearChildren(); }, + "_h_get": function(ev) { + this.clear(); + + var root = ev.getData(); + var data = root.at("data"); + + if (this.initialized) { + this.actions.destructor(); + } + this.actions = root.at("actions").clone(); + + var size = data.length(); + for (var i = 0; i < size; ++i) { + this.addElement(data.at(i).clone()); + } + this.initialized = true; + this.trigger("data"); + }, "setSubscriptionRange": function(s, e) { var needStart = s !== this._subscriptionStart; var needEnd = e !== this._subscriptionEnd; @@ -33,7 +64,8 @@ var PanesList = List.inherit({ var oe = this._subscriptionEnd; this._subscriptionStart = s; this._subscriptionEnd = e; - if (this._subscribed) { + if (this._subscribed && this.initialized) { + var size = this.actions.length(); this.trigger("rangeStart"); if (needStart) { if (s > os) { @@ -47,6 +79,9 @@ var PanesList = List.inherit({ var limit = Math.min(os, e) - s; for (var i = 0; i < limit; ++i) { var ctrl = new ImagePane(this._pairAddress["+"](new Address([this.data.at(i + s).toString()]))); + for (var a = 0; a < size; ++a) { + ctrl.addAction(this.actions.at(a)); + } this.addController(ctrl, i); } } @@ -59,6 +94,9 @@ var PanesList = List.inherit({ var amount = ce - start; //it can be negative, it's fine for (var i = 0; i < amount; ++i) { var ctrl = new ImagePane(this._pairAddress["+"](new Address([this.data.at(start + i).toString()]))); + for (var a = 0; a < size; ++a) { + ctrl.addAction(this.actions.at(a)); + } this.addController(ctrl); } } else if (ce < coe) { diff --git a/libjs/wController/player.js b/libjs/wController/player.js new file mode 100644 index 0000000..2987fd3 --- /dev/null +++ b/libjs/wController/player.js @@ -0,0 +1,603 @@ +"use strict"; + +var Uint64 = require("../wType/uint64"); +var Address = require("../wType/address"); + +var Controller = require("./controller"); +var Button = require("./button"); +var ImageById = require("./imageById"); +var Vocabulary = require("./vocabulary"); +var Audio = require("./file/audio"); +var Model = require("./localModel"); + +var Enum = require("../utils/enum"); +var StateMachine = require("../utils/stateMachine"); + +var Player = Controller.inherit({ + className: "Player", + constructor: function(addr) { + Controller.fn.constructor.call(this, addr); + + this.controls = Object.create(null); + this.views = Object.create(null); + this.mode = PlayerMode.straight.playback; + this.progress = new ProgressModel(); + this.volume = new Slider(); + this.volume.setValue(1); + this.progress.on("seekingStart", this._onSeekingStart, this); + this.progress.on("seekingEnd", this._onSeekingEnd, this); + this.progress.enable(false); + this.volume.on("value", this._onVolume, this); + this._audio = null; + this._createStateMachine(); + this._createPlayingInfrastructure(); + + this.addHandler("get"); + this.addHandler("viewsChange"); + this.addHandler("play"); + this.addHandler("pause"); + + this._playbackInterval = setInterval(this._onInterval.bind(this), intervalPrecision); + }, + destructor: function() { + this._clearInterval(this._playbackInterval); + this._destroyPlayingInfrastructure(); + this._fsm.destructor(); + this.progress.destructor(); + + Controller.fn.destructor.call(this); + }, + _addControl: function(type, address) { + var t = type.valueOf(); + + if (this.controls[t] !== undefined) { + throw new Error("An attempt to add multiple instances of " + ItemType.reversed[t] + " into Player"); + } + + if (ItemType.reversed[t] !== undefined) { + switch (t) { + case ItemType.straight.playPause: + case ItemType.straight.prev: + case ItemType.straight.next: + var btn = new Button(address.clone()); + btn.itemType = t; + this.controls[t] = btn; + this.addController(btn); + this.trigger("newElement", btn, t); + break; + default: + this.trigger("serviceMessage", "An attempt to add ItemType " + ItemType.reversed[t] + " to controls of the Player, but it's not qualified to be a control", 1); + } + } else { + this.trigger("serviceMessage", "An unrecgnized item ItemType in Player: " + t, 1); + } + }, + _addView: function(type, address) { + var t = type.valueOf(); + var ctrl; + var supported = false; + + if (this.views[t] !== undefined) { + throw new Error("An attempt to add multiple instances of " + ItemType.reversed[t] + " into Player"); + } + + if (ItemType.reversed[t] !== undefined) { + switch (t) { + case ItemType.straight.queue: + this.trigger("serviceMessage", "Queue is not supported yet in Player", 1); + break; + case ItemType.straight.currentPlayback: + ctrl = new Vocabulary(address.clone()); + ctrl.on("newElement", this._onNewPlaybackElement, this); + ctrl.on("removeElement", this._onRemovePlaybackElement, this); + supported = true; + break; + case ItemType.straight.picture: + ctrl = new ImageById(null, address.back()); + ctrl.ItemType = t; + this.views[t] = ctrl; + + this.trigger("newElement", ctrl, t); + supported = false; //just to avoid adding with addController, since ImageById is not a controller + break; + default: + this.trigger("serviceMessage", "An attempt to add ItemType " + ItemType.reversed[t] + " to views of the Player, but it's not qualified to be a view", 1); + } + } else { + this.trigger("serviceMessage", "An unrecognized item ItemType in Player: " + t, 1); + } + + if (supported) { + ctrl.ItemType = t; + this.views[t] = ctrl; + this.addController(ctrl); + + this.trigger("newElement", ctrl, t); + } + }, + _checkIfEnough: function() { + var diff = this._currentTime - this._seekingTime - this._ctx.currentTime; + if (diff > threshold) { + this._fsm.manipulation("enough"); + } else { + this._fsm.manipulation("notEnough"); + } + }, + _createPlayingInfrastructure: function() { + this._ctx = new AudioContext(); + this._decoder = new Mp3Decoder(); + this._gainNode = this._ctx.createGain(); + this._gainNode.connect(this._ctx.destination); + this._currentTime = 0; + this._seekingTime = 0; + this._buffers = []; + this._sources = []; + + this._ctx.suspend(); + }, + _createStateMachine: function() { + this._fsm = new StateMachine("initial", graphs[this.mode]); + this._fsm.on("stateChanged", this._onStateChanged, this); + }, + _destroyPlayingInfrastructure: function() { + this._ctx.close(); + this._decoder.delete(); + }, + getCurrentPlaybackTime: function() { + return this._ctx.currentTime + this._seekingTime; + }, + _h_get: function(ev) { + var data = ev.getData(); + + var controls = data.at("controls"); + var views = data.at("views"); + var mode = data.at("mode").valueOf(); + var size, i, vc; + + size = controls.length(); + for (i = 0; i < size; ++i) { + vc = controls.at(i); + this._addControl(vc.at("type"), vc.at("address")); + } + + size = views.length(); + for (i = 0; i < size; ++i) { + vc = views.at(i); + this._addView(vc.at("type"), vc.at("address")); + } + + if (this.mode !== mode) { + if (PlayerMode.reversed[mode] === undefined) { + throw new Error("Unsupported mode of player: " + mode); + } + this.mode = mode; + } + + this.initialized = true; + this.trigger("data"); + }, + _h_pause: function(ev) { + this._fsm.manipulation("pause"); + }, + _h_play: function(ev) { + this._fsm.manipulation("play"); + }, + _h_viewsChange: function(ev) { + var data = ev.getData(); + + var add = data.at("add"); + var remove = data.at("remove"); + var size, i, vc; + + size = remove.length(); + for (i = 0; i < size; ++i) { + this._removeView(remove.at(i).valueOf()); + } + + size = add.length(); + for (i = 0; i < size; ++i) { + vc = add.at(i); + this._addView(vc.at("type"), vc.at("address")); + } + }, + _onAudioNewSlice: function(frames) { + var arr = new Uint8Array(frames.valueOf()); + this._decoder.addFragment(arr); + + while (this._decoder.hasMore()) { + var sb = this._decoder.decode(9999); + if (sb === undefined) { + break; + } else { + this._buffers.push(sb); + + var startTime = this._currentTime - this._seekingTime; + if (startTime < this._ctx.currentTime) { + var offset = startTime - this._ctx.currentTime + sb.duration; + if (offset > 0) { + var src = this._ctx.createBufferSource(); + src.buffer = sb; + src.connect(this._gainNode); + src.start(0, Math.abs(startTime - this._ctx.currentTime)); + this._sources.push(src); + } + } else { + var src = this._ctx.createBufferSource(); + src.buffer = sb; + src.connect(this._gainNode); + src.start(startTime); + this._sources.push(src); + } + + this._currentTime += sb.duration; + } + } + + this.progress.setLoad(this._currentTime / this._audio.getDuration()); + + this._fsm.manipulation("newFrames"); + if (this._audio.hasMore()) { + this._audio.requestSlice(audioPortion); + } else { + this._fsm.manipulation("noMoreFrames"); + } + }, + _onControllerReady: function() { + this._fsm.manipulation("controllerReady"); + }, + _onInterval: function() { + if (this._audio && this._audio.initialized && seekingStates.indexOf(this._fsm.state()) === -1) { + var duration = this._audio.getDuration(); + this.progress.setValue(this.getCurrentPlaybackTime() / duration); + this._checkIfEnough(); + + if (this.progress.value >= 0.9999) { + var next = this.controls[ItemType.straight.next]; + if (next && next.enabled) { + next.activate(); + } else { + this._fsm.manipulation("pause"); + this._onSeekingStart(); + this._onSeekingEnd(0); + this.controls[ItemType.straight.playPause].activate(); + } + } + } + }, + _onNewPlaybackElement: function(key, element) { + switch (key) { + case "image": + var address = new Address(["images", element.toString()]); + this._addView(new Uint64(ItemType.straight.picture), address); + address.destructor(); + break; + case "audio": + if (this.mode === PlayerMode.straight.playback) { + this._audio = new Audio(new Address(["music", element.toString()])); + this.addForeignController("Corax", this._audio); + this._audio.on("slice", this._onAudioNewSlice, this); + this._audio.on("ready", this._onControllerReady, this); + this._fsm.manipulation("controller"); + } + break; + } + }, + _onRemovePlaybackElement: function(key) { + switch (key) { + case "image": + this._removeView(ItemType.straight.picture); + break; + case "audio": + this.removeForeignController(this._audio); + this._audio.destructor(); + this._audio = null; + } + }, + _onSeekingStart: function() { + this._fsm.manipulation("startSeeking"); + }, + _onSeekingEnd: function(progress) { + if (seekingStates.indexOf(this._fsm.state()) !== -1) { + for (var i = 0; i < this._sources.length; ++i) { + this._sources[i].stop(); + } + this._sources = []; + + var ct = this.getCurrentPlaybackTime(); + var duration = this._audio.getDuration(); + var targetTime = duration * progress; + this._seekingTime += targetTime - ct; + + var nc = 0; + + for (var i = 0; i < this._buffers.length; ++i) { + var buffer = this._buffers[i]; + var startTime = nc - targetTime; + if (startTime < 0) { + var offset = startTime + buffer.duration; + if (offset > 0) { + var src = this._ctx.createBufferSource(); + src.buffer = buffer; + src.connect(this._gainNode); + src.start(0, Math.abs(startTime)); + this._sources.push(src); + } + } else { + var src = this._ctx.createBufferSource(); + src.buffer = buffer; + src.connect(this._gainNode); + src.start(this._ctx.currentTime + startTime); + this._sources.push(src); + } + + nc += buffer.duration; + } + } + this._fsm.manipulation("stopSeeking"); + }, + _onStateChanged: function(e) { + switch (e.newState) { + case "initial": + if (e.manipulation === "noController") { + this.progress.enable(false); + this.removeForeignController(this._audio); + this._audio.destructor(); + this._audio = null; + this._destroyPlayingInfrastructure(); + this._createPlayingInfrastructure(); + } + break; + case "initialPlaying": + if (e.manipulation === "noController") { + this.progress.enable(false); + this._ctx.suspend(); + + this.removeForeignController(this._audio); + this._audio.destructor(); + this._audio = null; + this._destroyPlayingInfrastructure(); + this._createPlayingInfrastructure(); + } + break; + case "controllerNotReady": + break + case "controllerNotReadyPlaying": + break + case "hasController": + break; + case "hasControllerPlaying": + if (this._audio.hasMore()) { + this._audio.requestSlice(audioPortion); + } else { + this._fsm.manipulation("noMoreFrames"); + } + break; + case "buffering": + if (e.oldState === "hasController") { + this.progress.enable(true); + } + break; + case "bufferingPlaying": + if (e.oldState === "playing") { + this._ctx.suspend(); + } else if (e.oldState === "hasControllerPlaying") { + this.progress.enable(true); + } + break; + case "seeking": + break; + case "seekingPlaying": + if (e.oldState === "playing") { + this._ctx.suspend(); + } + break; + case "seekingAllLoaded": + break; + case "seekingPlayingAllLoaded": + if (e.oldState === "playingAllLoaded") { + this._ctx.suspend(); + } + break; + case "paused": + switch (e.oldState) { + case "playing": + this._ctx.suspend(); + break; + } + break; + case "pausedAllLoaded": + switch (e.oldState) { + case "playingAllLoaded": + this._ctx.suspend(); + break; + } + break; + case "playing": + this._ctx.resume(); + break; + case "playingAllLoaded": + switch (e.oldState) { + case "pausedAllLoaded": + case "bufferingPlaying": + case "seekingPlayingAllLoaded": + this._ctx.resume(); + break; + } + break; + } + }, + _onVolume: function(volume) { + this._gainNode.gain.cancelScheduledValues(this._ctx.currentTime); + this._gainNode.gain.exponentialRampToValueAtTime(volume, this._ctx.currentTime + 0.01); + }, + _removeControl: function(type) { + var ctrl = this.controls[type]; + if (ctrl !== undefined) { + this.trigger("removeElement", type); + this.removeController(ctrl); + ctrl.destructor(); + } + }, + _removeView: function(type) { + var view = this.views[type]; + if (view !== undefined) { + this.trigger("removeElement", type); + + if (type !== ItemType.straight.picture) { + this.removeController(view); + } + if (type === ItemType.straight.currentPlayback) { + if (this.views[ItemType.straight.picture]) { + this._removeView(ItemType.straight.picture); + } + this._fsm.manipulation("noController"); + } + delete this.views[type]; + view.destructor(); + } + } +}); + +var ItemType = new Enum("ItemType"); +ItemType.add("playPause"); +ItemType.add("currentPlayback"); +ItemType.add("queue"); +ItemType.add("picture"); +ItemType.add("prev"); +ItemType.add("next"); + +var PlayerMode = new Enum("PlayerMode"); +PlayerMode.add("playback"); + +Player.ItemType = ItemType; + +var graphs = Object.create(null); +graphs[PlayerMode.straight.playback] = { + "initial": { + controller: "controllerNotReady", + play: "initialPlaying" + }, + "initialPlaying": { + pause: "initial", + controller: "controllerNotReadyPlaying" + }, + "controllerNotReady": { + play: "controllerNotReadyPlaying", + controllerReady: "hasController" + }, + "controllerNotReadyPlaying": { + pause: "controllerNotReady", + controllerReady: "hasControllerPlaying" + }, + "hasController": { + newFrames: "bufferingPlaying", + play: "hasControllerPlaying", + noController: "initial" + }, + "hasControllerPlaying": { + newFrames: "bufferingPlaying", + pause: "hasController", + noController: "initialPlaying" + }, + "buffering": { + play: "bufferingPlaying", + enough: "paused", + noMoreFrames: "pausedAllLoaded", + startSeeking: "seeking", + noController: "initial" + }, + "bufferingPlaying": { + pause: "buffering", + enough: "playing", + noMoreFrames: "playingAllLoaded", + startSeeking: "seekingPlaying", + noController: "initialPlaying" + }, + "seeking": { + stopSeeking: "buffering", + noMoreFrames: "seekingAllLoaded", + noController: "initial" + }, + "seekingPlaying": { + stopSeeking: "bufferingPlaying", + noMoreFrames: "playingAllLoaded", + noController: "initialPlaying" + }, + "seekingAllLoaded": { + stopSeeking: "pausedAllLoaded", + noController: "initial" + }, + "seekingPlayingAllLoaded": { + stopSeeking: "playingAllLoaded", + noController: "initialPlaying" + }, + "paused": { + play: "playing", + notEnough: "buffering", + noController: "initial", + noMoreFrames: "pausedAllLoaded", + startSeeking: "seeking" + }, + "pausedAllLoaded": { + play: "playingAllLoaded", + noController: "initial", + startSeeking: "seekingAllLoaded" + }, + "playing": { + pause: "paused", + notEnough: "bufferingPlaying", + noMoreFrames: "playingAllLoaded", + noController: "initialPlaying", + startSeeking: "seekingPlaying" + }, + "playingAllLoaded": { + pause: "pausedAllLoaded", + noController: "initialPlaying", + startSeeking: "seekingPlayingAllLoaded" + } +} +var seekingStates = ["seeking", "seekingPlaying", "seekingAllLoaded", "seekingPlayingAllLoaded"] + +var audioPortion = 1024 * 50; //bytes to download for each portion +var threshold = 2; //seconds to buffer before playing +var intervalPrecision = 100; //millisecond of how often to check the playback + +var Slider = Model.inherit({ + className: "Slider", + constructor: function(properties) { + Model.fn.constructor.call(this, properties); + + this.enabled = true; + this.value = 0; + this.initialized = true; + }, + enable: function(en) { + if (en !== this.enabled) { + this.enabled = en; + this.trigger("enabled", en); + } + }, + setValue: function(p) { + if (p !== this.value) { + this.value = p; + this.trigger("value", p); + } + } +}); + +var ProgressModel = Slider.inherit({ + className: "ProgressModel", + constructor: function(properties) { + Slider.fn.constructor.call(this, properties); + + this.value = 0; + }, + setLoad: function(l) { + if (l !== this.load) { + this.load = l; + this.trigger("load", l); + } + } +}); + +module.exports = Player; diff --git a/libjs/wController/vocabulary.js b/libjs/wController/vocabulary.js index 9905909..afe4379 100644 --- a/libjs/wController/vocabulary.js +++ b/libjs/wController/vocabulary.js @@ -45,7 +45,7 @@ var Vocabulary = Controller.inherit({ var keys = insert.getKeys(); for (var j = 0; j < keys.length; ++j) { - key = keys[i]; + key = keys[j]; this.addElement(key, insert.at(key).clone()); } this.trigger("change", data.clone()); diff --git a/libjs/wModel/CMakeLists.txt b/libjs/wModel/CMakeLists.txt index b06a09c..d0bd7cc 100644 --- a/libjs/wModel/CMakeLists.txt +++ b/libjs/wModel/CMakeLists.txt @@ -13,5 +13,6 @@ configure_file(themeStorage.js themeStorage.js) configure_file(vocabulary.js vocabulary.js) configure_file(attributes.js attributes.js) configure_file(image.js image.js) +configure_file(button.js button.js) add_subdirectory(proxy) diff --git a/libjs/wModel/button.js b/libjs/wModel/button.js new file mode 100644 index 0000000..276f51f --- /dev/null +++ b/libjs/wModel/button.js @@ -0,0 +1,90 @@ +"use strict"; + +var Model = require("./model"); +var ModelString = require("./string"); + +var Vocabulary = require("../wType/vocabulary"); +var Boolean = require("../wType/boolean"); +var Address = require("../wType/address"); +var String = require("../wType/string"); + +var Button = Model.inherit({ + "className": "Button", + "constructor": function(address) { + Model.fn.constructor.call(this, address); + + this._enabled = true; + this._hasImage = false; + this._hasLabel =false; + this._imageName = undefined; + this._label = undefined; + + this.addHandler("get"); + this.addHandler("activate"); + }, + "setImage": function(name) { + if (this._hasImage) { + if (this._imageName !== name) { + this._image = name; + var vc = new Vocabulary(); + vc.insert("image", new String(this._imageName)); + this.broadcast(vc, "changeImage"); + } + } else { + this._image = name; + this._hasImage = true; + var vc = new Vocabulary(); + vc.insert("image", new String(this._imageName)); + this.broadcast(vc, "setImage"); + } + }, + "setEnabled": function(enabled) { + if (enabled !== this._enabled) { + this._enabled = enabled; + var vc = new Vocabulary(); + vc.insert("enabled", new Boolean(this._enabled)); + this.broadcast(vc, "setEnabled"); + } + }, + "setLabel": function(text) { + if (this._hasLabel) { + this._label.set(text); + } else { + this._label = new ModelString(this._address["+"](labelHop), text); + this.addModel(this._label); + var vc = new Vocabulary(); + vc.insert("hasLabel", new Boolean(true)); + vc.insert("label", this._label.getAddress()); + this.broadcast(vc, "setLabel"); + this._hasLabel = true; + } + }, + "_h_subscribe": function(ev) { + Model.fn._h_subscribe.call(this, ev); + + this._h_get(ev); + }, + "_h_get": function(ev) { + var vc = new Vocabulary(); + vc.insert("hasImage", new Boolean(this._hasImage)); + if (this._hasImage) { + vc.insert("image", new String(this._imageName)); + } + vc.insert("hasLabel", new Boolean(this._hasLabel)); + if (this._hasLabel) { + vc.insert("label", this._label.getAddress()); + } + vc.insert("enabled", new Boolean(this._enabled)); + + this.response(vc, "get", ev); + }, + "_h_activate": function() { + if (this._enabled) { + this.trigger("activated"); + } + } +}); + +var labelHop = new Address(["label"]); + +module.exports = Button; diff --git a/libjs/wModel/model.js b/libjs/wModel/model.js index d5f68a1..e5643b5 100644 --- a/libjs/wModel/model.js +++ b/libjs/wModel/model.js @@ -299,8 +299,10 @@ Model.ModelType = { String: 0, List: 1, Vocabulary: 2, - Image: 3, - Model: 4, + //Catalogue: 3, + Image: 4, + Button: 5, + Model: 6, Attributes: 50, @@ -310,7 +312,8 @@ Model.ModelType = { PageStorage: 103, PanesList: 104, Theme: 105, - ThemeStorage: 106 + ThemeStorage: 106, + Player: 107 }; module.exports = Model; diff --git a/libjs/wType/blob.js b/libjs/wType/blob.js index 3aa38de..5a33d19 100644 --- a/libjs/wType/blob.js +++ b/libjs/wType/blob.js @@ -17,6 +17,36 @@ var Blob = Object.inherit({ return this.size() == other.size(); //TODO let's pretend one shall never wish to compare blobs) }, + "+=": function(other) { + if (this.getType() !== other.getType()) { + throw new Error("An attempt to add and assign an " + other.className + " to " + this.className); + } + + var newData = new ArrayBuffer(this._data.byteLength + other._data.byteLength); + var newView = new Uint8Array(newData); + var thisView = new Uint8Array(this._data); + var otherView = new Uint8Array(other._data); + + newView.set(thisView, 0); + newView.set(otherView, this._data.byteLength); + + this._data = newData; + }, + "+": function(other) { + if (this.getType() !== other.getType()) { + throw new Error("An attempt to add an " + other.className + " to " + this.className); + } + + var newData = new ArrayBuffer(this._data.byteLength + other._data.byteLength); + var newView = new Uint8Array(newData); + var thisView = new Uint8Array(this._data); + var otherView = new Uint8Array(other._data); + + newView.set(thisView, 0); + newView.set(otherView, this._data.byteLength); + + return new Blob(newData); + }, "base64": function() { var arr = new Uint8Array(this._data); var bin = ""; diff --git a/lorgar/core/lorgar.js b/lorgar/core/lorgar.js index bc19b87..f0af7aa 100644 --- a/lorgar/core/lorgar.js +++ b/lorgar/core/lorgar.js @@ -4,6 +4,7 @@ var defineArray = []; defineArray.push("lib/utils/class"); + defineArray.push("lib/utils/enum"); defineArray.push("lib/wSocket/socket"); defineArray.push("lib/wDispatcher/dispatcher"); defineArray.push("lib/wDispatcher/handler"); @@ -18,6 +19,7 @@ defineArray.push("lib/wController/pageStorage"); defineArray.push("lib/wController/page"); defineArray.push("lib/wController/localModel"); + defineArray.push("lib/wController/player"); defineArray.push("views/view"); defineArray.push("views/layout"); @@ -27,6 +29,7 @@ define(moduleName, defineArray, function lorgar_module() { var Class = require("lib/utils/class"); + var Enum = require("lib/utils/enum"); var Socket = require("lib/wSocket/socket"); var Dispatcher = require("lib/wDispatcher/dispatcher"); var Handler = require("lib/wDispatcher/handler"); @@ -41,6 +44,7 @@ var PageStorage = require("lib/wController/pageStorage"); var PageController = require("lib/wController/page"); var LocalModel = require("lib/wController/localModel"); + var PlayerModel = require("lib/wController/player"); var View = require("views/view"); var Layout = require("views/layout"); @@ -53,16 +57,18 @@ "constructor": function() { Class.fn.constructor.call(this); + this._playerCtl = undefined; this._currentPageCtl = undefined; this._nodes = Object.create(null); this._initDispatcher(); + this._initModels(); + this._initViews(); this._prepareNode("Magnus", "localhost", 8081); this._prepareNode("Corax", "localhost", 8080); - this._initModels(); - this._initViews(); + this._registerModels(); this.connectNode("Magnus"); this.connectNode("Corax"); @@ -70,6 +76,8 @@ }, "destructor": function() { window.onpopstate = undefined; + this._unregisterModels(); + if (this._currentPageCtl) { this._currentPage.destructor(); this._currentPageCtl.destructor(); @@ -77,16 +85,17 @@ this._gc.destructor(); this._ps.destructor(); - this._mainColorHelper.destructor(); this._emptyHelper.destructor(); this._body.destructor(); this.coraxSocket.close(); + this.dispatcher.unregisterHandler(this._playerResponseHandler); this.dispatcher.unregisterDefaultHandler(this._logger); this._logger.destructor(); this.dispatcher.destructor(); + this._playerResponseHandler.destructor(); //this.magnusSocket.destructor(); //this.coraxSocket.destructor(); @@ -106,31 +115,37 @@ } node.socket.open(node.address, node.port); + node.state.setData(SocketState.straight.connecting); }, - "_initCoraxSocket": function() { - this.coraxSocket = new Socket("Lorgar"); - this.coraxSocket.on("connected", this._coraxSocketConnected, this); - this.coraxSocket.on("disconnected", this._coraxSocketDisconnected, this); - this.coraxSocket.on("error", this._coraxSocketError, this); - this.coraxSocket.on("message", this.dispatcher.pass, this.dispatcher); + _registerModels: function () { + this._gc.register(this.dispatcher, this._nodes.Magnus.socket); + this._ps.register(this.dispatcher, this._nodes.Magnus.socket); + }, + _unregisterModels: function() { + if (this._currentPageCtl) { + this._currentPageCtl.unregister(); + } + + this._gc.unregister(); + this._ps.unregister(); }, "_initDispatcher": function() { this.dispatcher = new Dispatcher(); this._logger = new Logger(); + this._playerResponseHandler = new Handler(new Address(["getPlayer"]), this, this._responsePlayer); this.dispatcher.registerDefaultHandler(this._logger); + this.dispatcher.registerHandler(this._playerResponseHandler); }, "_initModels": function() { this._gc = new GlobalControls(new Address(["magnus", "gc"])); this._ps = new PageStorage(new Address(["magnus", "ps"])); - this._mainColorHelper = new LocalModel({backgroundColor: "mainColor"}); this._emptyHelper = new LocalModel(); + this._gc.on("serviceMessage", this._onServiceMessage, this); + this._ps.on("serviceMessage", this._onServiceMessage, this); + this._gc.on("themeSelected", this.setTheme, this); - - this._gc.register(this.dispatcher, this._nodes.Magnus.socket); - this._ps.register(this.dispatcher, this._nodes.Magnus.socket); - this._ps.on("pageName", this._onPageName, this); }, "_initPageController": function(addr) { @@ -150,18 +165,10 @@ document.body.innerHTML = ""; document.body.appendChild(this._body._e); - window.addEventListener("resize",this._onWindowResize.bind(this) ,false); + window.addEventListener("resize", this._onWindowResize.bind(this), false); this._body.setSize(document.body.offsetWidth, document.body.offsetHeight); this._body.append(this._mainLayout); - var spacerL = new View(this._mainColorHelper, { - maxWidth: 50 - }); - var spacerR = new View(this._mainColorHelper, { - maxWidth: 50 - }); - this._mainLayout.append(spacerL, 1, 0, 1, 1); - this._mainLayout.append(spacerR, 1, 2, 1, 1); }, "_onHistoryPopState": function(e) { this._initPageController(new Address(e.state.address)); @@ -171,10 +178,29 @@ address: this._currentPageCtl.getPairAddress().toArray() }, "", name); }, + "_onServiceMessage": function(text, severity) { + var fn; + + switch (severity) { + case 2: + fn = console.error; + break; + case 1: + fn = console.warn; + break; + case 0: + default: + fn = console.info; + break; + } + + fn(text); + }, "_onSocketConnected": function(name) { console.log(name + " socket connected"); var node = this._nodes[name]; node.connected = true; + node.state.setData(SocketState.straight.connected); for (var id in node.foreigns) { if (node.foreigns[id].subscribed) { @@ -182,13 +208,18 @@ } } - if (name === "Magnus") { - this._gc.subscribe(); + switch (name) { + case "Magnus": + this._gc.subscribe(); - if (!this._currentPageCtl) { - this._ps.getPageAddress(location.pathname); - this._ps.one("pageAddress", this._initPageController, this); - } + if (!this._currentPageCtl) { + this._ps.getPageAddress(location.pathname); + this._ps.one("pageAddress", this._initPageController, this); + } + break; + case "Corax": + this._requestPlayer(); + break; } }, "_onSocketDisconnected": function(name) { @@ -196,19 +227,44 @@ var node = this._nodes[name]; node.connected = false; + switch (name) { + case "Corax": + if (this._playerCtl) { + this._mainLayout.removePlayer(); + this._playerCtl._onSocketDisconnected(); + this._playerCtl.unregister(); + this._playerCtl.destructor(); + this._playerCtl = undefined; + } + } + for (var id in node.foreigns) { if (node.foreigns[id].subscribed) { - node.foreigns[id].controller._onSocketDisconnected; + node.foreigns[id].controller._onSocketDisconnected(); } } + node.state.setData(SocketState.straight.disconnected); }, - "_onSocketError": function(name) { + "_onSocketError": function(name, e) { console.log(name + " socket error: "); console.log(e); }, "_onWindowResize": function() { this._body.setSize(document.body.offsetWidth, document.body.offsetHeight); }, + "play": function(id) { + if (this._nodes.Corax && this._nodes.Corax.connected) { + var vc = new Vocabulary(); + vc.insert("id", id.clone()); + + var ev = new Event(this._playerCtl.getPairAddress()["+="](play), vc); + + var socket = this._nodes.Corax.socket; + ev.setSenderId(socket.getId().clone()); + socket.send(ev); + ev.destructor(); + } + }, "_prepareNode": function(name, address, port) { if (this._nodes[name]) { throw new Error("An attempt to prepeare node " + name + " for the second time"); @@ -220,22 +276,27 @@ obj.socket = new Socket("Lorgar"); obj.connected = false; obj.foreigns = Object.create(null); + obj.state = new LocalModel({fontFamily: "casualFont"}); + obj.state.enum = SocketState; + obj.state.setData(SocketState.straight.disconnected); obj.socket.on("connected", this._onSocketConnected.bind(this, name)); obj.socket.on("disconnected", this._onSocketDisconnected.bind(this, name)); obj.socket.on("error", this._onSocketError.bind(this, name)); obj.socket.on("message", this.dispatcher.pass, this.dispatcher); + this._mainLayout.addState(name, obj.state); + this._nodes[name] = obj; }, - "registerForeignController": function(node, controller) { - var node = this._nodes[node]; + "registerForeignController": function(nodeName, controller) { + var node = this._nodes[nodeName]; if (node === undefined) { - throw new Error("An attempt to register controller to an unknown node " + node); + throw new Error("An attempt to register controller to an unknown node " + nodeName); } if (node.foreigns[controller.id] !== undefined) { - throw new Error("An attempt to register a controller under node " + node + " for a second time"); + throw new Error("An attempt to register a controller under node " + nodeName + " for a second time"); } var obj = Object.create(null); obj.controller = controller; @@ -243,47 +304,89 @@ node.foreigns[controller.id] = obj; controller.register(this.dispatcher, node.socket); }, + "_requestPlayer": function() { + var vc = new Vocabulary(); + vc.insert("source", new Address([])); + + var ev = new Event(new Address(["management", "givePlayer"]), vc); + + var socket = this._nodes.Corax.socket; + ev.setSenderId(socket.getId().clone()); + socket.send(ev); + ev.destructor(); + }, + "_responsePlayer": function(ev) { + var data = ev.getData(); + + this._playerCtl = new PlayerModel(data.at("address").clone()); + this._playerCtl.register(this.dispatcher, this._nodes["Corax"].socket); + this._playerCtl.subscribe(); + this._mainLayout.appendPlayer(this._playerCtl); + + this._playerCtl.on("serviceMessage", this._onServiceMessage, this); + }, + "scheduleToPlay": function(id) { + if (this._nodes.Corax && this._nodes.Corax.connected) { + var vc = new Vocabulary(); + vc.insert("id", id.clone()); + + var ev = new Event(this._playerCtl.getPairAddress()["+="](queue), vc); + + var socket = this._nodes.Corax.socket; + ev.setSenderId(socket.getId().clone()); + socket.send(ev); + ev.destructor(); + } + }, "setTheme": function(theme) { View.setTheme(theme); }, - "subscribeForeignController": function(node, controller) { - var node = this._nodes[node]; + "subscribeForeignController": function(nodeName, controller) { + var node = this._nodes[nodeName]; if (node === undefined) { - throw new Error("An attempt to subscribe a controller to an unknown node " + node); + throw new Error("An attempt to subscribe a controller to an unknown node " + nodeName); } if (node.foreigns[controller.id] === undefined) { - throw new Error("An attempt to subscribe not registered controller to node " + node); + throw new Error("An attempt to subscribe not registered controller to node " + nodeName); } node.foreigns[controller.id].subscribed = true; controller.subscribe(); }, - "unregisterForeignController": function(node, controller) { - var node = this._nodes[node]; + "unregisterForeignController": function(nodeName, controller) { + var node = this._nodes[nodeName]; if (node === undefined) { - throw new Error("An attempt to unregister a controller from an unknown node " + node); + throw new Error("An attempt to unregister a controller from an unknown node " + nodeName); } if (node.foreigns[controller.id] === undefined) { - throw new Error("An attempt to unregister not registered controller from node " + node); + throw new Error("An attempt to unregister not registered controller from node " + nodeName); } delete node.foreigns[controller.id]; controller.unregister(); }, - "unsubscribeForeignController": function(node, controller) { - var node = this._nodes[node]; + "unsubscribeForeignController": function(nodeName, controller) { + var node = this._nodes[nodeName]; if (node === undefined) { - throw new Error("An attempt to unsubscribe a controller from an unknown node " + node); + throw new Error("An attempt to unsubscribe a controller from an unknown node " + nodeName); } if (node.foreigns[controller.id] === undefined) { - throw new Error("An attempt to unsubscribe not registered controller from node " + node); + throw new Error("An attempt to unsubscribe not registered controller from node " + nodeName); } node.foreigns[controller.id].subscribed = false; controller.unsubscribe(); } }); + var SocketState = new Enum("SocketState", ["description"]); + SocketState.add("disconnected", {description: "Socket is disconnected"}); + SocketState.add("connecting", {description: "Socket is connecting to remote host"}); + SocketState.add("connected", {description: "Socket is connected"}); + + var queue = new Address(["queue"]); + var play = new Address(["play"]); + return Lorgar; }); })(); diff --git a/lorgar/css/main.css b/lorgar/css/main.css index 4a92774..96402ed 100644 --- a/lorgar/css/main.css +++ b/lorgar/css/main.css @@ -45,3 +45,15 @@ div.dragging .draggable { cursor: -moz-grabbing; cursor: grabbing; } + +.disabled { + opacity: 0.5; +} + +.button { + cursor: pointer +} + +.button.disabled { + cursor: not-allowed +} diff --git a/lorgar/index.html b/lorgar/index.html index 7b5c210..bef6be6 100644 --- a/lorgar/index.html +++ b/lorgar/index.html @@ -10,4 +10,4 @@

I am

- \ No newline at end of file + diff --git a/lorgar/lib/CMakeLists.txt b/lorgar/lib/CMakeLists.txt index b10b75c..9e87652 100644 --- a/lorgar/lib/CMakeLists.txt +++ b/lorgar/lib/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory(wDispatcher) add_subdirectory(wTest) add_subdirectory(wController) add_subdirectory(fonts) +add_subdirectory(em) diff --git a/lorgar/lib/bintrees/CMakeLists.txt b/lorgar/lib/bintrees/CMakeLists.txt index 346394a..548677a 100644 --- a/lorgar/lib/bintrees/CMakeLists.txt +++ b/lorgar/lib/bintrees/CMakeLists.txt @@ -1,3 +1,3 @@ cmake_minimum_required(VERSION 2.8.12) -configure_file(index.js index.js) \ No newline at end of file +configure_file(index.js index.js) diff --git a/lorgar/lib/em/CMakeLists.txt b/lorgar/lib/em/CMakeLists.txt new file mode 100644 index 0000000..42c93c9 --- /dev/null +++ b/lorgar/lib/em/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 2.8.12) + +execute_process(COMMAND + emcc --bind + ${CMAKE_CURRENT_SOURCE_DIR}/libmad.bc + ${CMAKE_CURRENT_SOURCE_DIR}/decoder.cpp + -o ${CMAKE_CURRENT_BINARY_DIR}/wrapper.js + -s FILESYSTEM=0 + -s ENVIRONMENT=web + -O3 + --llvm-lto 1 + ) diff --git a/lorgar/lib/em/decoder.cpp b/lorgar/lib/em/decoder.cpp new file mode 100644 index 0000000..8ee7e74 --- /dev/null +++ b/lorgar/lib/em/decoder.cpp @@ -0,0 +1,347 @@ +#include "decoder.h" +#include + +Decoder::Decoder(): +state(empty), +sampleRate(0), +channels(0), +cachedLength(0), +samplesPerFrame(0), +glue(new uint8_t[GLUE_LENGTH]), +cachedNext(NULL), +cachedThis(NULL), +cachedError(MAD_ERROR_NONE), +cached(false), +synth(new mad_synth()), +stream(new mad_stream()), +frame(new mad_frame()), +context(0), +pending() +{ + for (int i = 0; i < GLUE_LENGTH; ++i) { + glue[i] = 0; + } + + mad_frame_init(frame); + mad_stream_init(stream); + mad_synth_init(synth); + + emscripten::val AudioContext = emscripten::val::global("AudioContext"); + if (!AudioContext.as()) { + AudioContext = emscripten::val::global("webkitAudioContext"); + } + + context = AudioContext.new_(); +} + +Decoder::~Decoder() +{ + context.call("close"); + + mad_synth_finish(synth); + mad_stream_finish(stream); + mad_frame_finish(frame); + + delete synth; + delete stream; + delete frame; + + delete[] glue; +} + +void Decoder::addFragment(const emscripten::val& array) +{ + uint32_t length = array["length"].as(); + + if (length < GLUE_LENGTH / 2) { + std::cout << "Error: an attempt to add fragment smaller then half of the glue buffer, ignoring"; + return; + } + uint8_t* buffer = new uint8_t[length]; + + for (int i = 0; i < length; ++i) { + buffer[i] = array[std::to_string(i)].as(); + } + + RawBuffer rb = {buffer, length}; + pending.push_back(rb); + + switch (state) { + case empty: + mad_stream_buffer(stream, buffer, length); + + for (int i = 0; i < GLUE_LENGTH/2; ++i) { + glue[i] = buffer[length - GLUE_LENGTH/2 + i]; + } + + state = onBufferHalf; + prepareNextBuffer(); + break; + case onBufferHalf: + for (int i = 0; i < GLUE_LENGTH/2; ++i) { + glue[GLUE_LENGTH/2 + i] = buffer[i]; + } + + state = onBufferFull; + break; + case onBufferFull: + break; + case onGlueHalf: + for (int i = 0; i < GLUE_LENGTH/2; ++i) { + glue[GLUE_LENGTH/2 + i] = buffer[i]; + } + + state = onGlueFull; + cached = false; + prepareNextBuffer(); + break; + case onGlueFull: + break; + } +} + +emscripten::val Decoder::decode(uint32_t count) +{ + emscripten::val ret = emscripten::val::undefined(); + + int available = framesLeft(count); + int success = 0; + if (available > 0) { + ret = context.call("createBuffer", channels, available * samplesPerFrame, sampleRate); + + std::vector chans(channels, emscripten::val::undefined()); + for (int i = 0; i < channels; ++i) { + chans[i] = ret.call("getChannelData", i); + } + + for (int i = 0; success < available; ++i) { + int res = mad_frame_decode(frame, stream); + + if (res != 0) { + if (MAD_RECOVERABLE(stream->error)) { + + std::cout << "Unexpected error during the decoding process: " << mad_stream_errorstr(stream) << std::endl; + continue; + } else { + break; + } + } + + mad_synth_frame(synth, frame); + + for (int j = 0; j < samplesPerFrame; ++j) { + for (int k = 0; k < channels; ++k) { + float value = mad_f_todouble(synth->pcm.samples[k][j]); + chans[k].set(std::to_string(success * samplesPerFrame + j), emscripten::val(value)); + } + } + ++success; + } + + cachedLength -= available; +#if DEBUGGING + std::cout << "Processed " << available << " frames, " << success << " successfully, last error " << mad_stream_errorstr(stream) << std::endl; +#endif + if (cachedLength == 0) { + cached = false; + prepareNextBuffer(); + } + } + + return ret; +} + +bool Decoder::hasMore() const +{ + if (pending.size() == 1) { + return stream->error != MAD_ERROR_BUFLEN; + } else { + return true; + } +} + +uint32_t Decoder::framesLeft(uint32_t max) +{ + if (state == empty || state == onGlueHalf) { + return 0; + } + + if (cached == false) { + mad_stream probe; + mad_header ph; + initializeProbe(probe); + mad_header_init(&ph); + sampleRate = 0; + + while (cachedLength < max) { + if (mad_header_decode(&ph, &probe) == 0) { + if (sampleRate == 0) { + sampleRate = ph.samplerate; + channels = MAD_NCHANNELS(&ph); + samplesPerFrame = MAD_NSBSAMPLES(&ph) * 32; //not sure why 32, it's in libmad source + } else { + if (sampleRate != ph.samplerate || channels != MAD_NCHANNELS(&ph) || samplesPerFrame != MAD_NSBSAMPLES(&ph) * 32) { + if (cachedLength > 0) { +#if DEBUGGING + std::cout << "sample rate " << sampleRate << " -> " << ph.samplerate << std::endl; + std::cout << "channels " << channels << " -> " << MAD_NCHANNELS(&ph) << std::endl; + std::cout << "samples per frame " << samplesPerFrame << " -> " << MAD_NSBSAMPLES(&ph) * 32 << std::endl; +#endif + probe.next_frame = probe.this_frame; + break; + } + } + } + if (probe.next_frame > probe.this_frame) { + ++cachedLength; + } + } else { +#if DEBUGGING + std::cout << "framesLeft error: " << mad_stream_errorstr(&probe) << std::endl; +#endif + if (!MAD_RECOVERABLE(probe.error)) { + break; + } + } + } + + cachedNext = probe.next_frame; + cachedThis = probe.this_frame; + cachedError = probe.error; + mad_header_finish(&ph); + mad_stream_finish(&probe); +#if DEBUGGING + std::cout << cachedLength << " frames are available for decoding" << std::endl; +#endif + cached = true; + } + + return std::min(cachedLength, max); +} + +void Decoder::pullBuffer() +{ + if (cached == false) { + std::cout << "Error in pullBuffer method!" << std::endl; + } + stream->this_frame = cachedThis; + stream->next_frame = cachedNext; + stream->error = cachedError; +} + +void Decoder::changeBuffer() +{ + switch (state) { + case empty: + std::cout << "Wrong state on switchBuffer method - empty, aborting" << std::endl; + case onBufferHalf: + switchToGlue(); + state = onGlueHalf; + break; + case onBufferFull: + switchToGlue(); + state = onGlueFull; + break; + case onGlueHalf: + std::cout << "Wrong state on switchBuffer method - onGlueHalf, aborting" << std::endl; + break; + case onGlueFull: +#if DEBUGGING + std::cout << "Having another fragment " << pending[0].length << " bytes long" << std::endl; +#endif + + switchBuffer(pending[0].ptr, pending[0].length); + + for (int i = 0; i < GLUE_LENGTH/2; ++i) { + glue[i] = pending[0].ptr[pending[0].length - GLUE_LENGTH/2 + i]; + } + + state = onBufferHalf; + + if (pending.size() > 1) { + for (int i = 0; i < GLUE_LENGTH/2; ++i) { + glue[GLUE_LENGTH/2 + i] = pending[1].ptr[i]; + } + + state = onBufferFull; + } + } + + cached = false; +} + +void Decoder::prepareNextBuffer() +{ + bool shift; + do { + shift = false; + framesLeft(); + if (cachedLength == 0 && state != empty && state != onGlueHalf) { + pullBuffer(); + changeBuffer(); + shift = true; + } + } while (shift); +} + +void Decoder::initializeProbe(mad_stream& probe) +{ + mad_stream_init(&probe); + + probe.buffer = stream->buffer; + probe.bufend = stream->bufend; + probe.skiplen = stream->skiplen; + //probe.sync = stream->sync; + //probe.freerate = stream->freerate; + //probe.this_frame = stream->this_frame; + probe.next_frame = stream->next_frame; + //probe.ptr.byte = stream->ptr.byte; + //probe.ptr.cache = stream->ptr.cache; + //probe.ptr.cache = stream->ptr.cache; + //probe.anc_ptr.byte = stream->anc_ptr.byte; + //probe.anc_ptr.cache = stream->anc_ptr.cache; + //probe.anc_ptr.cache = stream->anc_ptr.cache; + //probe.anc_bitlen = stream->anc_bitlen; + //probe.main_data = stream.main_data; + //probe.md_len = stream.md_len; + //probe.options = stream->options; + //probe.error = stream->error; +} + +void Decoder::switchToGlue() +{ +#if DEBUGGING + std::cout << "Switching to glue" << std::endl; +#endif + switchBuffer(glue, GLUE_LENGTH); + +#if DEBUGGING + std::cout << "Freeing the drained fragment" << std::endl; +#endif + delete[] pending[0].ptr; + pending.pop_front(); +} + +void Decoder::switchBuffer(uint8_t* bufferPtr, uint32_t length) +{ + uint32_t left; + + if (stream->error != MAD_ERROR_BUFLEN) { + std::cout << "WARNING: Switching buffers while the previous one is not drained, last error: " << mad_stream_errorstr(stream) << std::endl; + } + if (stream->next_frame != NULL) { + left = stream->bufend - stream->next_frame; + } else { + std::cout << "WARNING: not supposed to happen" << std::endl; + } + + if (left > GLUE_LENGTH / 2) { + std::cout << "Error: bytes to read in the buffer are more then glue buffer can fit (" << left << ")" << std::endl; + throw 1; + } + + mad_stream_buffer(stream, bufferPtr + GLUE_LENGTH / 2 - left, length - (GLUE_LENGTH / 2 - left)); + stream->error = MAD_ERROR_NONE; + + while (mad_header_decode(&frame->header, stream) != 0 && stream->error != MAD_ERROR_BUFLEN) {} +} diff --git a/lorgar/lib/em/decoder.h b/lorgar/lib/em/decoder.h new file mode 100644 index 0000000..95d9a1d --- /dev/null +++ b/lorgar/lib/em/decoder.h @@ -0,0 +1,79 @@ +#ifndef DECODER_H +#define DECODER_H + +/** + * @todo write docs + */ +#include + +#include +#include +#include "mad.h" + +#define GLUE_LENGTH 6000 + +#define DEBUGGING false + +class Decoder { +public: + Decoder(); + ~Decoder(); + + void addFragment(const emscripten::val& array); + emscripten::val decode(uint32_t count = UINT32_MAX); + bool hasMore() const; + uint32_t framesLeft(uint32_t max = UINT32_MAX); + +private: + + enum State { + empty, + onBufferHalf, + onBufferFull, + onGlueHalf, + onGlueFull + }; + + struct RawBuffer { + uint8_t* ptr; + uint32_t length; + }; + + State state; + + uint32_t sampleRate; + uint8_t channels; + uint32_t cachedLength; + uint16_t samplesPerFrame; + uint8_t* glue; + uint8_t const* cachedNext; + uint8_t const* cachedThis; + mad_error cachedError; + bool cached; + + mad_synth* synth; + mad_stream* stream; + mad_frame* frame; + emscripten::val context; + + std::deque pending; + +private: + void pullBuffer(); + void changeBuffer(); + void prepareNextBuffer(); + void initializeProbe(mad_stream& probe); + void switchToGlue(); + void switchBuffer(uint8_t* bufferPtr, uint32_t length); +}; + +EMSCRIPTEN_BINDINGS(jsmad) { + emscripten::class_("Decoder") + .constructor<>() + .function("addFragment", &Decoder::addFragment) + .function("hasMore", &Decoder::hasMore) + .function("framesLeft", &Decoder::framesLeft) + .function("decode", &Decoder::decode); +} + +#endif // DECODER_H diff --git a/lorgar/lib/em/libmad.bc b/lorgar/lib/em/libmad.bc new file mode 100644 index 0000000..6e9d1e6 Binary files /dev/null and b/lorgar/lib/em/libmad.bc differ diff --git a/lorgar/lib/em/mad.h b/lorgar/lib/em/mad.h new file mode 100644 index 0000000..f54a0d0 --- /dev/null +++ b/lorgar/lib/em/mad.h @@ -0,0 +1,964 @@ +/* + * libmad - MPEG audio decoder library + * Copyright (C) 2000-2004 Underbit Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * If you would like to negotiate alternate licensing terms, you may do + * so by contacting: Underbit Technologies, Inc. + */ + +# ifdef __cplusplus +extern "C" { +# endif + +# define FPM_64BIT + + + +# define SIZEOF_INT 4 +# define SIZEOF_LONG 4 +# define SIZEOF_LONG_LONG 8 + + +/* Id: version.h,v 1.26 2004/01/23 09:41:33 rob Exp */ + +# ifndef LIBMAD_VERSION_H +# define LIBMAD_VERSION_H + +# define MAD_VERSION_MAJOR 0 +# define MAD_VERSION_MINOR 15 +# define MAD_VERSION_PATCH 1 +# define MAD_VERSION_EXTRA " (beta)" + +# define MAD_VERSION_STRINGIZE(str) #str +# define MAD_VERSION_STRING(num) MAD_VERSION_STRINGIZE(num) + +# define MAD_VERSION MAD_VERSION_STRING(MAD_VERSION_MAJOR) "." \ + MAD_VERSION_STRING(MAD_VERSION_MINOR) "." \ + MAD_VERSION_STRING(MAD_VERSION_PATCH) \ + MAD_VERSION_EXTRA + +# define MAD_PUBLISHYEAR "2000-2004" +# define MAD_AUTHOR "Underbit Technologies, Inc." +# define MAD_EMAIL "info@underbit.com" + +extern char const mad_version[]; +extern char const mad_copyright[]; +extern char const mad_author[]; +extern char const mad_build[]; + +# endif + +/* Id: fixed.h,v 1.38 2004/02/17 02:02:03 rob Exp */ + +# ifndef LIBMAD_FIXED_H +# define LIBMAD_FIXED_H + +# if SIZEOF_INT >= 4 +typedef signed int mad_fixed_t; + +typedef signed int mad_fixed64hi_t; +typedef unsigned int mad_fixed64lo_t; +# else +typedef signed long mad_fixed_t; + +typedef signed long mad_fixed64hi_t; +typedef unsigned long mad_fixed64lo_t; +# endif + +# if defined(_MSC_VER) +# define mad_fixed64_t signed __int64 +# elif 1 || defined(__GNUC__) +# define mad_fixed64_t signed long long +# endif + +# if defined(FPM_FLOAT) +typedef double mad_sample_t; +# else +typedef mad_fixed_t mad_sample_t; +# endif + +/* + * Fixed-point format: 0xABBBBBBB + * A == whole part (sign + 3 bits) + * B == fractional part (28 bits) + * + * Values are signed two's complement, so the effective range is: + * 0x80000000 to 0x7fffffff + * -8.0 to +7.9999999962747097015380859375 + * + * The smallest representable value is: + * 0x00000001 == 0.0000000037252902984619140625 (i.e. about 3.725e-9) + * + * 28 bits of fractional accuracy represent about + * 8.6 digits of decimal accuracy. + * + * Fixed-point numbers can be added or subtracted as normal + * integers, but multiplication requires shifting the 64-bit result + * from 56 fractional bits back to 28 (and rounding.) + * + * Changing the definition of MAD_F_FRACBITS is only partially + * supported, and must be done with care. + */ + +# define MAD_F_FRACBITS 28 + +# if MAD_F_FRACBITS == 28 +# define MAD_F(x) ((mad_fixed_t) (x##L)) +# else +# if MAD_F_FRACBITS < 28 +# warning "MAD_F_FRACBITS < 28" +# define MAD_F(x) ((mad_fixed_t) \ + (((x##L) + \ + (1L << (28 - MAD_F_FRACBITS - 1))) >> \ + (28 - MAD_F_FRACBITS))) +# elif MAD_F_FRACBITS > 28 +# error "MAD_F_FRACBITS > 28 not currently supported" +# define MAD_F(x) ((mad_fixed_t) \ + ((x##L) << (MAD_F_FRACBITS - 28))) +# endif +# endif + +# define MAD_F_MIN ((mad_fixed_t) -0x80000000L) +# define MAD_F_MAX ((mad_fixed_t) +0x7fffffffL) + +# define MAD_F_ONE MAD_F(0x10000000) + +# define mad_f_tofixed(x) ((mad_fixed_t) \ + ((x) * (double) (1L << MAD_F_FRACBITS) + 0.5)) +# define mad_f_todouble(x) ((double) \ + ((x) / (double) (1L << MAD_F_FRACBITS))) + +# define mad_f_intpart(x) ((x) >> MAD_F_FRACBITS) +# define mad_f_fracpart(x) ((x) & ((1L << MAD_F_FRACBITS) - 1)) + /* (x should be positive) */ + +# define mad_f_fromint(x) ((x) << MAD_F_FRACBITS) + +# define mad_f_add(x, y) ((x) + (y)) +# define mad_f_sub(x, y) ((x) - (y)) + +# if defined(FPM_FLOAT) +# error "FPM_FLOAT not yet supported" + +# undef MAD_F +# define MAD_F(x) mad_f_todouble(x) + +# define mad_f_mul(x, y) ((x) * (y)) +# define mad_f_scale64 + +# undef ASO_ZEROCHECK + +# elif defined(FPM_64BIT) + +/* + * This version should be the most accurate if 64-bit types are supported by + * the compiler, although it may not be the most efficient. + */ +# if defined(OPT_ACCURACY) +# define mad_f_mul(x, y) \ + ((mad_fixed_t) \ + ((((mad_fixed64_t) (x) * (y)) + \ + (1L << (MAD_F_SCALEBITS - 1))) >> MAD_F_SCALEBITS)) +# else +# define mad_f_mul(x, y) \ + ((mad_fixed_t) (((mad_fixed64_t) (x) * (y)) >> MAD_F_SCALEBITS)) +# endif + +# define MAD_F_SCALEBITS MAD_F_FRACBITS + +/* --- Intel --------------------------------------------------------------- */ + +# elif defined(FPM_INTEL) + +# if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable: 4035) /* no return value */ +static __forceinline +mad_fixed_t mad_f_mul_inline(mad_fixed_t x, mad_fixed_t y) +{ + enum { + fracbits = MAD_F_FRACBITS + }; + + __asm { + mov eax, x + imul y + shrd eax, edx, fracbits + } + + /* implicit return of eax */ +} +# pragma warning(pop) + +# define mad_f_mul mad_f_mul_inline +# define mad_f_scale64 +# else +/* + * This Intel version is fast and accurate; the disposition of the least + * significant bit depends on OPT_ACCURACY via mad_f_scale64(). + */ +# define MAD_F_MLX(hi, lo, x, y) \ + asm ("imull %3" \ + : "=a" (lo), "=d" (hi) \ + : "%a" (x), "rm" (y) \ + : "cc") + +# if defined(OPT_ACCURACY) +/* + * This gives best accuracy but is not very fast. + */ +# define MAD_F_MLA(hi, lo, x, y) \ + ({ mad_fixed64hi_t __hi; \ + mad_fixed64lo_t __lo; \ + MAD_F_MLX(__hi, __lo, (x), (y)); \ + asm ("addl %2,%0\n\t" \ + "adcl %3,%1" \ + : "=rm" (lo), "=rm" (hi) \ + : "r" (__lo), "r" (__hi), "0" (lo), "1" (hi) \ + : "cc"); \ + }) +# endif /* OPT_ACCURACY */ + +# if defined(OPT_ACCURACY) +/* + * Surprisingly, this is faster than SHRD followed by ADC. + */ +# define mad_f_scale64(hi, lo) \ + ({ mad_fixed64hi_t __hi_; \ + mad_fixed64lo_t __lo_; \ + mad_fixed_t __result; \ + asm ("addl %4,%2\n\t" \ + "adcl %5,%3" \ + : "=rm" (__lo_), "=rm" (__hi_) \ + : "0" (lo), "1" (hi), \ + "ir" (1L << (MAD_F_SCALEBITS - 1)), "ir" (0) \ + : "cc"); \ + asm ("shrdl %3,%2,%1" \ + : "=rm" (__result) \ + : "0" (__lo_), "r" (__hi_), "I" (MAD_F_SCALEBITS) \ + : "cc"); \ + __result; \ + }) +# elif defined(OPT_INTEL) +/* + * Alternate Intel scaling that may or may not perform better. + */ +# define mad_f_scale64(hi, lo) \ + ({ mad_fixed_t __result; \ + asm ("shrl %3,%1\n\t" \ + "shll %4,%2\n\t" \ + "orl %2,%1" \ + : "=rm" (__result) \ + : "0" (lo), "r" (hi), \ + "I" (MAD_F_SCALEBITS), "I" (32 - MAD_F_SCALEBITS) \ + : "cc"); \ + __result; \ + }) +# else +# define mad_f_scale64(hi, lo) \ + ({ mad_fixed_t __result; \ + asm ("shrdl %3,%2,%1" \ + : "=rm" (__result) \ + : "0" (lo), "r" (hi), "I" (MAD_F_SCALEBITS) \ + : "cc"); \ + __result; \ + }) +# endif /* OPT_ACCURACY */ + +# define MAD_F_SCALEBITS MAD_F_FRACBITS +# endif + +/* --- ARM ----------------------------------------------------------------- */ + +# elif defined(FPM_ARM) + +/* + * This ARM V4 version is as accurate as FPM_64BIT but much faster. The + * least significant bit is properly rounded at no CPU cycle cost! + */ +# if 1 +/* + * This is faster than the default implementation via MAD_F_MLX() and + * mad_f_scale64(). + */ +# define mad_f_mul(x, y) \ + ({ mad_fixed64hi_t __hi; \ + mad_fixed64lo_t __lo; \ + mad_fixed_t __result; \ + asm ("smull %0, %1, %3, %4\n\t" \ + "movs %0, %0, lsr %5\n\t" \ + "adc %2, %0, %1, lsl %6" \ + : "=&r" (__lo), "=&r" (__hi), "=r" (__result) \ + : "%r" (x), "r" (y), \ + "M" (MAD_F_SCALEBITS), "M" (32 - MAD_F_SCALEBITS) \ + : "cc"); \ + __result; \ + }) +# endif + +# define MAD_F_MLX(hi, lo, x, y) \ + asm ("smull %0, %1, %2, %3" \ + : "=&r" (lo), "=&r" (hi) \ + : "%r" (x), "r" (y)) + +# define MAD_F_MLA(hi, lo, x, y) \ + asm ("smlal %0, %1, %2, %3" \ + : "+r" (lo), "+r" (hi) \ + : "%r" (x), "r" (y)) + +# define MAD_F_MLN(hi, lo) \ + asm ("rsbs %0, %2, #0\n\t" \ + "rsc %1, %3, #0" \ + : "=r" (lo), "=r" (hi) \ + : "0" (lo), "1" (hi) \ + : "cc") + +# define mad_f_scale64(hi, lo) \ + ({ mad_fixed_t __result; \ + asm ("movs %0, %1, lsr %3\n\t" \ + "adc %0, %0, %2, lsl %4" \ + : "=&r" (__result) \ + : "r" (lo), "r" (hi), \ + "M" (MAD_F_SCALEBITS), "M" (32 - MAD_F_SCALEBITS) \ + : "cc"); \ + __result; \ + }) + +# define MAD_F_SCALEBITS MAD_F_FRACBITS + +/* --- MIPS ---------------------------------------------------------------- */ + +# elif defined(FPM_MIPS) + +/* + * This MIPS version is fast and accurate; the disposition of the least + * significant bit depends on OPT_ACCURACY via mad_f_scale64(). + */ +# define MAD_F_MLX(hi, lo, x, y) \ + asm ("mult %2,%3" \ + : "=l" (lo), "=h" (hi) \ + : "%r" (x), "r" (y)) + +# if defined(HAVE_MADD_ASM) +# define MAD_F_MLA(hi, lo, x, y) \ + asm ("madd %2,%3" \ + : "+l" (lo), "+h" (hi) \ + : "%r" (x), "r" (y)) +# elif defined(HAVE_MADD16_ASM) +/* + * This loses significant accuracy due to the 16-bit integer limit in the + * multiply/accumulate instruction. + */ +# define MAD_F_ML0(hi, lo, x, y) \ + asm ("mult %2,%3" \ + : "=l" (lo), "=h" (hi) \ + : "%r" ((x) >> 12), "r" ((y) >> 16)) +# define MAD_F_MLA(hi, lo, x, y) \ + asm ("madd16 %2,%3" \ + : "+l" (lo), "+h" (hi) \ + : "%r" ((x) >> 12), "r" ((y) >> 16)) +# define MAD_F_MLZ(hi, lo) ((mad_fixed_t) (lo)) +# endif + +# if defined(OPT_SPEED) +# define mad_f_scale64(hi, lo) \ + ((mad_fixed_t) ((hi) << (32 - MAD_F_SCALEBITS))) +# define MAD_F_SCALEBITS MAD_F_FRACBITS +# endif + +/* --- SPARC --------------------------------------------------------------- */ + +# elif defined(FPM_SPARC) + +/* + * This SPARC V8 version is fast and accurate; the disposition of the least + * significant bit depends on OPT_ACCURACY via mad_f_scale64(). + */ +# define MAD_F_MLX(hi, lo, x, y) \ + asm ("smul %2, %3, %0\n\t" \ + "rd %%y, %1" \ + : "=r" (lo), "=r" (hi) \ + : "%r" (x), "rI" (y)) + +/* --- PowerPC ------------------------------------------------------------- */ + +# elif defined(FPM_PPC) + +/* + * This PowerPC version is fast and accurate; the disposition of the least + * significant bit depends on OPT_ACCURACY via mad_f_scale64(). + */ +# define MAD_F_MLX(hi, lo, x, y) \ + do { \ + asm ("mullw %0,%1,%2" \ + : "=r" (lo) \ + : "%r" (x), "r" (y)); \ + asm ("mulhw %0,%1,%2" \ + : "=r" (hi) \ + : "%r" (x), "r" (y)); \ + } \ + while (0) + +# if defined(OPT_ACCURACY) +/* + * This gives best accuracy but is not very fast. + */ +# define MAD_F_MLA(hi, lo, x, y) \ + ({ mad_fixed64hi_t __hi; \ + mad_fixed64lo_t __lo; \ + MAD_F_MLX(__hi, __lo, (x), (y)); \ + asm ("addc %0,%2,%3\n\t" \ + "adde %1,%4,%5" \ + : "=r" (lo), "=r" (hi) \ + : "%r" (lo), "r" (__lo), \ + "%r" (hi), "r" (__hi) \ + : "xer"); \ + }) +# endif + +# if defined(OPT_ACCURACY) +/* + * This is slower than the truncating version below it. + */ +# define mad_f_scale64(hi, lo) \ + ({ mad_fixed_t __result, __round; \ + asm ("rotrwi %0,%1,%2" \ + : "=r" (__result) \ + : "r" (lo), "i" (MAD_F_SCALEBITS)); \ + asm ("extrwi %0,%1,1,0" \ + : "=r" (__round) \ + : "r" (__result)); \ + asm ("insrwi %0,%1,%2,0" \ + : "+r" (__result) \ + : "r" (hi), "i" (MAD_F_SCALEBITS)); \ + asm ("add %0,%1,%2" \ + : "=r" (__result) \ + : "%r" (__result), "r" (__round)); \ + __result; \ + }) +# else +# define mad_f_scale64(hi, lo) \ + ({ mad_fixed_t __result; \ + asm ("rotrwi %0,%1,%2" \ + : "=r" (__result) \ + : "r" (lo), "i" (MAD_F_SCALEBITS)); \ + asm ("insrwi %0,%1,%2,0" \ + : "+r" (__result) \ + : "r" (hi), "i" (MAD_F_SCALEBITS)); \ + __result; \ + }) +# endif + +# define MAD_F_SCALEBITS MAD_F_FRACBITS + +/* --- Default ------------------------------------------------------------- */ + +# elif defined(FPM_DEFAULT) + +/* + * This version is the most portable but it loses significant accuracy. + * Furthermore, accuracy is biased against the second argument, so care + * should be taken when ordering operands. + * + * The scale factors are constant as this is not used with SSO. + * + * Pre-rounding is required to stay within the limits of compliance. + */ +# if defined(OPT_SPEED) +# define mad_f_mul(x, y) (((x) >> 12) * ((y) >> 16)) +# else +# define mad_f_mul(x, y) ((((x) + (1L << 11)) >> 12) * \ + (((y) + (1L << 15)) >> 16)) +# endif + +/* ------------------------------------------------------------------------- */ + +# else +# error "no FPM selected" +# endif + +/* default implementations */ + +# if !defined(mad_f_mul) +# define mad_f_mul(x, y) \ + ({ register mad_fixed64hi_t __hi; \ + register mad_fixed64lo_t __lo; \ + MAD_F_MLX(__hi, __lo, (x), (y)); \ + mad_f_scale64(__hi, __lo); \ + }) +# endif + +# if !defined(MAD_F_MLA) +# define MAD_F_ML0(hi, lo, x, y) ((lo) = mad_f_mul((x), (y))) +# define MAD_F_MLA(hi, lo, x, y) ((lo) += mad_f_mul((x), (y))) +# define MAD_F_MLN(hi, lo) ((lo) = -(lo)) +# define MAD_F_MLZ(hi, lo) ((void) (hi), (mad_fixed_t) (lo)) +# endif + +# if !defined(MAD_F_ML0) +# define MAD_F_ML0(hi, lo, x, y) MAD_F_MLX((hi), (lo), (x), (y)) +# endif + +# if !defined(MAD_F_MLN) +# define MAD_F_MLN(hi, lo) ((hi) = ((lo) = -(lo)) ? ~(hi) : -(hi)) +# endif + +# if !defined(MAD_F_MLZ) +# define MAD_F_MLZ(hi, lo) mad_f_scale64((hi), (lo)) +# endif + +# if !defined(mad_f_scale64) +# if defined(OPT_ACCURACY) +# define mad_f_scale64(hi, lo) \ + ((((mad_fixed_t) \ + (((hi) << (32 - (MAD_F_SCALEBITS - 1))) | \ + ((lo) >> (MAD_F_SCALEBITS - 1)))) + 1) >> 1) +# else +# define mad_f_scale64(hi, lo) \ + ((mad_fixed_t) \ + (((hi) << (32 - MAD_F_SCALEBITS)) | \ + ((lo) >> MAD_F_SCALEBITS))) +# endif +# define MAD_F_SCALEBITS MAD_F_FRACBITS +# endif + +/* C routines */ + +mad_fixed_t mad_f_abs(mad_fixed_t); +mad_fixed_t mad_f_div(mad_fixed_t, mad_fixed_t); + +# endif + +/* Id: bit.h,v 1.12 2004/01/23 09:41:32 rob Exp */ + +# ifndef LIBMAD_BIT_H +# define LIBMAD_BIT_H + +struct mad_bitptr { + unsigned char const *byte; + unsigned short cache; + unsigned short left; +}; + +void mad_bit_init(struct mad_bitptr *, unsigned char const *); + +# define mad_bit_finish(bitptr) /* nothing */ + +unsigned int mad_bit_length(struct mad_bitptr const *, + struct mad_bitptr const *); + +# define mad_bit_bitsleft(bitptr) ((bitptr)->left) +unsigned char const *mad_bit_nextbyte(struct mad_bitptr const *); + +void mad_bit_skip(struct mad_bitptr *, unsigned int); +unsigned long mad_bit_read(struct mad_bitptr *, unsigned int); +void mad_bit_write(struct mad_bitptr *, unsigned int, unsigned long); + +unsigned short mad_bit_crc(struct mad_bitptr, unsigned int, unsigned short); + +# endif + +/* Id: timer.h,v 1.16 2004/01/23 09:41:33 rob Exp */ + +# ifndef LIBMAD_TIMER_H +# define LIBMAD_TIMER_H + +typedef struct { + signed long seconds; /* whole seconds */ + unsigned long fraction; /* 1/MAD_TIMER_RESOLUTION seconds */ +} mad_timer_t; + +extern mad_timer_t const mad_timer_zero; + +# define MAD_TIMER_RESOLUTION 352800000UL + +enum mad_units { + MAD_UNITS_HOURS = -2, + MAD_UNITS_MINUTES = -1, + MAD_UNITS_SECONDS = 0, + + /* metric units */ + + MAD_UNITS_DECISECONDS = 10, + MAD_UNITS_CENTISECONDS = 100, + MAD_UNITS_MILLISECONDS = 1000, + + /* audio sample units */ + + MAD_UNITS_8000_HZ = 8000, + MAD_UNITS_11025_HZ = 11025, + MAD_UNITS_12000_HZ = 12000, + + MAD_UNITS_16000_HZ = 16000, + MAD_UNITS_22050_HZ = 22050, + MAD_UNITS_24000_HZ = 24000, + + MAD_UNITS_32000_HZ = 32000, + MAD_UNITS_44100_HZ = 44100, + MAD_UNITS_48000_HZ = 48000, + + /* video frame/field units */ + + MAD_UNITS_24_FPS = 24, + MAD_UNITS_25_FPS = 25, + MAD_UNITS_30_FPS = 30, + MAD_UNITS_48_FPS = 48, + MAD_UNITS_50_FPS = 50, + MAD_UNITS_60_FPS = 60, + + /* CD audio frames */ + + MAD_UNITS_75_FPS = 75, + + /* video drop-frame units */ + + MAD_UNITS_23_976_FPS = -24, + MAD_UNITS_24_975_FPS = -25, + MAD_UNITS_29_97_FPS = -30, + MAD_UNITS_47_952_FPS = -48, + MAD_UNITS_49_95_FPS = -50, + MAD_UNITS_59_94_FPS = -60 +}; + +# define mad_timer_reset(timer) ((void) (*(timer) = mad_timer_zero)) + +int mad_timer_compare(mad_timer_t, mad_timer_t); + +# define mad_timer_sign(timer) mad_timer_compare((timer), mad_timer_zero) + +void mad_timer_negate(mad_timer_t *); +mad_timer_t mad_timer_abs(mad_timer_t); + +void mad_timer_set(mad_timer_t *, unsigned long, unsigned long, unsigned long); +void mad_timer_add(mad_timer_t *, mad_timer_t); +void mad_timer_multiply(mad_timer_t *, signed long); + +signed long mad_timer_count(mad_timer_t, enum mad_units); +unsigned long mad_timer_fraction(mad_timer_t, unsigned long); +void mad_timer_string(mad_timer_t, char *, char const *, + enum mad_units, enum mad_units, unsigned long); + +# endif + +/* Id: stream.h,v 1.20 2004/02/05 09:02:39 rob Exp */ + +# ifndef LIBMAD_STREAM_H +# define LIBMAD_STREAM_H + + +# define MAD_BUFFER_GUARD 8 +# define MAD_BUFFER_MDLEN (511 + 2048 + MAD_BUFFER_GUARD) + +enum mad_error { + MAD_ERROR_NONE = 0x0000, /* no error */ + + MAD_ERROR_BUFLEN = 0x0001, /* input buffer too small (or EOF) */ + MAD_ERROR_BUFPTR = 0x0002, /* invalid (null) buffer pointer */ + + MAD_ERROR_NOMEM = 0x0031, /* not enough memory */ + + MAD_ERROR_LOSTSYNC = 0x0101, /* lost synchronization */ + MAD_ERROR_BADLAYER = 0x0102, /* reserved header layer value */ + MAD_ERROR_BADBITRATE = 0x0103, /* forbidden bitrate value */ + MAD_ERROR_BADSAMPLERATE = 0x0104, /* reserved sample frequency value */ + MAD_ERROR_BADEMPHASIS = 0x0105, /* reserved emphasis value */ + + MAD_ERROR_BADCRC = 0x0201, /* CRC check failed */ + MAD_ERROR_BADBITALLOC = 0x0211, /* forbidden bit allocation value */ + MAD_ERROR_BADSCALEFACTOR = 0x0221, /* bad scalefactor index */ + MAD_ERROR_BADMODE = 0x0222, /* bad bitrate/mode combination */ + MAD_ERROR_BADFRAMELEN = 0x0231, /* bad frame length */ + MAD_ERROR_BADBIGVALUES = 0x0232, /* bad big_values count */ + MAD_ERROR_BADBLOCKTYPE = 0x0233, /* reserved block_type */ + MAD_ERROR_BADSCFSI = 0x0234, /* bad scalefactor selection info */ + MAD_ERROR_BADDATAPTR = 0x0235, /* bad main_data_begin pointer */ + MAD_ERROR_BADPART3LEN = 0x0236, /* bad audio data length */ + MAD_ERROR_BADHUFFTABLE = 0x0237, /* bad Huffman table select */ + MAD_ERROR_BADHUFFDATA = 0x0238, /* Huffman data overrun */ + MAD_ERROR_BADSTEREO = 0x0239 /* incompatible block_type for JS */ +}; + +# define MAD_RECOVERABLE(error) ((error) & 0xff00) + +struct mad_stream { + unsigned char const *buffer; /* input bitstream buffer */ + unsigned char const *bufend; /* end of buffer */ + unsigned long skiplen; /* bytes to skip before next frame */ + + int sync; /* stream sync found */ + unsigned long freerate; /* free bitrate (fixed) */ + + unsigned char const *this_frame; /* start of current frame */ + unsigned char const *next_frame; /* start of next frame */ + struct mad_bitptr ptr; /* current processing bit pointer */ + + struct mad_bitptr anc_ptr; /* ancillary bits pointer */ + unsigned int anc_bitlen; /* number of ancillary bits */ + + unsigned char (*main_data)[MAD_BUFFER_MDLEN]; + /* Layer III main_data() */ + unsigned int md_len; /* bytes in main_data */ + + int options; /* decoding options (see below) */ + enum mad_error error; /* error code (see above) */ +}; + +enum { + MAD_OPTION_IGNORECRC = 0x0001, /* ignore CRC errors */ + MAD_OPTION_HALFSAMPLERATE = 0x0002 /* generate PCM at 1/2 sample rate */ +# if 0 /* not yet implemented */ + MAD_OPTION_LEFTCHANNEL = 0x0010, /* decode left channel only */ + MAD_OPTION_RIGHTCHANNEL = 0x0020, /* decode right channel only */ + MAD_OPTION_SINGLECHANNEL = 0x0030 /* combine channels */ +# endif +}; + +void mad_stream_init(struct mad_stream *); +void mad_stream_finish(struct mad_stream *); + +# define mad_stream_options(stream, opts) \ + ((void) ((stream)->options = (opts))) + +void mad_stream_buffer(struct mad_stream *, + unsigned char const *, unsigned long); +void mad_stream_skip(struct mad_stream *, unsigned long); + +int mad_stream_sync(struct mad_stream *); + +char const *mad_stream_errorstr(struct mad_stream const *); + +# endif + +/* Id: frame.h,v 1.20 2004/01/23 09:41:32 rob Exp */ + +# ifndef LIBMAD_FRAME_H +# define LIBMAD_FRAME_H + + +enum mad_layer { + MAD_LAYER_I = 1, /* Layer I */ + MAD_LAYER_II = 2, /* Layer II */ + MAD_LAYER_III = 3 /* Layer III */ +}; + +enum mad_mode { + MAD_MODE_SINGLE_CHANNEL = 0, /* single channel */ + MAD_MODE_DUAL_CHANNEL = 1, /* dual channel */ + MAD_MODE_JOINT_STEREO = 2, /* joint (MS/intensity) stereo */ + MAD_MODE_STEREO = 3 /* normal LR stereo */ +}; + +enum mad_emphasis { + MAD_EMPHASIS_NONE = 0, /* no emphasis */ + MAD_EMPHASIS_50_15_US = 1, /* 50/15 microseconds emphasis */ + MAD_EMPHASIS_CCITT_J_17 = 3, /* CCITT J.17 emphasis */ + MAD_EMPHASIS_RESERVED = 2 /* unknown emphasis */ +}; + +struct mad_header { + enum mad_layer layer; /* audio layer (1, 2, or 3) */ + enum mad_mode mode; /* channel mode (see above) */ + int mode_extension; /* additional mode info */ + enum mad_emphasis emphasis; /* de-emphasis to use (see above) */ + + unsigned long bitrate; /* stream bitrate (bps) */ + unsigned int samplerate; /* sampling frequency (Hz) */ + + unsigned short crc_check; /* frame CRC accumulator */ + unsigned short crc_target; /* final target CRC checksum */ + + int flags; /* flags (see below) */ + int private_bits; /* private bits (see below) */ + + mad_timer_t duration; /* audio playing time of frame */ +}; + +struct mad_frame { + struct mad_header header; /* MPEG audio header */ + + int options; /* decoding options (from stream) */ + + mad_fixed_t sbsample[2][36][32]; /* synthesis subband filter samples */ + mad_fixed_t (*overlap)[2][32][18]; /* Layer III block overlap data */ +}; + +# define MAD_NCHANNELS(header) ((header)->mode ? 2 : 1) +# define MAD_NSBSAMPLES(header) \ + ((header)->layer == MAD_LAYER_I ? 12 : \ + (((header)->layer == MAD_LAYER_III && \ + ((header)->flags & MAD_FLAG_LSF_EXT)) ? 18 : 36)) + +enum { + MAD_FLAG_NPRIVATE_III = 0x0007, /* number of Layer III private bits */ + MAD_FLAG_INCOMPLETE = 0x0008, /* header but not data is decoded */ + + MAD_FLAG_PROTECTION = 0x0010, /* frame has CRC protection */ + MAD_FLAG_COPYRIGHT = 0x0020, /* frame is copyright */ + MAD_FLAG_ORIGINAL = 0x0040, /* frame is original (else copy) */ + MAD_FLAG_PADDING = 0x0080, /* frame has additional slot */ + + MAD_FLAG_I_STEREO = 0x0100, /* uses intensity joint stereo */ + MAD_FLAG_MS_STEREO = 0x0200, /* uses middle/side joint stereo */ + MAD_FLAG_FREEFORMAT = 0x0400, /* uses free format bitrate */ + + MAD_FLAG_LSF_EXT = 0x1000, /* lower sampling freq. extension */ + MAD_FLAG_MC_EXT = 0x2000, /* multichannel audio extension */ + MAD_FLAG_MPEG_2_5_EXT = 0x4000 /* MPEG 2.5 (unofficial) extension */ +}; + +enum { + MAD_PRIVATE_HEADER = 0x0100, /* header private bit */ + MAD_PRIVATE_III = 0x001f /* Layer III private bits (up to 5) */ +}; + +void mad_header_init(struct mad_header *); + +# define mad_header_finish(header) /* nothing */ + +int mad_header_decode(struct mad_header *, struct mad_stream *); + +void mad_frame_init(struct mad_frame *); +void mad_frame_finish(struct mad_frame *); + +int mad_frame_decode(struct mad_frame *, struct mad_stream *); + +void mad_frame_mute(struct mad_frame *); + +# endif + +/* Id: synth.h,v 1.15 2004/01/23 09:41:33 rob Exp */ + +# ifndef LIBMAD_SYNTH_H +# define LIBMAD_SYNTH_H + + +struct mad_pcm { + unsigned int samplerate; /* sampling frequency (Hz) */ + unsigned short channels; /* number of channels */ + unsigned short length; /* number of samples per channel */ + mad_fixed_t samples[2][1152]; /* PCM output samples [ch][sample] */ +}; + +struct mad_synth { + mad_fixed_t filter[2][2][2][16][8]; /* polyphase filterbank outputs */ + /* [ch][eo][peo][s][v] */ + + unsigned int phase; /* current processing phase */ + + struct mad_pcm pcm; /* PCM output */ +}; + +/* single channel PCM selector */ +enum { + MAD_PCM_CHANNEL_SINGLE = 0 +}; + +/* dual channel PCM selector */ +enum { + MAD_PCM_CHANNEL_DUAL_1 = 0, + MAD_PCM_CHANNEL_DUAL_2 = 1 +}; + +/* stereo PCM selector */ +enum { + MAD_PCM_CHANNEL_STEREO_LEFT = 0, + MAD_PCM_CHANNEL_STEREO_RIGHT = 1 +}; + +void mad_synth_init(struct mad_synth *); + +# define mad_synth_finish(synth) /* nothing */ + +void mad_synth_mute(struct mad_synth *); + +void mad_synth_frame(struct mad_synth *, struct mad_frame const *); + +# endif + +/* Id: decoder.h,v 1.17 2004/01/23 09:41:32 rob Exp */ + +# ifndef LIBMAD_DECODER_H +# define LIBMAD_DECODER_H + + +enum mad_decoder_mode { + MAD_DECODER_MODE_SYNC = 0, + MAD_DECODER_MODE_ASYNC +}; + +enum mad_flow { + MAD_FLOW_CONTINUE = 0x0000, /* continue normally */ + MAD_FLOW_STOP = 0x0010, /* stop decoding normally */ + MAD_FLOW_BREAK = 0x0011, /* stop decoding and signal an error */ + MAD_FLOW_IGNORE = 0x0020 /* ignore the current frame */ +}; + +struct mad_decoder { + enum mad_decoder_mode mode; + + int options; + + struct { + long pid; + int in; + int out; + } async; + + struct { + struct mad_stream stream; + struct mad_frame frame; + struct mad_synth synth; + } *sync; + + void *cb_data; + + enum mad_flow (*input_func)(void *, struct mad_stream *); + enum mad_flow (*header_func)(void *, struct mad_header const *); + enum mad_flow (*filter_func)(void *, + struct mad_stream const *, struct mad_frame *); + enum mad_flow (*output_func)(void *, + struct mad_header const *, struct mad_pcm *); + enum mad_flow (*error_func)(void *, struct mad_stream *, struct mad_frame *); + enum mad_flow (*message_func)(void *, void *, unsigned int *); +}; + +void mad_decoder_init(struct mad_decoder *, void *, + enum mad_flow (*)(void *, struct mad_stream *), + enum mad_flow (*)(void *, struct mad_header const *), + enum mad_flow (*)(void *, + struct mad_stream const *, + struct mad_frame *), + enum mad_flow (*)(void *, + struct mad_header const *, + struct mad_pcm *), + enum mad_flow (*)(void *, + struct mad_stream *, + struct mad_frame *), + enum mad_flow (*)(void *, void *, unsigned int *)); +int mad_decoder_finish(struct mad_decoder *); + +# define mad_decoder_options(decoder, opts) \ + ((void) ((decoder)->options = (opts))) + +int mad_decoder_run(struct mad_decoder *, enum mad_decoder_mode); +int mad_decoder_message(struct mad_decoder *, void *, unsigned int *); + +# endif + +# ifdef __cplusplus +} +# endif diff --git a/lorgar/lib/utils/CMakeLists.txt b/lorgar/lib/utils/CMakeLists.txt index b06c48f..c988215 100644 --- a/lorgar/lib/utils/CMakeLists.txt +++ b/lorgar/lib/utils/CMakeLists.txt @@ -2,4 +2,6 @@ cmake_minimum_required(VERSION 2.8.12) add_jslib(utils/class.js lib/utils/class ${LORGAR_DIR} browser) add_jslib(utils/subscribable.js lib/utils/subscribable ${LORGAR_DIR} browser) -add_jslib(utils/globalMethods.js lib/utils/globalMethods ${LORGAR_DIR} browser) \ No newline at end of file +add_jslib(utils/globalMethods.js lib/utils/globalMethods ${LORGAR_DIR} browser) +add_jslib(utils/enum.js lib/utils/enum ${LORGAR_DIR} browser) +add_jslib(utils/stateMachine.js lib/utils/stateMachine ${LORGAR_DIR} browser) diff --git a/lorgar/lib/wController/CMakeLists.txt b/lorgar/lib/wController/CMakeLists.txt index 79ca778..7ed96b3 100644 --- a/lorgar/lib/wController/CMakeLists.txt +++ b/lorgar/lib/wController/CMakeLists.txt @@ -16,4 +16,8 @@ add_jslib(wController/attributes.js lib/wController/attributes ${LORGAR_DIR} bro add_jslib(wController/localModel.js lib/wController/localModel ${LORGAR_DIR} browser) add_jslib(wController/imagePane.js lib/wController/imagePane ${LORGAR_DIR} browser) add_jslib(wController/file/file.js lib/wController/file/file ${LORGAR_DIR} browser) +add_jslib(wController/file/audio.js lib/wController/file/audio ${LORGAR_DIR} browser) add_jslib(wController/image.js lib/wController/image ${LORGAR_DIR} browser) +add_jslib(wController/button.js lib/wController/button ${LORGAR_DIR} browser) +add_jslib(wController/player.js lib/wController/player ${LORGAR_DIR} browser) +add_jslib(wController/imageById.js lib/wController/imageById ${LORGAR_DIR} browser) diff --git a/lorgar/main.js b/lorgar/main.js index 286a597..046eb99 100644 --- a/lorgar/main.js +++ b/lorgar/main.js @@ -1,5 +1,8 @@ "use strict"; (function main_js() { + requirejs.config({ + "baseUrl": "/" + }); requirejs.onError = function(e) { throw e; } @@ -8,6 +11,8 @@ defineArray.push("test/test"); defineArray.push("core/lorgar"); defineArray.push("lib/utils/globalMethods"); + defineArray.push("lib/em/wrapper"); + require(defineArray, function main_module() { require("lib/utils/globalMethods"); @@ -17,6 +22,8 @@ var Controller = require("lib/wController/controller"); var View = require("views/view"); + window.Mp3Decoder = Module.Decoder; + var waiter = { views: false, controllers: false, @@ -32,12 +39,14 @@ window.unregisterForeignController = window.lorgar.unregisterForeignController.bind(window.lorgar); window.subscribeForeignController = window.lorgar.subscribeForeignController.bind(window.lorgar); window.unsubscribeForeignController = window.lorgar.unsubscribeForeignController.bind(window.lorgar); + window.play = window.lorgar.play.bind(window.lorgar); + window.scheduleToPlay = window.lorgar.scheduleToPlay.bind(window.lorgar); } } } - Controller.initialize(["String", "List", "Vocabulary", "Page", "PanesList", "Link", "Image"], waiter.check.bind(waiter, "controllers")); - View.initialize(["Label", "Page", "PanesList", "Nav", "Image"], waiter.check.bind(waiter, "views")); + Controller.initialize(["String", "List", "Vocabulary", "Page", "PanesList", "Link", "Image", "Button"], waiter.check.bind(waiter, "controllers")); + View.initialize(["Label", "Page", "PanesList", "Nav", "Image", "Button", "Enumeration"], waiter.check.bind(waiter, "views")); var test = new Test(); test.run(); diff --git a/lorgar/views/CMakeLists.txt b/lorgar/views/CMakeLists.txt index c656448..aedfc2c 100644 --- a/lorgar/views/CMakeLists.txt +++ b/lorgar/views/CMakeLists.txt @@ -11,5 +11,10 @@ configure_file(mainLayout.js mainLayout.js) configure_file(page.js page.js) configure_file(pane.js pane.js) configure_file(image.js image.js) +configure_file(button.js button.js) +configure_file(enumeration.js enumeration.js) +configure_file(player.js player.js) +configure_file(slider.js slider.js) +configure_file(songProgress.js songProgress.js) add_subdirectory(helpers) diff --git a/lorgar/views/button.js b/lorgar/views/button.js new file mode 100644 index 0000000..db915f7 --- /dev/null +++ b/lorgar/views/button.js @@ -0,0 +1,98 @@ +"use strict"; +(function() { + var moduleName = "views/button"; + + var deps = []; + deps.push("views/layout"); + deps.push("views/label"); + + define(moduleName, deps, function() { + var Layout = require("views/layout"); + var Label = require("views/label"); + + var Button = Layout.inherit({ + "className": "Button", + "constructor": function(controller, options) { + var base = { + padding: 5 + }; + W.extend(base, options) + Layout.fn.constructor.call(this, controller, base); + + this.addClass("hoverable"); + this.addClass("button"); + this._enabled = true; + this._hasLabel = false; + this._e.addEventListener("click", this._onClick.bind(this), false); + + controller.on("setEnabled", this._onSetEnabled, this); + controller.on("setLabel", this._onSetLabel, this); + + this._onSetEnabled(controller.enabled); + this._onSetLabel(controller.hasLabel, controller.label); + }, + "destructor": function() { + this._f.off("setEnabled", this._onSetEnabled, this); + this._f.off("setLabel", this._onSetLabel, this); + + Layout.fn.destructor.call(this); + }, + "append": function(child, aligment, index) { + this._updateLimits(); + + Layout.fn.append.call(this, child, aligment, index); + }, + "_onChildChangeLimits": function(child) { + this._updateLimits(); + + Layout.fn._onChildChangeLimits.call(this, child); + }, + "_onClick": function(e) { + if (this._enabled) { + this._f.activate(); + } + e.stopPropagation(); + }, + "_onSetEnabled": function(enabled) { + if (this._enabled !== enabled) { + this._enabled = enabled; + if (this._enabled) { + this.removeClass("disabled"); + } else { + this.addClass("disabled"); + } + } + }, + "_onSetLabel": function(hasLabel, label) { + if (this._hasLabel !== hasLabel) { + this._hasLabel = hasLabel; + if (this._hasLabel) { + this._label = new Label(label); + this.append(this._label, Layout.Aligment.CenterCenter); + } else { + this._label.destructor(); + delete this._label(); + } + } + }, + "_updateLimits": function() { + var minWidth = this._o.padding * 2; + var maxWidth = this._o.padding * 2; + var minHeight = this._o.padding * 2; + var maxHeight = this._o.padding * 2; + + if (this._hasLabel) { + minWidth += this._label._o.minWidth; + minHeight += this._label._o.minHeight; + maxWidth += this._label._o.maxWidth; + maxHeight += this._label._o.maxHeight; + } + + this._setLimits(minWidth, minHeight, maxWidth, maxHeight); + } + }); + + return Button; + }) +})(); + diff --git a/lorgar/views/enumeration.js b/lorgar/views/enumeration.js new file mode 100644 index 0000000..3400372 --- /dev/null +++ b/lorgar/views/enumeration.js @@ -0,0 +1,53 @@ +"use strict"; +(function view_enumeration_js() { + var moduleName = "views/enumeration"; + + var deps = []; + deps.push("views/gridLayout"); + deps.push("views/label"); + deps.push("lib/wController/localModel"); + + define(moduleName, deps, function view_enumeration_module() { + var GridLayout = require("views/gridLayout"); + var Label = require("views/label"); + var LocalModel = require("lib/wController/localModel"); + + var Enumeration = GridLayout.inherit({ + className: "Enumeration", + constructor: function(controller, options) { + var base = {}; + W.extend(base, options) + + this._lm = new LocalModel(); + GridLayout.fn.constructor.call(this, controller, base); + + this._lv = new Label(this._lm); + this.append(this._lv, 0, 0, 1, 1, GridLayout.Aligment.CenterCenter); + + this._uncyclic.push(this._lm.destructor.bind(this._lm)); + }, + _onData: function() { + if (this._f.initialized) { + var e = this._f.enum; + var value = this._f.data; + var title; + if (e.hasAdditional("title")) { + title = e.additional[value].title; + } else { + title = e.reversed[value]; + } + + var desc = ""; + if (e.hasAdditional("description")) { + desc = e.additional[value].description; + } + + this._lm.setData(title); + this._e.setAttribute("title", desc); + } + } + }); + + return Enumeration; + }) +})(); diff --git a/lorgar/views/gridLayout.js b/lorgar/views/gridLayout.js index cb44ebb..712001c 100644 --- a/lorgar/views/gridLayout.js +++ b/lorgar/views/gridLayout.js @@ -250,20 +250,52 @@ var target = pos + span; var minSize = 0; var maxSize = 0; + var flexibleColls = []; for (j = pos; j < target; ++j) { minSize += this._cols[j].min; maxSize += this._cols[j].max; - } - if (e.child._o.minWidth > minSize) { - var portion = (e.child._o.minWidth - minSize) / span; - for (j = pos; j < target; ++j) { - this._cols[j].min += portion; + if (this._cols[j].min < this._cols[j].max) { + flexibleColls.push(this._cols[j]); } } - if (e.child._o.maxWidth < maxSize) { - var portion = (maxSize - e.child._o.maxWidth) / span; - for (j = pos; j < target; ++j) { - this._cols[j].max -= portion; + + var leftMin = e.child._o.minWidth - minSize; + if (leftMin > 0) { + while (leftMin > 0 && flexibleColls.length > 0) { + var portion = leftMin / flexibleColls.length; + + for (j = 0; j < flexibleColls.length; ++j) { + var lPortion = Math.min(flexibleColls[j].max, portion); + flexibleColls[j].min += lPortion; + leftMin -= lPortion; + if (flexibleColls[j].min === flexibleColls[j].max) { + flexibleColls.splice(j, 1); + break; + } + } + if (leftMin < 1) { + leftMin = 0; + } + } + } + var leftMax = maxSize - e.child._o.maxWidth + if (leftMax > 0) { + while (leftMax > 0 && flexibleColls.length > 0) { + var portion = leftMax / flexibleColls.length; + + for (j = 0; j < flexibleColls.length; ++j) { + var lPortion = Math.max(flexibleColls[j].min, portion); + flexibleColls[j].max -= lPortion; + leftMax -= lPortion; + if (flexibleColls[j].min === flexibleColls[j].max) { + flexibleColls.splice(j, 1); + break; + } + } + + if (leftMax < 1) { + leftMax = 0; + } } } } @@ -275,20 +307,52 @@ var target = pos + span; var minSize = 0; var maxSize = 0; + var flexibleRows = []; for (j = pos; j < target; ++j) { minSize += this._rows[j].min; maxSize += this._rows[j].max; - } - if (e.child._o.minHeight > minSize) { - var portion = (e.child._o.minHeight - minSize) / span; - for (j = pos; j < target; ++j) { - this._rows[j].min += portion; + if (this._rows[j].min < this._rows[j].max) { + flexibleRows.push(this._rows[j]); } } - if (e.child._o.maxHeight < maxSize) { - var portion = (maxSize - e.child._o.maxHeight) / span; - for (j = pos; j < target; ++j) { - this._rows[j].max -= portion; + var leftMin = e.child._o.minHeigh - minSize; + if (leftMin > 0) { + while (leftMin > 0 && flexibleRows.length > 0) { + var portion = leftMin / flexibleRows.length; + + for (j = 0; j < flexibleRows.length; ++j) { + var lPortion = Math.min(flexibleRows[j].max, portion); + flexibleRows[j].min += lPortion; + leftMin -= lPortion; + if (flexibleRows[j].min === flexibleRows[j].max) { + flexibleRows.splice(j, 1); + break; + } + } + + if (leftMin < 1) { + leftMin = 0; + } + } + } + var leftMax = maxSize - e.child._o.maxHeigh + if (leftMax > 0) { + while (leftMax > 0 && flexibleRows.length > 0) { + var portion = leftMax / flexibleRows.length; + + for (j = 0; j < flexibleRows.length; ++j) { + var lPortion = Math.max(flexibleRows[j].min, portion); + flexibleRows[j].max -= lPortion; + leftMax -= lPortion; + if (flexibleRows[j].min === flexibleRows[j].max) { + flexibleRows.splice(j, 1); + break; + } + } + + if (leftMax < 1) { + leftMax = 0; + } } } } diff --git a/lorgar/views/helpers/draggable.js b/lorgar/views/helpers/draggable.js index 173b4ad..f55eca2 100644 --- a/lorgar/views/helpers/draggable.js +++ b/lorgar/views/helpers/draggable.js @@ -30,9 +30,9 @@ this._y = 0; this._e.addEventListener("mousedown", this._proxy.onMouseDown, false); - this._e.addEventListener("touchstart", this._touch, false); - this._e.addEventListener("touchmove", this._touch, false); - this._e.addEventListener("touchend", this._touch, false); + this._e.addEventListener("touchstart", W.touchToMouse, false); + this._e.addEventListener("touchmove", W.touchToMouse, false); + this._e.addEventListener("touchend", W.touchToMouse, false); }, "destructor": function () { if (this._dragging) { @@ -45,9 +45,9 @@ } this._e.removeEventListener("mousedown", this._proxy.onMouseDown); - this._e.removeEventListener("touchstart", this._touch); - this._e.removeEventListener("touchmove", this._touch); - this._e.removeEventListener("touchend", this._touch); + this._e.removeEventListener("touchstart", W.touchToMouse); + this._e.removeEventListener("touchmove", W.touchToMouse); + this._e.removeEventListener("touchend", W.touchToMouse); Subscribable.fn.destructor.call(this); }, @@ -105,41 +105,6 @@ this._v.trigger("dragEnd"); return false; } - }, - "_touch": function (e) { - e.preventDefault(); - if (e.touches.length > 1 || (e.type == "touchend" && e.touches.length > 0)) - return; - - var type = null; - var touch = null; - var src = e.currentTarget; - switch (e.type) { - case "touchstart": - type = "mousedown"; - touch = e.changedTouches[0]; - - break; - case "touchmove": - type = "mousemove"; - touch = e.changedTouches[0]; - src = window; - break; - case "touchend": - type = "mouseup"; - touch = e.changedTouches[0]; - src = window; - break; - } - - var event = new MouseEvent(type, { - button: 0, - screenX: touch.screenX, - screenY: touch.screenY, - clientX: touch.clientX, - clientY: touch.clientY - }); - src.dispatchEvent(event); } }); diff --git a/lorgar/views/layout.js b/lorgar/views/layout.js index c54feba..f41c45c 100644 --- a/lorgar/views/layout.js +++ b/lorgar/views/layout.js @@ -76,8 +76,12 @@ } } + if (this._w !== undefined && this._h !== undefined) { child.setSize(this._w, this._h); + index = index || this._c.length - 1; + var c = this._c[index]; + this._positionElement(c); } }, "clear": function() { diff --git a/lorgar/views/mainLayout.js b/lorgar/views/mainLayout.js index 8571ea9..89c2bda 100644 --- a/lorgar/views/mainLayout.js +++ b/lorgar/views/mainLayout.js @@ -5,19 +5,46 @@ var defineArray = []; defineArray.push("views/gridLayout"); defineArray.push("views/label"); + defineArray.push("views/view"); defineArray.push("views/navigationPanel"); defineArray.push("views/layout"); + defineArray.push("views/enumeration"); + defineArray.push("views/player"); defineArray.push("lib/wController/localModel"); define(moduleName, defineArray, function mainLayout_module() { var GridLayout = require("views/gridLayout"); var ViewLabel = require("views/label"); + var View = require("views/view"); var ViewNavigationPanel = require("views/navigationPanel"); var Layout = require("views/layout"); + var Enumeration = require("views/enumeration"); + var Player = require("views/player"); var LocalModel = require("lib/wController/localModel"); var MainLayout = GridLayout.inherit({ "className": "MainLayout", + "constructor": function(controller, options) { + GridLayout.fn.constructor.call(this, controller, options); + + this._statusBarPosition = 2; + this._player = undefined; + + this._mainColorHelper = new LocalModel({backgroundColor: "mainColor"}); + this._statusBarModel = new LocalModel({backgroundColor: "secondaryColor"}); + + this._uncyclic.push(this._statusBarModel.destructor.bind(this._statusBarModel)); + this._uncyclic.push(this._mainColorHelper.destructor.bind(this._mainColorHelper)); + + var spacerL = new View(this._mainColorHelper, {maxWidth: 50}); + var spacerR = new View(this._mainColorHelper, {maxWidth: 50}); + this.append(spacerL, 1, 0, 1, 1); + this.append(spacerR, 1, 2, 1, 1); + + this._statusBar = new GridLayout(this._statusBarModel); + this._statusBar.append(new View(this._statusBarModel), 0, 1, 1, 1); + this.append(this._statusBar, 3, 0, 1, 3); + }, "_onNewController": function(controller) { GridLayout.fn._onNewController.call(this, controller); @@ -25,13 +52,8 @@ switch (controller.name) { case "version": - var lm = new LocalModel({ - backgroundColor: "secondaryColor" - }); - var lay = new Layout(lm, {maxHeight: 15}) view = new ViewLabel(controller); - lay.append(view, Layout.Aligment.RightCenter); - this.append(lay, 2, 0, 1, 3); + this._statusBar.append(view, 0, 0, 1, 1, Layout.Aligment.LeftCenter); break; case "navigationPanel": view = new ViewNavigationPanel(controller); @@ -43,6 +65,31 @@ //this.trigger("serviceMessage", "Unsupported view: " + name + " (" + type + ")", 1); break; } + }, + addState: function(name, state) { + var lm = new LocalModel({fontFamily: "casualFont"}); + lm.setData(name + ": "); + var lv = new ViewLabel(lm); + var e = new Enumeration(state); + this._statusBar.append(lv, 0, this._statusBarPosition++, 1, 1, Layout.Aligment.LeftCenter); + this._statusBar.append(e, 0, this._statusBarPosition++, 1, 1, Layout.Aligment.LeftCenter); + + this._uncyclic.push(lm.destructor.bind(lm)); + }, + appendPlayer: function(playerModel) { + if (this._player !== undefined) { + throw new Error("An attempt to add player to main layout for the second time"); + } + this._player = new Player(playerModel); + this.append(this._player, 2, 0, 1, 3); + }, + removePlayer: function() { + if (this._player === undefined) { + throw new Error("An attempt to remove non existing player from mainLayout"); + } + this.removeChild(this._player); + this._player.destructor(); + this._player = undefined; } }); diff --git a/lorgar/views/pane.js b/lorgar/views/pane.js index 2e33e7c..e0fcf70 100644 --- a/lorgar/views/pane.js +++ b/lorgar/views/pane.js @@ -8,6 +8,7 @@ defineArray.push("views/layout"); defineArray.push("views/label"); defineArray.push("views/image"); + defineArray.push("views/button"); defineArray.push("lib/wController/localModel"); define(moduleName, defineArray, function() { @@ -15,6 +16,7 @@ var Layout = require("views/layout"); var Label = require("views/label"); var Image = require("views/image"); + var Button = require("views/button"); var LM = require("lib/wController/localModel"); var Pane = Layout.inherit({ @@ -25,6 +27,7 @@ W.extend(base, options); Layout.fn.constructor.call(this, controller, options); + this._aCount = 0; this._initProxy(); this.addClass("hoverable"); this._e.addEventListener("click", this._proxy.onClick, false); @@ -46,6 +49,12 @@ this._f.on("newElement", this._onNewElement, this); this._f.on("removeElement", this._onRemoveElement, this); + this._f.on("addAction", this._onAddAction, this); + + var acts = this._f.getActions(); + for (var i = 0; i < acts.length; ++i) { + this._onAddAction(acts[i]); + } this._uncyclic.push(function() { lm.destructor(); @@ -56,6 +65,31 @@ Layout.fn.destructor.call(this); }, + "_onAddAction": function(model) { + var alignment; + switch (this._aCount) { + case 0: + alignment = Layout.Aligment.LeftTop; + break; + case 1: + alignment = Layout.Aligment.RightTop; + break; + case 2: + alignment = Layout.Aligment.RightBottom; + break; + case 3: + alignment = Layout.Aligment.LeftBottom; + break; + default: + console.warn("Pane can't place more then 4 action, ignoring"); + break + } + if (alignment !== undefined) { + var view = new Button(model); + this.append(view, alignment); + this._aCount++; + } + }, "_applyProperties": function() { this._onAddProperty("secondaryColor", "background"); @@ -67,8 +101,8 @@ }; }, "_onClick": function() { - if (this._f.data.at("hasPageLink").valueOf() === true) { - this.trigger("activate", this._f.data.at("pageLink").clone()); + if (this._f.hasPageLink()) { + this.trigger("activate", this._f.getPageLink()); } }, "_onLabelChangeLimits": function(label) { diff --git a/lorgar/views/panesList.js b/lorgar/views/panesList.js index 08dac91..af62199 100644 --- a/lorgar/views/panesList.js +++ b/lorgar/views/panesList.js @@ -50,15 +50,13 @@ this._f.setSubscriptionRange(0, 0); }, "append": function(child, index) { - var model = new LM(); - var nest = new Layout(model, { + var nest = new Layout(helper, { minHeight: this._o.nestHeight, maxHeight: this._o.nestHeight, minWidth: this._o.nestWidth, minWidth: this._o.nestWidth, scrollable: Layout.Scroll.None }); - nest._uncyclic.push(function() {model.destructor()}); nest.append(child); child.on("activate", this._onChildActivate, this); //todo need to remove handler on deletion this._addChild(nest, 0, index); @@ -184,6 +182,8 @@ } }); + var helper = new LM(); + return PanesList; }); })(); diff --git a/lorgar/views/player.js b/lorgar/views/player.js new file mode 100644 index 0000000..4b42cb9 --- /dev/null +++ b/lorgar/views/player.js @@ -0,0 +1,163 @@ +"use strict"; +(function() { + var moduleName = "views/player" + + var deps = []; + deps.push("views/gridLayout"); + deps.push("views/button"); + deps.push("views/label"); + deps.push("views/view"); + deps.push("views/image"); + deps.push("views/songProgress"); + deps.push("views/slider"); + + deps.push("lib/wController/localModel"); + + deps.push("lib/utils/enum"); + + define(moduleName, deps, function() { + var GridLayout = require("views/gridLayout"); + var Button = require("views/button"); + var Label = require("views/label"); + var View = require("views/view"); + var Image = require("views/image"); + var SongProgress = require("views/songProgress"); + var Slider = require("views/slider"); + + var Model = require("lib/wController/localModel"); + + var Enum = require("lib/utils/enum"); + + var Player = GridLayout.inherit({ + className: "Player", + constructor: function(ctrl, options) { + GridLayout.fn.constructor.call(this, ctrl, options); + + this._playPause = null; + this._prev = null; + this._next = null; + this._picture = null; + this._cpbCtrl = null; + + this._infoModels = { + artist: new Model(null, "artist: unknown"), + album: new Model(null, "album: unknown"), + song: new Model(null, "song: unknown") + } + + ctrl.on("newElement", this._onNewElement, this); + ctrl.on("removeElement", this._onRemoveElement, this); + + var artist = new Label(this._infoModels.artist); + var album = new Label(this._infoModels.album); + var song = new Label(this._infoModels.song); + var spacer = new View(helper, {maxWidth: 10}); + var progress = new SongProgress(ctrl.progress); + var volume = new Slider(ctrl.volume, {maxWidth: 100}); + + this.append(artist, 0, 4, 1, 1, GridLayout.Aligment.LeftCenter); + this.append(song, 1, 4, 1, 1, GridLayout.Aligment.LeftCenter); + this.append(album, 2, 4, 1, 1, GridLayout.Aligment.LeftCenter); + this.append(spacer, 0, 6, 3, 1, GridLayout.Aligment.LeftCenter); + this.append(progress, 1, 5, 1, 1, GridLayout.Aligment.CenterCenter); + this.append(volume, 1, 7, 1, 1, GridLayout.Aligment.CenterCenter); + + this._uncyclic.push(this._infoModels.artist.destructor.bind(this._infoModels.artist)); + this._uncyclic.push(this._infoModels.song.destructor.bind(this._infoModels.song)); + this._uncyclic.push(this._infoModels.album.destructor.bind(this._infoModels.album)); + }, + destructor: function() { + this._f.off("newElement", this._onNewElement, this); + this._f.off("removeElement", this._onRemoveElement, this); + this._clearCpbCtrl(); + + GridLayout.fn.destructor.call(this); + }, + _clearCpbCtrl: function() { + if (this._cpbCtrl !== null) { + this._cpbCtrl.off("newElement", this._onCpbNewElement, this); + this._cpbCtrl.off("removeElement", this._onCpbRemoveElement, this); + this._cpbCtrl = null; + } + }, + _onNewElement: function(ctrl, type) { + var ItemType = Enum.storage["ItemType"]; + + switch (type) { + case ItemType.straight.playPause: + this._playPause = new Button(ctrl); + this.append(this._playPause, 0, 2, 3, 1); + break; + case ItemType.straight.prev: + this._prev = new Button(ctrl); + this.append(this._prev, 0, 1, 3, 1); + break; + case ItemType.straight.next: + this._next = new Button(ctrl); + this.append(this._next, 0, 3, 3, 1); + break; + case ItemType.straight.queue: + break; + case ItemType.straight.currentPlayback: + this._clearCpbCtrl(); + this._cpbCtrl = ctrl; + this._cpbCtrl.on("newElement", this._onCpbNewElement, this); + this._cpbCtrl.on("removeElement", this._onCpbRemoveElement, this); + break; + case ItemType.straight.picture: + this._picture = new Image(ctrl, { + maxWidth: 100, + maxHeight: 100 + }); + this.append(this._picture, 0, 0, 3, 1); + break; + } + }, + _onRemoveElement: function(type) { + var ItemType = Enum.storage["ItemType"]; + + switch (type) { + case ItemType.straight.playPause: + this._playPause.destructor(); + this._playPause = null; + break; + case ItemType.straight.prev: + this._prev.destructor(); + this._prev = null; + break; + case ItemType.straight.next: + this._next.destructor(); + this._next = null; + break; + case ItemType.straight.queue: + break; + case ItemType.straight.currentPlayback: + this._clearCpbCtrl(); + break; + case ItemType.straight.picture: + this._picture.destructor(); + this._picture = null; + break; + } + }, + _onCpbNewElement: function(key, value) { + var model = this._infoModels[key]; + + if (model) { + model.setData(key + ": " + value.toString()); + } + }, + _onCpbRemoveElement: function(key) { + var model = this._infoModels[key]; + + if (model) { + model.setData(key + ": unknown"); + } + } + }); + + var helper = new Model(); + + return Player; + }); +})() diff --git a/lorgar/views/slider.js b/lorgar/views/slider.js new file mode 100644 index 0000000..a8b12b6 --- /dev/null +++ b/lorgar/views/slider.js @@ -0,0 +1,142 @@ +"use strict"; +(function() { + var moduleName = "views/slider"; + + var deps = []; + deps.push("views/view"); + + define(moduleName, deps, function() { + var View = require("views/view"); + + var Slider = View.inherit({ + className: "Slider", + constructor: function(controller, options) { + var base = { + minHeight: 10, + maxHeight: 10 + }; + W.extend(base, options) + var el = document.createElement("div"); + this._createBars(); + View.fn.constructor.call(this, controller, base, el); + + this._seeking = false; + this._createProxy(); + + this._f.on("value", this._onValue, this); + this._f.on("enabled", this._onEnabled, this); + + this._e.style.backgroundColor = "#eeeeee"; + this._e.appendChild(this._value); + + if (this._f.enabled) { + this._e.addEventListener("mousedown", this._proxy.onMouseDown, false); + this._e.addEventListener("touchstart", W.touchToMouse, false); + this._e.addEventListener("touchmove", W.touchToMouse, false); + this._e.addEventListener("touchend", W.touchToMouse, false); + } + }, + destructor: function() { + if (this._seeking) { + window.removeEventListener("mouseup", this._proxy.onMouseUp); + window.removeEventListener("mousemove", this._proxy.onMouseMove); + lorgar._body.removeClass("dragging"); + lorgar._body.removeClass("non-selectable"); + } + if (this._f.enabled) { + this._e.removeEventListener("mousedown", this._proxy.onMouseDown); + this._e.removeEventListener("touchstart", W.touchToMouse); + this._e.removeEventListener("touchmove", W.touchToMouse); + this._e.removeEventListener("touchend", W.touchToMouse); + } + + this._f.off("value", this._value, this); + this._f.off("enabled", this._onEnabled, this); + + View.fn.destructor.call(this); + }, + _createBars: function() { + this._value = document.createElement("div"); + + this._value.style.backgroundColor = View.theme.primaryColor || "#ff0000"; + this._value.style.height = "100%"; + this._value.style.width = "0"; + this._value.style.position = "absolute"; + this._value.style.top = "0"; + this._value.style.left = "0"; + }, + _createProxy: function () { + this._proxy = { + onMouseDown: this._onMouseDown.bind(this), + onMouseUp: this._onMouseUp.bind(this), + onMouseMove: this._onMouseMove.bind(this) + } + }, + _onData: function() { + this._onValue(this._f.value); + }, + _onEnabled: function(enabled) { + if (enabled) { + this._e.addEventListener("mousedown", this._proxy.onMouseDown, false); + this._e.addEventListener("touchstart", W.touchToMouse, false); + this._e.addEventListener("touchmove", W.touchToMouse, false); + this._e.addEventListener("touchend", W.touchToMouse, false); + } else { + if (this._seeking) { + this._onMouseUp(); + } + + this._e.removeEventListener("mousedown", this._proxy.onMouseDown); + this._e.removeEventListener("touchstart", W.touchToMouse); + this._e.removeEventListener("touchmove", W.touchToMouse); + this._e.removeEventListener("touchend", W.touchToMouse); + } + }, + _onMouseDown: function(e) { + if (e.which === 1) { + window.addEventListener("mouseup", this._proxy.onMouseUp); + window.addEventListener("mousemove", this._proxy.onMouseMove); + lorgar._body.addClass("dragging"); + lorgar._body.addClass("non-selectable"); + this._seeking = true; + + this._ap = this.getAbsolutePosition(); + var seek = Math.max(Math.min(e.pageX - this._ap.x, this._w), 0); + var nSeek = seek / this._w; + if (this._seek !== nSeek) { + this._seek = nSeek; + this._f.setValue(this._seek); + } + } + }, + _onMouseMove: function(e) { + var seek = Math.max(Math.min(e.pageX - this._ap.x, this._w), 0); + var nSeek = seek / this._w; + if (this._seek !== nSeek) { + this._seek = nSeek; + this._f.setValue(this._seek); + } + }, + _onMouseUp: function() { + delete this._ap; + delete this._seek; + + this._seeking = false; + window.removeEventListener("mouseup", this._proxy.onMouseUp); + window.removeEventListener("mousemove", this._proxy.onMouseMove); + lorgar._body.removeClass("dragging"); + }, + _onValue: function(pb) { + this._value.style.width = pb * 100 + "%"; + }, + _resetTheme: function() { + View.fn._resetTheme.call(this); + + this._value.style.backgroundColor = View.theme.primaryColor || "#ff0000"; + } + }); + + return Slider; + }) +})(); + diff --git a/lorgar/views/songProgress.js b/lorgar/views/songProgress.js new file mode 100644 index 0000000..0639b07 --- /dev/null +++ b/lorgar/views/songProgress.js @@ -0,0 +1,70 @@ +"use strict"; +(function() { + var moduleName = "views/songProgress"; + + var deps = []; + deps.push("views/slider"); + + define(moduleName, deps, function() { + var Slider = require("views/slider"); + + var SongProgress = Slider.inherit({ + className: "SongProgress", + constructor: function(controller, options) { + var base = { + minHeight: 10, + maxHeight: 10 + }; + W.extend(base, options) + Slider.fn.constructor.call(this, controller, base); + + this._f.on("load", this._onLoad, this); + + this._e.insertBefore(this._loadProgress, this._value); + }, + destructor: function() { + this._f.off("load", this._onLoad, this); + + Slider.fn.destructor.call(this); + }, + _createBars: function() { + Slider.fn._createBars.call(this); + + this._loadProgress = document.createElement("div"); + + this._loadProgress.style.backgroundColor = Slider.theme.secondaryColor || "#ffff00"; + this._loadProgress.style.height = "100%"; + this._loadProgress.style.width = "0"; + this._loadProgress.style.position = "absolute"; + this._loadProgress.style.top = "0"; + this._loadProgress.style.left = "0"; + }, + _onData: function() { + Slider.fn._onData.call(this); + this._onLoad(this._f.load); + }, + _onLoad: function(load) { + this._loadProgress.style.width = load * 100 + "%"; + }, + _onMouseDown: function(e) { + if (e.which === 1) { + this._f.trigger("seekingStart"); + } + + Slider.fn._onMouseDown.call(this, e); + }, + _onMouseUp: function(e) { + Slider.fn._onMouseUp.call(this, e); + this._f.trigger("seekingEnd", this._f.value); + }, + _resetTheme: function() { + Slider.fn._resetTheme.call(this); + + this._loadProgress.style.backgroundColor = Slider.theme.secondaryColor || "#ffff00" + } + }); + + return SongProgress; + }) +})(); + diff --git a/lorgar/views/view.js b/lorgar/views/view.js index efe9125..cc3e745 100644 --- a/lorgar/views/view.js +++ b/lorgar/views/view.js @@ -54,7 +54,9 @@ for (var i = 0; i < this._f._controllers.length; ++i) { this._onNewController(this._f._controllers[i]); } - this._onData(this._f); + //if (this._f.initialized) { + this._onData(this._f); + //} View.collection[this._id] = this; this._applyProperties(); @@ -76,11 +78,7 @@ Subscribable.fn.destructor.call(this); }, "addClass": function(className) { - var arr = this._e.className.split(" "); - if (arr.indexOf(className) === -1) { - arr.push(className); - this._e.className = arr.join(" "); - } + this._e.classList.add(className); }, "_applyProperties": function() { for (var i = 0; i < this._f.properties.length; ++i) { @@ -100,6 +98,21 @@ return w; }, + "getAbsolutePosition": function() { + var pp; + if (this._p) { + pp = this._p.getAbsolutePosition(); + } else { + pp = Object.create(null); + pp.x = 0; + pp.y = 0; + } + + pp.x += this._x; + pp.y += this._y; + + return pp; + }, "_initDraggable": function() { this._dg = new Draggable(this, { snapDistance: this._o.snapDistance @@ -136,20 +149,11 @@ "remove": function() { if (this._p) { this._p.removeChild(this); + delete this._p; //just to make sure } }, "removeClass": function(className) { - var arr = this._e.className.split(" "); - var index = arr.indexOf(className) - var toJoin = false; - while (index !== -1) { - arr.splice(index, 1); - index = arr.indexOf(className) - toJoin = true; - } - if (toJoin) { - this._e.className = arr.join(" "); - } + this._e.classList.remove(className); }, "_resetTheme": function() { this._onClearProperties(); @@ -191,9 +195,12 @@ } if (needToTell) { this.trigger("changeLimits", this); + if (this._w !== undefined && this._h !== undefined) { + this.setSize(this._w, this._h); + } } - return needToTell && this._events.changeLimits && this._events.changeLimits.length; //to see if someone actually going to listen that event + return needToTell && this._events.changeLimits && this._events.changeLimits.length; //to see if someone actually going to listen that event, if not - return result }, "setMaxSize": function(w, h) { this._o.maxWidth = w; @@ -284,22 +291,29 @@ View.ViewType = { Label: 0, - Image: 3, - View: 4, + Image: 4, + Button: 5, + View: 6, + Enumeration: 7, Page: 102, - PanesList: 104 + PanesList: 104, + Player: 107 }; View.ReversedViewType = { "0": "Label", - "3": "Image", - "4": "View", + "4": "Image", + "5": "Button", + "6": "View", + "7": "Enumeration", "101": "Nav", "102": "Page", - "104": "PanesList" + "104": "PanesList", + + "107": "Player" }; View.ViewTypesPaths = { @@ -307,7 +321,10 @@ Nav: "views/nav", Page: "views/page", PanesList: "views/panesList", - Image: "views/image" + Image: "views/image", + Button: "views/button", + Player: "views/player", + Enumeration: "views/enumeration" }; View.constructors = { diff --git a/magnus/app.js b/magnus/app.js index 389ab9c..451dcfe 100644 --- a/magnus/app.js +++ b/magnus/app.js @@ -3,6 +3,8 @@ var morgan = require("morgan"); var favicon = require("serve-favicon"); var Magnus = require("./core/magnus"); +express.static.mime.types.wasm = 'application/wasm'; + require("./lib/utils/globalMethods"); var config = require("./config"); diff --git a/magnus/lib/log.js b/magnus/lib/log.js index 51778e4..f983ea7 100644 --- a/magnus/lib/log.js +++ b/magnus/lib/log.js @@ -5,7 +5,7 @@ var ENV = config.get('build'); function getLogger(module) { var path = module.filename.split('/').slice(-2).join('/'); - return new Winston.Logger({ + return Winston.createLogger({ transports: [ new Winston.transports.Console({ colorize: true, diff --git a/magnus/lib/wModel/CMakeLists.txt b/magnus/lib/wModel/CMakeLists.txt index 01c6b6a..fdff702 100644 --- a/magnus/lib/wModel/CMakeLists.txt +++ b/magnus/lib/wModel/CMakeLists.txt @@ -13,5 +13,6 @@ add_jslib(wModel/themeStorage.js lib/wModel/themeStorage ${MAGNUS_DIR} node) add_jslib(wModel/vocabulary.js lib/wModel/vocabulary ${MAGNUS_DIR} node) add_jslib(wModel/attributes.js lib/wModel/attributes ${MAGNUS_DIR} node) add_jslib(wModel/image.js lib/wModel/image ${MAGNUS_DIR} node) +add_jslib(wModel/button.js lib/wModel/button ${MAGNUS_DIR} node) add_subdirectory(proxy) diff --git a/magnus/lib/wModel/proxy/CMakeLists.txt b/magnus/lib/wModel/proxy/CMakeLists.txt index 2ef4f4b..14b2608 100644 --- a/magnus/lib/wModel/proxy/CMakeLists.txt +++ b/magnus/lib/wModel/proxy/CMakeLists.txt @@ -6,3 +6,4 @@ add_jslib(wModel/proxy/vocabulary.js lib/wModel/proxy/vocabulary ${MAGNUS_DIR} n add_jslib(wModel/proxy/catalogue.js lib/wModel/proxy/catalogue ${MAGNUS_DIR} node) configure_file(pane.js pane.js) +configure_file(panesList.js panesList.js) diff --git a/magnus/lib/wModel/proxy/pane.js b/magnus/lib/wModel/proxy/pane.js index 5bc8b91..f3c4e4a 100644 --- a/magnus/lib/wModel/proxy/pane.js +++ b/magnus/lib/wModel/proxy/pane.js @@ -4,12 +4,18 @@ var MVocabulary = require("./vocabulary"); var Address = require("../../wType/address"); var Boolean = require("../../wType/boolean"); +var Uint64 = require("../../wType/uint64"); +var String = require("../../wType/string"); +var Vocabulary = require("../../wType/vocabulary"); +var Vector = require("../../wType/vector"); var Pane = MVocabulary.inherit({ "className": "Pane", - "constructor": function(address, controllerAddress, socket) { + "constructor": function(address, controllerAddress, socket, actions) { MVocabulary.fn.constructor.call(this, address, controllerAddress, socket); + this._actions = new Vector(); + this._createActions(actions || []); if (this.constructor.pageAddress) { this.hasPageLink = true; @@ -17,14 +23,45 @@ var Pane = MVocabulary.inherit({ this._pageLink = this.constructor.pageAddress["+"](new Address([id.toString()])); } }, + "destructor": function() { + this._actions.destructor(); + if (this.hasPageLink) { + this._pageLink.destructor(); + } + + MVocabulary.fn.destructor.call(this); + }, + "_createActions": function(actions) { + for (var i = 0; i < actions.length; ++i) { + var action = actions[i]; + var actionVC = new Vocabulary(); + actionVC.insert("type", new Uint64(action.type)); + var supported = false; + + switch (action.type) { + case 0: //this type of actions has only action itself, no additional arguments, what to do is desctibed in the view + actionVC.insert("action", new String(action.action)); + supported = true; + break; + } + + if (supported) { + this._actions.push(actionVC); + } else { + actionVC.destructor(); + } + } + }, "_getAllData": function() { var vc = this.controller.data.clone(); vc.insert("hasPageLink", new Boolean(this.hasPageLink)); + vc.insert("actions", this._actions.clone()); if (this.hasPageLink) { vc.insert("pageLink", this._pageLink.clone()); } + return vc; } }); diff --git a/magnus/lib/wModel/proxy/panesList.js b/magnus/lib/wModel/proxy/panesList.js new file mode 100644 index 0000000..cda0f8a --- /dev/null +++ b/magnus/lib/wModel/proxy/panesList.js @@ -0,0 +1,66 @@ +"use strict"; + +var Vocabulary = require("../../wType/vocabulary"); +var Uint64 = require("../../wType/uint64"); +var String = require("../../wType/string"); +var Vector = require("../../wType/vector"); + +var Catalogue = require("./catalogue"); + +var PanesList = Catalogue.inherit({ + "className": "PanesList", + "constructor": function(address, ctrlAddr, ctrlOpts, socket, actions) { + Catalogue.fn.constructor.call(this, address, ctrlAddr, ctrlOpts, socket); + + this._actions = new Vector(); + this._createActions(actions || []); + }, + "destructor": function() { + this._actions.destructor(); + + Catalogue.fn.destructor.call(this); + }, + "_createActions": function(actions) { + for (var i = 0; i < actions.length; ++i) { + var action = actions[i]; + var actionVC = new Vocabulary(); + actionVC.insert("type", new Uint64(action.type)); + var supported = false; + + switch (action.type) { + case 0: //this type of actions has only action itself, no additional arguments, what to do is desctibed in the view + actionVC.insert("action", new String(action.action)); + supported = true; + break; + } + + if (supported) { + this._actions.push(actionVC); + } else { + actionVC.destructor(); + } + } + }, + "_h_get": function(ev) { + if (this.ready) { + var vc = new Vocabulary(); + + vc.insert("data", this._getAllData()); + vc.insert("actions", this._actions.clone()); + this.response(vc, "get", ev); + } else { + this._waitingEvents.push(ev.clone()); + } + }, + "_onRemoteData": function() { + this.setReady(true); + + var vc = new Vocabulary(); + vc.insert("data", this._getAllData()); + vc.insert("actions", this._actions.clone()); + + this.broadcast(vc, "get") + } +}); + +module.exports = PanesList; diff --git a/magnus/pages/album.js b/magnus/pages/album.js index 62146a5..8aa916d 100644 --- a/magnus/pages/album.js +++ b/magnus/pages/album.js @@ -2,7 +2,7 @@ var TempPage = require("./tempPage"); var String = require("../lib/wModel/string"); -var ProxyCatModel = require("../lib/wModel/proxy/catalogue"); +var PanesList = require("../lib/wModel/proxy/panesList"); var PaneModel = require("../lib/wModel/proxy/pane"); var Image = require("../lib/wModel/image"); var Model = require("../lib/wModel/model"); @@ -41,16 +41,23 @@ var AlbumPage = TempPage.inherit({ var spacer = new Model(this._address["+"](new Address(["spacer"]))); this.addItem(spacer, 0, 2, 2, 1); - this._songs = new ProxyCatModel( + this._songs = new PanesList( this._address["+"](new Address(["songs"])), new Address(["songs"]), { sorting: {ascending: true, field: "name"}, filter: {album: id.clone()} }, - proxySocket + proxySocket, + [{ + type: 0, + action: "play" + }, + { + type: 0, + action: "scheduledToPlay" + }] ); - this._songs.className = "PanesList"; var PaneClass = PaneModel.Songs; this._songs.setChildrenClass(PaneClass); this.addItem(this._songs, 2, 0, 1, 3, TempPage.Aligment.CenterTop); diff --git a/magnus/pages/artist.js b/magnus/pages/artist.js index ef496c7..f49a34c 100644 --- a/magnus/pages/artist.js +++ b/magnus/pages/artist.js @@ -2,7 +2,7 @@ var TempPage = require("./tempPage"); var String = require("../lib/wModel/string"); -var ProxyCatModel = require("../lib/wModel/proxy/catalogue"); +var PanesList = require("../lib/wModel/proxy/panesList"); var PaneModel = require("../lib/wModel/proxy/pane"); var VCController = require("../lib/wController/vocabulary"); @@ -22,7 +22,7 @@ var ArtistPage = TempPage.inherit({ header.addProperty("fontFamily", "casualFont"); this.addItem(header, 0, 0, 1, 1, TempPage.Aligment.CenterTop); - this._albums = new ProxyCatModel( + this._albums = new PanesList( this._address["+"](new Address(["albums"])), new Address(["albums"]), { @@ -36,16 +36,23 @@ var ArtistPage = TempPage.inherit({ this._albums.setChildrenClass(PaneClass); this.addItem(this._albums, 1, 0, 1, 1, TempPage.Aligment.CenterTop); - this._songs = new ProxyCatModel( + this._songs = new PanesList( this._address["+"](new Address(["songs"])), new Address(["songs"]), { sorting: {ascending: true, field: "name"}, filter: {artist: id.clone()} }, - proxySocket + proxySocket, + [{ + type: 0, + action: "play" + }, + { + type: 0, + action: "scheduledToPlay" + }] ); - this._songs.className = "PanesList"; var PaneClass = PaneModel.Songs; this._songs.setChildrenClass(PaneClass); this.addItem(this._songs, 2, 0, 1, 1, TempPage.Aligment.CenterTop); diff --git a/magnus/pages/list.js b/magnus/pages/list.js index e4c91d9..1e494c5 100644 --- a/magnus/pages/list.js +++ b/magnus/pages/list.js @@ -2,42 +2,45 @@ var Page = require("../lib/wModel/page"); var String = require("../lib/wModel/string"); -var ProxyCatModel = require("../lib/wModel/proxy/catalogue"); +var PanesList = require("../lib/wModel/proxy/panesList"); var PaneModel = require("../lib/wModel/proxy/pane"); var Address = require("../lib/wType/address"); var Uint64 = require("../lib/wType/uint64"); +var Vocabulary = require("../lib/wType/vocabulary"); var Handler = require("../lib/wDispatcher/handler"); var List = Page.inherit({ "className": "ListPage", - "constructor": function(address, name, remoteAddress, socket, ChildClass) { + "constructor": function(address, name, remoteAddress, socket, ChildClass, listOptions, paneActions) { Page.fn.constructor.call(this, address, name); this._proxySocket = socket; this._ChildClass = ChildClass; + this._listOptions = listOptions || new Vocabulary() var header = new String(this._address["+"](new Address(["header"])), name); header.addProperty("fontFamily", "casualFont"); this.addItem(header, 0, 0, 1, 1, Page.Aligment.CenterTop); - this._list = new ProxyCatModel( + this._list = new PanesList( this._address["+"](new Address(["list"])), remoteAddress, { sorting: {ascending: true, field: "name"} }, - socket + socket, + paneActions ); - this._list.className = "PanesList"; var PaneClass = PaneModel[name]; if (!PaneClass) { PaneClass = PaneModel.inherit({}); PaneClass.pageAddress = address; + PaneModel[name] = PaneClass; } this._list.setChildrenClass(PaneClass); - this.addItem(this._list, 1, 0, 1, 1, Page.Aligment.CenterTop); + this.addItem(this._list, 1, 0, 1, 1, Page.Aligment.CenterTop, new Uint64(List.getModelTypeId(this._list)), listOptions); this._reporterHandler = new Handler(this._address["+"](new Address(["subscribeMember"])), this, this._h_subscribeMember); }, diff --git a/magnus/pages/music.js b/magnus/pages/music.js index 50296c4..ff00222 100644 --- a/magnus/pages/music.js +++ b/magnus/pages/music.js @@ -7,6 +7,7 @@ var PanesList = require("../lib/wModel/panesList"); var Address = require("../lib/wType/address"); var Vocabulary = require("../lib/wType/vocabulary"); var Boolean = require("../lib/wType/boolean"); +var Uint64 = require("../lib/wType/uint64"); var Link = require("../lib/wModel/link"); @@ -135,12 +136,23 @@ var MusicPage = Page.inherit({ this._albumsLink.label.addProperty("color", "primaryFontColor"); this._albumsLink.addProperty("backgroundColor", "primaryColor"); + var lOpts = new Vocabulary(); + lOpts.insert("nestWidth", new Uint64(300)); this._songs = new List( this._addresses.songs.local.clone(), "Songs", this._addresses.songs.remote.clone(), socket, - Song + Song, + lOpts, + [{ + type: 0, + action: "play" + }, + { + type: 0, + action: "scheduledToPlay" + }] ); this._songsLink = new Link(this._address["+"](new Address(["songsLink"])), "Songs", this._addresses.songs.local.clone()); this._songsLink.label.addProperty("fontSize", "largeFontSize"); diff --git a/magnus/pages/test.js b/magnus/pages/test.js index 584493e..99fa73f 100644 --- a/magnus/pages/test.js +++ b/magnus/pages/test.js @@ -2,6 +2,7 @@ var Page = require("../lib/wModel/page"); var String = require("../lib/wModel/string"); +var Button = require("../lib/wModel/button"); var Address = require("../lib/wType/address"); @@ -13,6 +14,16 @@ var TestPage = Page.inherit({ var header = new String(this._address["+"](new Address(["message"])), "This is a test page"); header.addProperty("fontFamily", "casualFont"); this.addItem(header, 0, 0, 1, 1, Page.Aligment.CenterTop); + + this._button = new Button(this._address["+"](new Address(["testButton"]))); + this._button.setLabel("Push me"); + this._button.on("activated", this._onActivate, this); + this.addItem(this._button, 1, 0, 1, 1, Page.Aligment.CenterTop); + }, + "_onActivate": function() { + this.trigger("serviceMessage", "Button works!"); + this._button.setEnabled(false); + setTimeout(this._button.setEnabled.bind(this._button, true), 3000); } }); diff --git a/perturabo/CMakeLists.txt b/perturabo/CMakeLists.txt index 82b4d97..c37ac4f 100644 --- a/perturabo/CMakeLists.txt +++ b/perturabo/CMakeLists.txt @@ -25,7 +25,7 @@ target_link_libraries(perturabo wSocket) target_link_libraries(perturabo wDispatcher) target_link_libraries(perturabo utils) target_link_libraries(perturabo wModel) -target_link_libraries(perturabo wServerUtils) target_link_libraries(perturabo wDatabase) +target_link_libraries(perturabo wServerUtils) install(TARGETS perturabo RUNTIME DESTINATION bin) diff --git a/perturabo/perturabo.cpp b/perturabo/perturabo.cpp index 4d5917d..7231262 100644 --- a/perturabo/perturabo.cpp +++ b/perturabo/perturabo.cpp @@ -86,11 +86,11 @@ void Perturabo::start() server->listen(8082); cout << "Registering models..." << endl; - attributes->registerModel(dispatcher, server); - commands->registerModel(dispatcher, server); + attributes->getRegistered(connector); + commands->getRegistered(connector); for (; beg != end; ++beg) { - beg->second->registerModel(dispatcher, server); + beg->second->getRegistered(connector); } cout << "Opening and indexing databases..." << endl; @@ -115,11 +115,11 @@ void Perturabo::stop() commands->enableCommand(W::String(u"clearDatabase"), false); for (; beg != end; ++beg) { - beg->second->unregisterModel(); + beg->second->getUnregistered(); } - commands->unregisterModel(); - attributes->unregisterModel(); + commands->getUnregistered(); + attributes->getUnregistered(); server->stop(); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5fa9f00..df2f755 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 2.8.12) project(test) find_package(CxxTest) +cmake_policy(SET CMP0071 NEW) if(CXXTEST_FOUND) include_directories(${CXXTEST_INCLUDE_DIR}) enable_testing()