Merge remote-tracking branch 'upstream/master' into android_nov

This commit is contained in:
Brad Davis 2018-01-09 20:02:55 -08:00
commit fb59a0a56f
22 changed files with 663 additions and 131 deletions

View file

@ -36,6 +36,7 @@ Rectangle {
property bool pendingInventoryReply: true;
property bool isShowingMyItems: false;
property bool isDebuggingFirstUseTutorial: false;
property int pendingItemCount: 0;
// Style
color: hifi.colors.white;
Connections {
@ -79,18 +80,22 @@ Rectangle {
onInventoryResult: {
purchasesReceived = true;
if (root.pendingInventoryReply) {
inventoryTimer.start();
}
if (result.status !== 'success') {
console.log("Failed to get purchases", result.message);
} else {
} else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling
var inventoryResult = processInventoryResult(result.data.assets);
var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex;
purchasesModel.clear();
purchasesModel.append(inventoryResult);
root.pendingItemCount = 0;
for (var i = 0; i < purchasesModel.count; i++) {
if (purchasesModel.get(i).status === "pending") {
root.pendingItemCount++;
}
}
if (previousPurchasesModel.count !== 0) {
checkIfAnyItemStatusChanged();
} else {
@ -103,6 +108,12 @@ Rectangle {
previousPurchasesModel.append(inventoryResult);
buildFilteredPurchasesModel();
purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning);
}
if (root.pendingInventoryReply && root.pendingItemCount > 0) {
inventoryTimer.start();
}
root.pendingInventoryReply = false;
@ -419,6 +430,8 @@ Rectangle {
visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
clip: true;
model: filteredPurchasesModel;
snapMode: ListView.SnapToItem;
highlightRangeMode: ListView.StrictlyEnforceRange;
// Anchors
anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom;
anchors.topMargin: 12;

View file

@ -25,8 +25,12 @@ Item {
HifiConstants { id: hifi; }
id: root;
property bool historyReceived: false;
property bool initialHistoryReceived: false;
property bool historyRequestPending: true;
property bool noMoreHistoryData: false;
property int pendingCount: 0;
property int currentHistoryPage: 1;
property var pagesAlreadyAdded: new Array();
Connections {
target: Commerce;
@ -36,32 +40,86 @@ Item {
}
onHistoryResult : {
historyReceived = true;
if (result.status === 'success') {
var sameItemCount = 0;
tempTransactionHistoryModel.clear();
tempTransactionHistoryModel.append(result.data.history);
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
if (!transactionHistoryModel.get(i)) {
sameItemCount = -1;
break;
} else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type &&
tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) {
sameItemCount++;
}
}
root.initialHistoryReceived = true;
root.historyRequestPending = false;
if (sameItemCount !== tempTransactionHistoryModel.count) {
transactionHistoryModel.clear();
if (result.status === 'success') {
var currentPage = parseInt(result.current_page);
if (result.data.history.length === 0) {
root.noMoreHistoryData = true;
console.log("No more data to retrieve from Commerce.history() endpoint.")
} else if (root.currentHistoryPage === 1) {
var sameItemCount = 0;
tempTransactionHistoryModel.clear();
tempTransactionHistoryModel.append(result.data.history);
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
transactionHistoryModel.append(tempTransactionHistoryModel.get(i));
if (!transactionHistoryModel.get(i)) {
sameItemCount = -1;
break;
} else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type &&
tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) {
sameItemCount++;
}
}
if (sameItemCount !== tempTransactionHistoryModel.count) {
transactionHistoryModel.clear();
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
transactionHistoryModel.append(tempTransactionHistoryModel.get(i));
}
calculatePendingAndInvalidated();
}
} else {
if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) {
console.log("Page " + currentPage + " of history has already been added to the list.");
} else {
// First, add the history result to a temporary model
tempTransactionHistoryModel.clear();
tempTransactionHistoryModel.append(result.data.history);
// Make a note that we've already added this page to the model...
root.pagesAlreadyAdded.push(currentPage);
var insertionIndex = 0;
// If there's nothing in the model right now, we don't need to modify insertionIndex.
if (transactionHistoryModel.count !== 0) {
var currentIteratorPage;
// Search through the whole transactionHistoryModel and look for the insertion point.
// The insertion point is found when the result page from the server is less than
// the page that the current item came from, OR when we've reached the end of the whole model.
for (var i = 0; i < transactionHistoryModel.count; i++) {
currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage;
if (currentPage < currentIteratorPage) {
insertionIndex = i;
break;
} else if (i === transactionHistoryModel.count - 1) {
insertionIndex = i + 1;
break;
}
}
}
// Go through the results we just got back from the server, setting the "resultIsFromPage"
// property of those results and adding them to the main model.
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage);
transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i))
}
calculatePendingAndInvalidated();
}
calculatePendingAndInvalidated();
}
}
refreshTimer.start();
// Only auto-refresh if the user hasn't scrolled
// and there is more data to grab
if (transactionHistory.atYBeginning && !root.noMoreHistoryData) {
refreshTimer.start();
}
}
}
@ -134,9 +192,13 @@ Item {
onVisibleChanged: {
if (visible) {
historyReceived = false;
transactionHistoryModel.clear();
Commerce.balance();
Commerce.history();
initialHistoryReceived = false;
root.currentHistoryPage = 1;
root.noMoreHistoryData = false;
root.historyRequestPending = true;
Commerce.history(root.currentHistoryPage);
} else {
refreshTimer.stop();
}
@ -164,9 +226,12 @@ Item {
id: refreshTimer;
interval: 4000;
onTriggered: {
console.log("Refreshing Wallet Home...");
Commerce.balance();
Commerce.history();
if (transactionHistory.atYBeginning) {
console.log("Refreshing 1st Page of Recent Activity...");
root.historyRequestPending = true;
Commerce.balance();
Commerce.history(1);
}
}
}
@ -241,7 +306,7 @@ Item {
anchors.right: parent.right;
Item {
visible: transactionHistoryModel.count === 0 && root.historyReceived;
visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived;
anchors.centerIn: parent;
width: parent.width - 12;
height: parent.height;
@ -364,7 +429,12 @@ Item {
onAtYEndChanged: {
if (transactionHistory.atYEnd) {
console.log("User scrolled to the bottom of 'Recent Activity'.");
// Grab next page of results and append to model
if (!root.historyRequestPending && !root.noMoreHistoryData) {
// Grab next page of results and append to model
root.historyRequestPending = true;
Commerce.history(++root.currentHistoryPage);
console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity...");
}
}
}
}

View file

@ -244,6 +244,7 @@ Item {
PageIndicator {
id: pageIndicator
currentIndex: swipeView.currentIndex
visible: swipeView.count > 1
delegate: Item {
width: 15

View file

@ -72,11 +72,16 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con
send(endpoint, success, fail, QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request);
}
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) {
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& requestParams) {
auto wallet = DependencyManager::get<Wallet>();
QJsonObject request;
request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, request);
requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams);
}
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) {
QJsonObject requestParams;
keysQuery(endpoint, success, fail, requestParams);
}
void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) {
@ -169,6 +174,7 @@ void Ledger::historySuccess(QNetworkReply& reply) {
QJsonObject newDataData;
newDataData["history"] = newHistoryArray;
newData["data"] = newDataData;
newData["current_page"] = data["current_page"].toInt();
emit historyResult(newData);
}
@ -176,8 +182,11 @@ void Ledger::historyFailure(QNetworkReply& reply) {
failResponse("history", reply);
}
void Ledger::history(const QStringList& keys) {
keysQuery("history", "historySuccess", "historyFailure");
void Ledger::history(const QStringList& keys, const int& pageNumber) {
QJsonObject params;
params["per_page"] = 100;
params["page"] = pageNumber;
keysQuery("history", "historySuccess", "historyFailure", params);
}
// The api/failResponse is called just for the side effect of logging.

View file

@ -29,7 +29,7 @@ public:
bool receiveAt(const QString& hfc_key, const QString& old_key);
void balance(const QStringList& keys);
void inventory(const QStringList& keys);
void history(const QStringList& keys);
void history(const QStringList& keys, const int& pageNumber);
void account();
void reset();
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
@ -79,6 +79,7 @@ private:
QJsonObject apiResponse(const QString& label, QNetworkReply& reply);
QJsonObject failResponse(const QString& label, QNetworkReply& reply);
void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request);
void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams);
void keysQuery(const QString& endpoint, const QString& success, const QString& fail);
void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure = false);
};

View file

@ -96,12 +96,12 @@ void QmlCommerce::inventory() {
}
}
void QmlCommerce::history() {
void QmlCommerce::history(const int& pageNumber) {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
QStringList cachedPublicKeys = wallet->listPublicKeys();
if (!cachedPublicKeys.isEmpty()) {
ledger->history(cachedPublicKeys);
ledger->history(cachedPublicKeys, pageNumber);
}
}

View file

@ -60,7 +60,7 @@ protected:
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
Q_INVOKABLE void balance();
Q_INVOKABLE void inventory();
Q_INVOKABLE void history();
Q_INVOKABLE void history(const int& pageNumber);
Q_INVOKABLE void generateKeyPair();
Q_INVOKABLE void reset();
Q_INVOKABLE void resetLocalWalletOnly();

View file

@ -3,6 +3,7 @@
// libraries/midi/src
//
// Created by Burt Sloane
// Modified by Bruce Brown
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
@ -14,30 +15,45 @@
#include <QtCore/QLoggingCategory>
#if defined Q_OS_WIN32
#include "Windows.h"
#endif
#if defined Q_OS_WIN32
const int MIDI_BYTE_MASK = 0x0FF;
const int MIDI_NIBBLE_MASK = 0x00F;
const int MIDI_PITCH_BEND_MASK = 0x3F80;
const int MIDI_SHIFT_STATUS = 4;
const int MIDI_SHIFT_NOTE = 8;
const int MIDI_SHIFT_VELOCITY = 16;
const int MIDI_SHIFT_PITCH_BEND = 9;
// Status Decode
const int MIDI_NOTE_OFF = 0x8;
const int MIDI_NOTE_ON = 0x9;
const int MIDI_POLYPHONIC_KEY_PRESSURE = 0xa;
const int MIDI_PROGRAM_CHANGE = 0xc;
const int MIDI_CHANNEL_PRESSURE = 0xd;
const int MIDI_PITCH_BEND_CHANGE = 0xe;
const int MIDI_SYSTEM_MESSAGE = 0xf;
#endif
const int MIDI_STATUS_MASK = 0x0F0;
const int MIDI_NOTE_OFF = 0x080;
const int MIDI_NOTE_ON = 0x090;
const int MIDI_CONTROL_CHANGE = 0x0b0;
const int MIDI_CONTROL_CHANGE = 0xb;
const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b;
static Midi* instance = NULL; // communicate this to non-class callbacks
static Midi* instance = NULL; // communicate this to non-class callbacks
static bool thruModeEnabled = false;
static bool broadcastEnabled = false;
static bool typeNoteOffEnabled = true;
static bool typeNoteOnEnabled = true;
static bool typePolyKeyPressureEnabled = false;
static bool typeControlChangeEnabled = true;
static bool typeProgramChangeEnabled = true;
static bool typeChanPressureEnabled = false;
static bool typePitchBendEnabled = true;
static bool typeSystemMessageEnabled = false;
std::vector<QString> Midi::midiinexclude;
std::vector<QString> Midi::midioutexclude;
std::vector<QString> Midi::midiInExclude;
std::vector<QString> Midi::midiOutExclude;
#if defined Q_OS_WIN32
@ -47,7 +63,6 @@ std::vector<QString> Midi::midioutexclude;
std::vector<HMIDIIN> midihin;
std::vector<HMIDIOUT> midihout;
void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
switch (wMsg) {
case MIM_OPEN:
@ -58,23 +73,64 @@ void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD
if (midihin[i] == hMidiIn) {
midihin[i] = NULL;
instance->allNotesOff();
instance->midiHardwareChange();
}
}
break;
case MIM_DATA: {
int status = MIDI_BYTE_MASK & dwParam1;
int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE);
int vel = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY);
if (thruModeEnabled) {
instance->sendNote(status, note, vel); // relay the note on to all other midi devices
int device = -1;
for (int i = 0; i < midihin.size(); i++) {
if (midihin[i] == hMidiIn) {
device = i;
}
}
instance->noteReceived(status, note, vel); // notify the javascript
int raw = dwParam1;
int channel = (MIDI_NIBBLE_MASK & dwParam1) + 1;
int status = MIDI_BYTE_MASK & dwParam1;
int type = MIDI_NIBBLE_MASK & (dwParam1 >> MIDI_SHIFT_STATUS);
int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE);
int velocity = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY);
int bend = 0;
int program = 0;
if (!typeNoteOffEnabled && type == MIDI_NOTE_OFF) {
return;
}
if (!typeNoteOnEnabled && type == MIDI_NOTE_ON) {
return;
}
if (!typePolyKeyPressureEnabled && type == MIDI_POLYPHONIC_KEY_PRESSURE) {
return;
}
if (!typeControlChangeEnabled && type == MIDI_CONTROL_CHANGE) {
return;
}
if (typeProgramChangeEnabled && type == MIDI_PROGRAM_CHANGE) {
program = note;
note = 0;
}
if (typeChanPressureEnabled && type == MIDI_CHANNEL_PRESSURE) {
velocity = note;
note = 0;
}
if (typePitchBendEnabled && type == MIDI_PITCH_BEND_CHANGE) {
bend = ((MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE)) |
(MIDI_PITCH_BEND_MASK & (dwParam1 >> MIDI_SHIFT_PITCH_BEND))) - 8192;
channel = 0; // Weird values on different instruments
note = 0;
velocity = 0;
}
if (!typeSystemMessageEnabled && type == MIDI_SYSTEM_MESSAGE) {
return;
}
if (thruModeEnabled) {
instance->sendNote(status, note, velocity); // relay the message on to all other midi devices.
}
instance->midiReceived(device, raw, channel, status, type, note, velocity, bend, program); // notify the javascript
break;
}
}
}
void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
switch (wMsg) {
case MOM_OPEN:
@ -85,21 +141,45 @@ void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_P
if (midihout[i] == hmo) {
midihout[i] = NULL;
instance->allNotesOff();
instance->midiHardwareChange();
}
}
break;
}
}
void Midi::sendNote(int status, int note, int vel) {
for (int i = 0; i < midihout.size(); i++) {
if (midihout[i] != NULL) {
midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (vel << MIDI_SHIFT_VELOCITY));
void Midi::sendRawMessage(int device, int raw) {
if (broadcastEnabled) {
for (int i = 0; i < midihout.size(); i++) {
if (midihout[i] != NULL) {
midiOutShortMsg(midihout[i], raw);
}
}
} else {
midiOutShortMsg(midihout[device], raw);
}
}
void Midi::sendMessage(int device, int channel, int type, int note, int velocity) {
int message = (channel - 1) | (type << MIDI_SHIFT_STATUS);
if (broadcastEnabled) {
for (int i = 0; i < midihout.size(); i++) {
if (midihout[i] != NULL) {
midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY));
}
}
} else {
midiOutShortMsg(midihout[device], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY));
}
}
void Midi::sendNote(int status, int note, int velocity) {
for (int i = 0; i < midihout.size(); i++) {
if (midihout[i] != NULL) {
midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY));
}
}
}
void Midi::MidiSetup() {
midihin.clear();
@ -110,8 +190,8 @@ void Midi::MidiSetup() {
midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS));
bool found = false;
for (int j = 0; j < midiinexclude.size(); j++) {
if (midiinexclude[j].toStdString().compare(incaps.szPname) == 0) {
for (int j = 0; j < midiInExclude.size(); j++) {
if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) {
found = true;
break;
}
@ -122,7 +202,6 @@ void Midi::MidiSetup() {
midiInStart(tmphin);
midihin.push_back(tmphin);
}
}
MIDIOUTCAPS outcaps;
@ -130,8 +209,8 @@ void Midi::MidiSetup() {
midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS));
bool found = false;
for (int j = 0; j < midioutexclude.size(); j++) {
if (midioutexclude[j].toStdString().compare(outcaps.szPname) == 0) {
for (int j = 0; j < midiOutExclude.size(); j++) {
if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) {
found = true;
break;
}
@ -164,7 +243,13 @@ void Midi::MidiCleanup() {
midihout.clear();
}
#else
void Midi::sendNote(int status, int note, int vel) {
void Midi::sendRawMessage(int device, int raw) {
}
void Midi::sendNote(int status, int note, int velocity) {
}
void Midi::sendMessage(int device, int channel, int type, int note, int velocity){
}
void Midi::MidiSetup() {
@ -176,26 +261,30 @@ void Midi::MidiCleanup() {
}
#endif
void Midi::noteReceived(int status, int note, int velocity) {
if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) &&
((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON) &&
((status & MIDI_STATUS_MASK) != MIDI_CONTROL_CHANGE)) {
return; // NOTE: only sending note-on, note-off, and control-change to Javascript
}
void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) {
QVariantMap eventData;
eventData["device"] = device;
eventData["raw"] = raw;
eventData["channel"] = channel;
eventData["status"] = status;
eventData["type"] = type;
eventData["note"] = note;
eventData["velocity"] = velocity;
emit midiNote(eventData);
eventData["bend"] = bend;
eventData["program"] = program;
emit midiNote(eventData);// Legacy
emit midiMessage(eventData);
}
void Midi::midiHardwareChange() {
emit midiReset();
}
//
Midi::Midi() {
instance = this;
#if defined Q_OS_WIN32
midioutexclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing
midiOutExclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing (Lags)
#endif
MidiSetup();
}
@ -203,10 +292,18 @@ Midi::Midi() {
Midi::~Midi() {
}
void Midi::sendRawDword(int device, int raw) {
sendRawMessage(device, raw);
}
void Midi::playMidiNote(int status, int note, int velocity) {
sendNote(status, note, velocity);
}
void Midi::sendMidiMessage(int device, int channel, int type, int note, int velocity) {
sendMessage(device, channel, type, note, velocity);
}
void Midi::allNotesOff() {
sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off
}
@ -219,6 +316,7 @@ void Midi::resetDevices() {
void Midi::USBchanged() {
instance->MidiCleanup();
instance->MidiSetup();
instance->midiHardwareChange();
}
//
@ -245,16 +343,16 @@ QStringList Midi::listMidiDevices(bool output) {
void Midi::unblockMidiDevice(QString name, bool output) {
if (output) {
for (unsigned long i = 0; i < midioutexclude.size(); i++) {
if (midioutexclude[i].toStdString().compare(name.toStdString()) == 0) {
midioutexclude.erase(midioutexclude.begin() + i);
for (unsigned long i = 0; i < midiOutExclude.size(); i++) {
if (midiOutExclude[i].toStdString().compare(name.toStdString()) == 0) {
midiOutExclude.erase(midiOutExclude.begin() + i);
break;
}
}
} else {
for (unsigned long i = 0; i < midiinexclude.size(); i++) {
if (midiinexclude[i].toStdString().compare(name.toStdString()) == 0) {
midiinexclude.erase(midiinexclude.begin() + i);
for (unsigned long i = 0; i < midiInExclude.size(); i++) {
if (midiInExclude[i].toStdString().compare(name.toStdString()) == 0) {
midiInExclude.erase(midiInExclude.begin() + i);
break;
}
}
@ -264,9 +362,9 @@ void Midi::unblockMidiDevice(QString name, bool output) {
void Midi::blockMidiDevice(QString name, bool output) {
unblockMidiDevice(name, output); // make sure it's only in there once
if (output) {
midioutexclude.push_back(name);
midiOutExclude.push_back(name);
} else {
midiinexclude.push_back(name);
midiInExclude.push_back(name);
}
}
@ -274,3 +372,38 @@ void Midi::thruModeEnable(bool enable) {
thruModeEnabled = enable;
}
void Midi::broadcastEnable(bool enable) {
broadcastEnabled = enable;
}
void Midi::typeNoteOffEnable(bool enable) {
typeNoteOffEnabled = enable;
}
void Midi::typeNoteOnEnable(bool enable) {
typeNoteOnEnabled = enable;
}
void Midi::typePolyKeyPressureEnable(bool enable) {
typePolyKeyPressureEnabled = enable;
}
void Midi::typeControlChangeEnable(bool enable) {
typeControlChangeEnabled = enable;
}
void Midi::typeProgramChangeEnable(bool enable) {
typeProgramChangeEnabled = enable;
}
void Midi::typeChanPressureEnable(bool enable) {
typeChanPressureEnabled = enable;
}
void Midi::typePitchBendEnable(bool enable) {
typePitchBendEnabled = enable;
}
void Midi::typeSystemMessageEnable(bool enable) {
typeSystemMessageEnabled = enable;
}

View file

@ -3,6 +3,7 @@
// libraries/midi/src
//
// Created by Burt Sloane
// Modified by Bruce Brown
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
@ -24,13 +25,16 @@ class Midi : public QObject, public Dependency {
SINGLETON_DEPENDENCY
public:
void noteReceived(int status, int note, int velocity); // relay a note to Javascript
void sendNote(int status, int note, int vel); // relay a note to MIDI outputs
void midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program); // relay a note to Javascript
void midiHardwareChange(); // relay hardware change to Javascript
void sendRawMessage(int device, int raw); // relay midi message to MIDI outputs
void sendNote(int status, int note, int velocity); // relay a note to MIDI outputs
void sendMessage(int device, int channel, int type, int note, int velocity); // relay a message to MIDI outputs
static void USBchanged();
private:
static std::vector<QString> midiinexclude;
static std::vector<QString> midioutexclude;
static std::vector<QString> midiInExclude;
static std::vector<QString> midiOutExclude;
private:
void MidiSetup();
@ -38,31 +42,63 @@ private:
signals:
void midiNote(QVariantMap eventData);
void midiMessage(QVariantMap eventData);
void midiReset();
public slots:
/// play a note on all connected devices
/// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc
/// @param {int} note: midi note number
/// @param {int} velocity: note velocity (0 means noteoff)
Q_INVOKABLE void playMidiNote(int status, int note, int velocity);
public slots:
// Send Raw Midi Packet to all connected devices
Q_INVOKABLE void sendRawDword(int device, int raw);
/// Send Raw Midi message to selected device
/// @param {int} device: device number
/// @param {int} raw: raw midi message (DWORD)
/// turn off all notes on all connected devices
Q_INVOKABLE void allNotesOff();
// Send Midi Message to all connected devices
Q_INVOKABLE void sendMidiMessage(int device, int channel, int type, int note, int velocity);
/// Send midi message to selected device/devices
/// @param {int} device: device number
/// @param {int} channel: channel number
/// @param {int} type: 0x8 is noteoff, 0x9 is noteon (if velocity=0, noteoff), etc
/// @param {int} note: midi note number
/// @param {int} velocity: note velocity (0 means noteoff)
/// clean up and re-discover attached devices
Q_INVOKABLE void resetDevices();
// Send Midi Message to all connected devices
Q_INVOKABLE void playMidiNote(int status, int note, int velocity);
/// play a note on all connected devices
/// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc
/// @param {int} note: midi note number
/// @param {int} velocity: note velocity (0 means noteoff)
/// ask for a list of inputs/outputs
Q_INVOKABLE QStringList listMidiDevices(bool output);
/// turn off all notes on all connected devices
Q_INVOKABLE void allNotesOff();
/// block an input/output by name
Q_INVOKABLE void blockMidiDevice(QString name, bool output);
/// clean up and re-discover attached devices
Q_INVOKABLE void resetDevices();
/// unblock an input/output by name
Q_INVOKABLE void unblockMidiDevice(QString name, bool output);
/// ask for a list of inputs/outputs
Q_INVOKABLE QStringList listMidiDevices(bool output);
/// block an input/output by name
Q_INVOKABLE void blockMidiDevice(QString name, bool output);
/// unblock an input/output by name
Q_INVOKABLE void unblockMidiDevice(QString name, bool output);
/// repeat all incoming notes to all outputs (default disabled)
Q_INVOKABLE void thruModeEnable(bool enable);
/// broadcast on all unblocked devices
Q_INVOKABLE void broadcastEnable(bool enable);
/// filter by event types
Q_INVOKABLE void typeNoteOffEnable(bool enable);
Q_INVOKABLE void typeNoteOnEnable(bool enable);
Q_INVOKABLE void typePolyKeyPressureEnable(bool enable);
Q_INVOKABLE void typeControlChangeEnable(bool enable);
Q_INVOKABLE void typeProgramChangeEnable(bool enable);
Q_INVOKABLE void typeChanPressureEnable(bool enable);
Q_INVOKABLE void typePitchBendEnable(bool enable);
Q_INVOKABLE void typeSystemMessageEnable(bool enable);
/// repeat all incoming notes to all outputs (default disabled)
Q_INVOKABLE void thruModeEnable(bool enable);
public:
Midi();

View file

@ -22,24 +22,25 @@ function pushAll(dest, orig) {
if (!App.isAndroid()) {
pushAll(DEFAULT_SCRIPTS_COMBINED, [
"system/progress.js",
"system/away.js",
"system/audio.js",
"system/hmd.js",
"system/menu.js",
"system/bubble.js",
"system/snapshot.js",
"system/help.js",
"system/pal.js", // "system/mod.js", // older UX, if you prefer
"system/makeUserConnection.js",
"system/tablet-goto.js",
"system/marketplaces/marketplaces.js",
"system/commerce/wallet.js",
"system/edit.js",
"system/notifications.js",
"system/dialTone.js",
"system/firstPersonHMD.js",
"system/tablet-ui/tabletUI.js"
"system/progress.js",
"system/away.js",
"system/audio.js",
"system/hmd.js",
"system/menu.js",
"system/bubble.js",
"system/snapshot.js",
"system/help.js",
"system/pal.js", // "system/mod.js", // older UX, if you prefer
"system/makeUserConnection.js",
"system/tablet-goto.js",
"system/marketplaces/marketplaces.js",
"system/commerce/wallet.js",
"system/edit.js",
"system/notifications.js",
"system/dialTone.js",
"system/firstPersonHMD.js",
"system/tablet-ui/tabletUI.js",
"system/emote.js"
]);
} else {
pushAll(DEFAULT_SCRIPTS_COMBINED, [

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

122
scripts/system/emote.js Normal file
View file

@ -0,0 +1,122 @@
"use strict";
//
// emote.js
// scripts/system/
//
// Created by Brad Hefta-Gaub on 7 Jan 2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals Script, Tablet */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function() { // BEGIN LOCAL_SCOPE
var EMOTE_ANIMATIONS = ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping'];
var ANIMATIONS = Array();
EMOTE_ANIMATIONS.forEach(function (name) {
var animationURL = Script.resolvePath("assets/animations/" + name + ".fbx");
var resource = AnimationCache.prefetch(animationURL);
var animation = AnimationCache.getAnimation(animationURL);
ANIMATIONS[name] = { url: animationURL, animation: animation, resource: resource};
});
var EMOTE_APP_BASE = "html/EmoteApp.html";
var EMOTE_APP_URL = Script.resolvePath(EMOTE_APP_BASE);
var EMOTE_LABEL = "EMOTE";
var EMOTE_APP_SORT_ORDER = 11;
var FPS = 60;
var MSEC_PER_SEC = 1000;
var FINISHED = 3; // see ScriptableResource::State
var onEmoteScreen = false;
var button;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var activeTimer = false; // used to cancel active timer if a user plays an amimation while another animation is playing
var activeEmote = false; // to keep track of the currently playing emote
button = tablet.addButton({
//icon: "icons/tablet-icons/emote.svg", // TODO - we need graphics for this
text: EMOTE_LABEL,
sortOrder: EMOTE_APP_SORT_ORDER
});
function onClicked() {
if (onEmoteScreen) {
tablet.gotoHomeScreen();
} else {
onEmoteScreen = true;
tablet.gotoWebScreen(EMOTE_APP_URL);
}
}
function onScreenChanged(type, url) {
onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) == url.length - EMOTE_APP_BASE.length);
button.editProperties({ isActive: onEmoteScreen });
}
// Handle the events we're receiving from the web UI
function onWebEventReceived(event) {
// Converts the event to a JavasScript Object
if (typeof event === "string") {
event = JSON.parse(event);
}
if (event.type === "click") {
var emoteName = event.data;
if (ANIMATIONS[emoteName].resource.state == FINISHED) {
if (activeTimer !== false) {
Script.clearTimeout(activeTimer);
}
// if the activeEmote is different from the chosen emote, then play the new emote. Other wise,
// this is a second click on the same emote as the activeEmote, and we will just stop it.
if (activeEmote !== emoteName) {
activeEmote = emoteName;
var frameCount = ANIMATIONS[emoteName].animation.frames.length;
MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount);
var timeOut = MSEC_PER_SEC * frameCount / FPS;
activeTimer = Script.setTimeout(function () {
MyAvatar.restoreAnimation();
activeTimer = false;
activeEmote = false;
}, timeOut);
} else {
activeEmote = false;
MyAvatar.restoreAnimation();
}
}
}
}
button.clicked.connect(onClicked);
tablet.screenChanged.connect(onScreenChanged);
tablet.webEventReceived.connect(onWebEventReceived);
Script.scriptEnding.connect(function () {
if (onEmoteScreen) {
tablet.gotoHomeScreen();
}
button.clicked.disconnect(onClicked);
tablet.screenChanged.disconnect(onScreenChanged);
if (tablet) {
tablet.removeButton(button);
}
if (activeTimer !== false) {
Script.clearTimeout(activeTimer);
MyAvatar.restoreAnimation();
}
});
}()); // END LOCAL_SCOPE

View file

@ -0,0 +1,136 @@
<!--
// EmoteApp.html
//
// Created by Brad Hefta-Gaub on 7 Jan 2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-->
<html>
<head>
<title>Emote App</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600,700"" rel="stylesheet">
<style>
body {
margin: 0;
width: 100%;
font-family: 'Raleway', sans-serif;
color: white;
background: linear-gradient(#2b2b2b, #0f212e);
}
.top-bar {
height: 90px;
background: linear-gradient(#2b2b2b, #1e1e1e);
font-weight: bold;
padding-left: 30px;
padding-right: 30px;
display: flex;
align-items: center;
position: fixed;
width: 480px;
top: 0;
z-index: 1;
}
.content {
margin-top: 90px;
padding: 30px;
}
input[type=button] {
font-family: 'Raleway';
font-weight: bold;
font-size: 13px;
text-transform: uppercase;
vertical-align: top;
height: 28px;
min-width: 120px;
padding: 0px 18px;
margin-right: 6px;
border-radius: 5px;
border: none;
color: #fff;
background-color: #000;
background: linear-gradient(#343434 20%, #000 100%);
cursor: pointer;
}
input[type=button].white {
color: #121212;
background-color: #afafaf;
background: linear-gradient(#fff 20%, #afafaf 100%);
}
input[type=button]:enabled:hover {
background: linear-gradient(#000, #000);
border: none;
}
input[type=button].white:enabled:hover {
background: linear-gradient(#fff, #fff);
border: none;
}
input[type=button]:active {
background: linear-gradient(#343434, #343434);
}
input[type=button].white:active {
background: linear-gradient(#afafaf, #afafaf);
}
input[type=button]:disabled {
color: #252525;
background: linear-gradient(#575757 20%, #252525 100%);
}
input[type=button][pressed=pressed] {
color: #00b4ef;
}
</style>
</head>
<body>
<div class="top-bar">
<h4>Emote App</h4>
</div>
<div class="content">
<p>Click an emotion to Emote:<p>
<p><input type="button" class="emote-button white" value="Crying"></p>
<p><input type="button" class="emote-button white" value="Surprised"></p>
<p><input type="button" class="emote-button white" value="Dancing"></p>
<p><input type="button" class="emote-button white" value="Cheering"></p>
<p><input type="button" class="emote-button white" value="Waving"></p>
<p><input type="button" class="emote-button white" value="Fall"></p>
<p><input type="button" class="emote-button white" value="Pointing"></p>
<p><input type="button" class="emote-button white" value="Clapping"></p>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
function main() {
// Send an event to emote.js when the page loads and is ready to get things rolling
console.log("document ready");
var readyEvent = {
"type": "ready",
};
// The event bridge handles event represented as a string the best. So here we first create a Javascript object, then convert to stirng
EventBridge.emitWebEvent(JSON.stringify(readyEvent));
// Send an event when user click on each of the emote buttons
$(".emote-button").click(function(){
console.log(this.value + " button click");
var clickEvent = {
"type": "click",
"data": this.value
};
EventBridge.emitWebEvent(JSON.stringify(clickEvent));
});
}
$(document).ready(main);
</script>
</body>
</html>

View file

@ -28,6 +28,7 @@ var selectionDisplay = null; // for gridTool.js to ignore
var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml";
var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav"));
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
@ -341,6 +342,15 @@ var selectionDisplay = null; // for gridTool.js to ignore
// we currently assume a wearable is a single entity
Entities.editEntity(pastedEntityIDs[0], offsets);
}
var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position;
Audio.playSound(REZZING_SOUND, {
volume: 1.0,
position: rezPosition,
localOnly: true
});
} else {
Window.notifyEditError("Can't import entities: entities would be out of bounds.");
}