This commit is contained in:
Philip Rosedale 2014-05-06 14:51:14 -07:00
commit a03716a152
19 changed files with 321 additions and 153 deletions

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var windowDimensions = Controller.getViewportDimensions();
var LASER_WIDTH = 4;
var LASER_COLOR = { red: 255, green: 0, blue: 0 };
var LASER_LENGTH_FACTOR = 1.5;
@ -16,6 +18,40 @@ var LASER_LENGTH_FACTOR = 1.5;
var LEFT = 0;
var RIGHT = 1;
var SPAWN_DISTANCE = 1;
var radiusDefault = 0.10;
var modelURLs = [
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/orc.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx",
];
var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/";
var numberOfTools = 1;
var toolHeight = 50;
var toolWidth = 50;
var toolVerticalSpacing = 4;
var toolsHeight = toolHeight * numberOfTools + toolVerticalSpacing * (numberOfTools - 1);
var toolsX = windowDimensions.x - 8 - toolWidth;
var toolsY = (windowDimensions.y - toolsHeight) / 2;
var firstModel = Overlays.addOverlay("image", {
x: 0, y: 0, width: toolWidth, height: toolHeight,
subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight },
imageURL: toolIconUrl + "voxel-tool.svg",
x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight,
visible: true,
alpha: 0.9
});
function controller(wichSide) {
this.side = wichSide;
this.palm = 2 * wichSide;
@ -46,7 +82,10 @@ function controller(wichSide) {
this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously)
this.grabbing = false;
this.modelID;
this.modelID = { isKnownID: false };
this.oldModelRotation;
this.oldModelPosition;
this.oldModelRadius;
this.laser = Overlays.addOverlay("line3d", {
position: this.palmPosition,
@ -85,23 +124,19 @@ function controller(wichSide) {
this.grab = function (modelID) {
if (!modelID.isKnownID) {
var identify = Models.identifyModel(modelID);
if (!identify.isKnownID) {
print("Unknown ID " + identify.id + "(grab)");
return;
}
modelID = identify;
}
this.grab = function (modelID, properties) {
print("Grabbing " + modelID.id);
this.grabbing = true;
this.modelID = modelID;
this.oldModelPosition = properties.position;
this.oldModelRotation = properties.modelRotation;
this.oldModelRadius = properties.radius;
}
this.release = function () {
this.grabbing = false;
this.modelID = 0;
this.modelID.isKnownID = false;
}
this.checkTrigger = function () {
@ -118,6 +153,34 @@ function controller(wichSide) {
}
}
this.checkModel = function (properties) {
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
// / | X - base of the perpendicular line
// A---X----->B d - distance fom axis
// x x - distance from A
//
// |X-A| = (P-A).B
// X == A + ((P-A).B)B
// d = |P-X|
var A = this.palmPosition;
var B = this.front;
var P = properties.position;
var x = Vec3.dot(Vec3.subtract(P, A), B);
var y = Vec3.dot(Vec3.subtract(P, A), this.up);
var z = Vec3.dot(Vec3.subtract(P, A), this.right);
var X = Vec3.sum(A, Vec3.multiply(B, x));
var d = Vec3.length(Vec3.subtract(P, X));
if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) {
return { valid: true, x: x, y: y, z: z };
}
return { valid: false };
}
this.moveLaser = function () {
var endPosition = Vec3.sum(this.palmPosition, Vec3.multiply(this.front, LASER_LENGTH_FACTOR));
@ -143,44 +206,33 @@ function controller(wichSide) {
});
}
this.checkModel = function (modelID) {
if (!modelID.isKnownID) {
var identify = Models.identifyModel(modelID);
if (!identify.isKnownID) {
print("Unknown ID " + identify.id + "(checkModel)");
return;
}
modelID = identify;
this.moveModel = function () {
if (this.grabbing) {
var newPosition = Vec3.sum(this.palmPosition,
Vec3.multiply(this.front, this.x));
newPosition = Vec3.sum(newPosition,
Vec3.multiply(this.up, this.y));
newPosition = Vec3.sum(newPosition,
Vec3.multiply(this.right, this.z));
var newRotation = Quat.multiply(this.rotation,
Quat.inverse(this.oldRotation));
newRotation = Quat.multiply(newRotation,
this.oldModelRotation);
Models.editModel(this.modelID, {
position: newPosition,
modelRotation: newRotation
});
print("Moving " + this.modelID.id);
// Vec3.print("Old Position: ", this.oldModelPosition);
// Vec3.print("Sav Position: ", newPosition);
Quat.print("Old Rotation: ", this.oldModelRotation);
Quat.print("New Rotation: ", newRotation);
this.oldModelRotation = newRotation;
this.oldModelPosition = newPosition;
}
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
// / | X - base of the perpendicular line
// A---X----->B d - distance fom axis
// x x - distance from A
//
// |X-A| = (P-A).B
// X == A + ((P-A).B)B
// d = |P-X|
var A = this.palmPosition;
var B = this.front;
var P = Models.getModelProperties(modelID).position;
this.x = Vec3.dot(Vec3.subtract(P, A), B);
this.y = Vec3.dot(Vec3.subtract(P, A), this.up);
this.z = Vec3.dot(Vec3.subtract(P, A), this.right);
var X = Vec3.sum(A, Vec3.multiply(B, this.x));
var d = Vec3.length(Vec3.subtract(P, X));
// Vec3.print("A: ", A);
// Vec3.print("B: ", B);
// Vec3.print("Particle pos: ", P);
// print("d: " + d + ", x: " + this.x);
if (d < Models.getModelProperties(modelID).radius && 0 < this.x && this.x < LASER_LENGTH_FACTOR) {
return true;
}
return false;
}
this.update = function () {
@ -205,25 +257,40 @@ function controller(wichSide) {
this.checkTrigger();
if (this.pressing) {
Vec3.print("Looking at: ", this.palmPosition);
var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR);
for (var i = 0; i < foundModels.length; i++) {
print("Model found ID (" + foundModels[i].id + ")");
if (this.checkModel(foundModels[i])) {
if (this.grab(foundModels[i])) {
return;
}
}
}
}
this.moveLaser();
if (!this.pressed && this.grabbing) {
// release if trigger not pressed anymore.
this.release();
}
this.moveLaser();
if (this.pressing) {
Vec3.print("Looking at: ", this.palmPosition);
var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR);
for (var i = 0; i < foundModels.length; i++) {
if (!foundModels[i].isKnownID) {
var identify = Models.identifyModel(foundModels[i]);
if (!identify.isKnownID) {
print("Unknown ID " + identify.id + "(update loop)");
return;
}
foundModels[i] = identify;
}
var properties = Models.getModelProperties(foundModels[i]);
print("Checking properties: " + properties.id + " " + properties.isKnownID);
var check = this.checkModel(properties);
if (check.valid) {
this.grab(foundModels[i], properties);
this.x = check.x;
this.y = check.y;
this.z = check.z;
return;
}
}
}
}
this.cleanup = function () {
@ -238,78 +305,44 @@ var leftController = new controller(LEFT);
var rightController = new controller(RIGHT);
function moveModels() {
if (leftController.grabbing) {
if (rightController.grabbing) {
var properties = Models.getModelProperties(leftController.modelID);
var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x));
var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x));
var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5);
var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint));
var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x));
var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x));
var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5);
var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint));
var ratio = length / oldLength;
var newPosition = Vec3.sum(middle,
Vec3.multiply(Vec3.subtract(properties.position, oldMiddle), ratio));
Vec3.print("Ratio : " + ratio + " New position: ", newPosition);
var rotation = Quat.multiply(leftController.rotation,
Quat.inverse(leftController.oldRotation));
rotation = Quat.multiply(rotation, properties.modelRotation);
Models.editModel(leftController.modelID, {
position: newPosition,
//modelRotation: rotation,
radius: properties.radius * ratio
});
return;
} else {
var newPosition = Vec3.sum(leftController.palmPosition,
Vec3.multiply(leftController.front, leftController.x));
newPosition = Vec3.sum(newPosition,
Vec3.multiply(leftController.up, leftController.y));
newPosition = Vec3.sum(newPosition,
Vec3.multiply(leftController.right, leftController.z));
var rotation = Quat.multiply(leftController.rotation,
Quat.inverse(leftController.oldRotation));
rotation = Quat.multiply(rotation,
Models.getModelProperties(leftController.modelID).modelRotation);
Models.editModel(leftController.modelID, {
position: newPosition,
modelRotation: rotation
});
}
}
if (rightController.grabbing) {
var newPosition = Vec3.sum(rightController.palmPosition,
Vec3.multiply(rightController.front, rightController.x));
newPosition = Vec3.sum(newPosition,
Vec3.multiply(rightController.up, rightController.y));
newPosition = Vec3.sum(newPosition,
Vec3.multiply(rightController.right, rightController.z));
if (leftController.grabbing && rightController.grabbing && rightController.modelID.id == leftController.modelID.id) {
print("Both controllers");
var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x));
var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x));
var rotation = Quat.multiply(rightController.rotation,
Quat.inverse(rightController.oldRotation));
rotation = Quat.multiply(rotation,
Models.getModelProperties(rightController.modelID).modelRotation);
var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5);
var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint));
Models.editModel(rightController.modelID, {
var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x));
var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x));
var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5);
var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint));
var ratio = length / oldLength;
var newPosition = Vec3.sum(middle,
Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio));
Vec3.print("Ratio : " + ratio + " New position: ", newPosition);
var rotation = Quat.multiply(leftController.rotation,
Quat.inverse(leftController.oldRotation));
rotation = Quat.multiply(rotation, leftController.oldModelRotation);
Models.editModel(leftController.modelID, {
position: newPosition,
modelRotation: rotation
//modelRotation: rotation,
radius: leftController.oldModelRadius * ratio
});
leftController.oldModelPosition = newPosition;
leftController.oldModelRotation = rotation;
leftController.oldModelRadius *= ratio;
return;
}
leftController.moveModel();
rightController.moveModel();
}
function checkController(deltaTime) {
@ -318,6 +351,8 @@ function checkController(deltaTime) {
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
moveOverlays();
// this is expected for hydras
if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) {
//print("no hydra connected?");
@ -329,14 +364,48 @@ function checkController(deltaTime) {
moveModels();
}
function moveOverlays() {
windowDimensions = Controller.getViewportDimensions();
toolsX = windowDimensions.x - 8 - toolWidth;
toolsY = (windowDimensions.y - toolsHeight) / 2;
Overlays.addOverlay(firstModel, {
x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight,
});
}
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
var url;
if (clickedOverlay == firstModel) {
url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]);
if (url == null) {
return; }
} else {
print("Didn't click on anything");
return;
}
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
Models.addModel({ position: position,
radius: radiusDefault,
modelURL: url
});
}
function scriptEnding() {
leftController.cleanup();
rightController.cleanup();
Overlays.deleteOverlay(firstModel);
}
Script.scriptEnding.connect(scriptEnding);
// register the call back so it fires before each data send
Script.update.connect(checkController);
Controller.mousePressEvent.connect(mousePressEvent);

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -2843,7 +2843,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
// save absolute translations
glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation();
glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation();
// get the eye positions relative to the neck and use them to set the face translation
glm::vec3 leftEyePosition, rightEyePosition;
_myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3());
@ -2857,11 +2857,22 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
_myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() -
neckPosition);
// update the attachments to match
QVector<glm::vec3> absoluteAttachmentTranslations;
glm::vec3 delta = _myAvatar->getSkeletonModel().getTranslation() - absoluteSkeletonTranslation;
foreach (Model* attachment, _myAvatar->getAttachmentModels()) {
absoluteAttachmentTranslations.append(attachment->getTranslation());
attachment->setTranslation(attachment->getTranslation() + delta);
}
displaySide(_mirrorCamera, true);
// restore absolute translations
_myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
_myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation);
for (int i = 0; i < absoluteAttachmentTranslations.size(); i++) {
_myAvatar->getAttachmentModels().at(i)->setTranslation(absoluteAttachmentTranslations.at(i));
}
} else {
displaySide(_mirrorCamera, true);
}

View file

@ -215,6 +215,10 @@ Menu::Menu() :
toggleChat();
connect(&xmppClient, SIGNAL(connected()), this, SLOT(toggleChat()));
connect(&xmppClient, SIGNAL(disconnected()), this, SLOT(toggleChat()));
QDir::setCurrent(Application::resourcesPath());
// init chat window to listen chat
_chatWindow = new ChatWindow(Application::getInstance()->getWindow());
#endif
QMenu* viewMenu = addMenu("View");

View file

@ -424,19 +424,24 @@ void ModelUploader::processCheck() {
}
bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geometry) {
QSet<QByteArray> added;
foreach (FBXMesh mesh, geometry.meshes) {
foreach (FBXMeshPart part, mesh.parts) {
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty()) {
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() &&
!added.contains(part.diffuseTexture.filename)) {
if (!addPart(texdir + "/" + part.diffuseTexture.filename,
QString("texture%1").arg(++_texturesCount), true)) {
return false;
}
added.insert(part.diffuseTexture.filename);
}
if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty()) {
if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty() &&
!added.contains(part.normalTexture.filename)) {
if (!addPart(texdir + "/" + part.normalTexture.filename,
QString("texture%1").arg(++_texturesCount), true)) {
return false;
}
added.insert(part.normalTexture.filename);
}
}
}

View file

@ -36,6 +36,7 @@ void XmppClient::xmppConnected() {
_publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM);
_publicChatRoom->setNickName(AccountManager::getInstance().getAccountInfo().getUsername());
_publicChatRoom->join();
emit joinedPublicChatRoom();
}
void XmppClient::xmppError(QXmppClient::Error error) {

View file

@ -28,6 +28,9 @@ public:
QXmppClient& getXMPPClient() { return _xmppClient; }
const QXmppMucRoom* getPublicChatRoom() const { return _publicChatRoom; }
signals:
void joinedPublicChatRoom();
private slots:
void xmppConnected();
void xmppError(QXmppClient::Error error);

View file

@ -369,7 +369,7 @@ void Avatar::simulateAttachments(float deltaTime) {
glm::quat jointRotation;
if (_skeletonModel.getJointPosition(jointIndex, jointPosition) &&
_skeletonModel.getJointRotation(jointIndex, jointRotation)) {
model->setTranslation(jointPosition + jointRotation * attachment.translation * _skeletonModel.getScale());
model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale);
model->setRotation(jointRotation * attachment.rotation);
model->setScale(_skeletonModel.getScale() * attachment.scale);
model->simulate(deltaTime);

View file

@ -83,6 +83,7 @@ public:
//getters
bool isInitialized() const { return _initialized; }
SkeletonModel& getSkeletonModel() { return _skeletonModel; }
const QVector<Model*>& getAttachmentModels() const { return _attachmentModels; }
glm::vec3 getChestPosition() const;
float getScale() const { return _scale; }
const glm::vec3& getVelocity() const { return _velocity; }

View file

@ -220,15 +220,10 @@ void FaceplusReader::update() {
if (!_referenceInitialized) {
_referenceX = x;
_referenceY = y;
_referenceScale = scale;
_referenceInitialized = true;
}
const float TRANSLATION_SCALE = 10.0f;
const float REFERENCE_DISTANCE = 10.0f;
float depthScale = _referenceScale / scale;
float z = REFERENCE_DISTANCE * (depthScale - 1.0f);
glm::vec3 headTranslation((x - _referenceX) * depthScale * TRANSLATION_SCALE,
(y - _referenceY) * depthScale * TRANSLATION_SCALE, z);
glm::vec3 headTranslation((x - _referenceX) * TRANSLATION_SCALE, (y - _referenceY) * TRANSLATION_SCALE, 0.0f);
glm::quat headRotation(glm::radians(glm::vec3(-_outputVector.at(_headRotationIndices[0]),
_outputVector.at(_headRotationIndices[1]), -_outputVector.at(_headRotationIndices[2]))));
float estimatedEyePitch = (_outputVector.at(_leftEyeRotationIndices[0]) +

View file

@ -76,7 +76,6 @@ private:
int _rightEyeRotationIndices[2];
float _referenceX;
float _referenceY;
float _referenceScale;
bool _referenceInitialized;
QVector<float> _blendshapeCoefficients;
#endif

View file

@ -37,6 +37,7 @@ AttachmentsDialog::AttachmentsDialog() :
container->setLayout(_attachments = new QVBoxLayout());
container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
area->setWidget(container);
_attachments->addStretch(1);
foreach (const AttachmentData& data, Application::getInstance()->getAvatar()->getAttachmentData()) {
addAttachment(data);
@ -49,20 +50,30 @@ AttachmentsDialog::AttachmentsDialog() :
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
layout->addWidget(buttons);
connect(buttons, SIGNAL(accepted()), SLOT(deleteLater()));
_ok = buttons->button(QDialogButtonBox::Ok);
setMinimumSize(600, 600);
}
void AttachmentsDialog::setVisible(bool visible) {
QDialog::setVisible(visible);
// un-default the OK button
if (visible) {
_ok->setDefault(false);
}
}
void AttachmentsDialog::updateAttachmentData() {
QVector<AttachmentData> data;
for (int i = 0; i < _attachments->count(); i++) {
for (int i = 0; i < _attachments->count() - 1; i++) {
data.append(static_cast<AttachmentPanel*>(_attachments->itemAt(i)->widget())->getAttachmentData());
}
Application::getInstance()->getAvatar()->setAttachmentData(data);
}
void AttachmentsDialog::addAttachment(const AttachmentData& data) {
_attachments->addWidget(new AttachmentPanel(this, data));
_attachments->insertWidget(_attachments->count() - 1, new AttachmentPanel(this, data));
}
static QDoubleSpinBox* createTranslationBox(AttachmentsDialog* dialog, float value) {
@ -86,7 +97,10 @@ static QDoubleSpinBox* createRotationBox(AttachmentsDialog* dialog, float value)
}
AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) {
setFrameStyle(QFrame::StyledPanel);
QFormLayout* layout = new QFormLayout();
layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
setLayout(layout);
QHBoxLayout* urlBox = new QHBoxLayout();
@ -130,6 +144,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
QPushButton* remove = new QPushButton("Delete");
layout->addRow(remove);
connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater()));
dialog->connect(remove, SIGNAL(clicked(bool)), SLOT(updateAttachmentData()), Qt::QueuedConnection);
}
AttachmentData AttachmentPanel::getAttachmentData() const {

View file

@ -13,6 +13,7 @@
#define hifi_AttachmentsDialog_h
#include <QDialog>
#include <QFrame>
#include <AvatarData.h>
@ -29,6 +30,8 @@ public:
AttachmentsDialog();
virtual void setVisible(bool visible);
public slots:
void updateAttachmentData();
@ -40,10 +43,11 @@ private slots:
private:
QVBoxLayout* _attachments;
QPushButton* _ok;
};
/// A panel controlling a single attachment.
class AttachmentPanel : public QWidget {
class AttachmentPanel : public QFrame {
Q_OBJECT
public:

View file

@ -26,17 +26,23 @@
#include "ChatWindow.h"
const int NUM_MESSAGES_TO_TIME_STAMP = 20;
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)");
const QRegularExpression regexHifiLinks("([#@]\\S+)");
const QString mentionSoundsPath("/sounds/mention/");
const QString mentionRegex("@(\\b%1\\b)");
ChatWindow::ChatWindow(QWidget* parent) :
FramelessDialog(parent, 0, POSITION_RIGHT),
ui(new Ui::ChatWindow),
numMessagesAfterLastTimeStamp(0),
_mousePressed(false),
_mouseStartPosition()
_mouseStartPosition(),
_trayIcon(parent),
_effectPlayer()
{
setAttribute(Qt::WA_DeleteOnClose, false);
@ -77,16 +83,47 @@ ChatWindow::ChatWindow(QWidget* parent) :
ui->usersWidget->hide();
ui->messagesScrollArea->hide();
ui->messagePlainTextEdit->hide();
connect(&xmppClient, SIGNAL(connected()), this, SLOT(connected()));
connect(&XmppClient::getInstance(), SIGNAL(joinedPublicChatRoom()), this, SLOT(connected()));
}
connect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage)));
connect(&_trayIcon, SIGNAL(messageClicked()), this, SLOT(notificationClicked()));
#endif
QDir mentionSoundsDir(Application::resourcesPath() + mentionSoundsPath);
_mentionSounds = mentionSoundsDir.entryList(QDir::Files);
_trayIcon.setIcon(QIcon( Application::resourcesPath() + "/images/hifi-logo.svg"));
}
void ChatWindow::notificationClicked() {
if (parentWidget()->isMinimized()) {
parentWidget()->showNormal();
}
if (isHidden()) {
show();
}
// find last mention
int messageCount = ui->messagesVBoxLayout->count();
for (unsigned int i = messageCount; i > 0; i--) {
ChatMessageArea* area = (ChatMessageArea*)ui->messagesVBoxLayout->itemAt(i - 1)->widget();
QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getAccountInfo().getUsername()));
if (area->toPlainText().contains(usernameMention)) {
int top = area->geometry().top();
int height = area->geometry().height();
QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar();
verticalScrollBar->setSliderPosition(top - verticalScrollBar->size().height() + height);
return;
}
}
scrollToBottom();
}
ChatWindow::~ChatWindow() {
#ifdef HAVE_QXMPP
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
disconnect(&xmppClient, SIGNAL(connected()), this, SLOT(connected()));
disconnect(&xmppClient, SIGNAL(joinedPublicChatRoom()), this, SLOT(connected()));
disconnect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage)));
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
@ -105,9 +142,15 @@ void ChatWindow::keyPressEvent(QKeyEvent* event) {
void ChatWindow::showEvent(QShowEvent* event) {
FramelessDialog::showEvent(event);
if (!event->spontaneous()) {
ui->messagePlainTextEdit->setFocus();
}
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
if (xmppClient.isConnected()) {
participantsChanged();
}
}
bool ChatWindow::eventFilter(QObject* sender, QEvent* event) {
@ -304,6 +347,21 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
} else {
lastMessageStamp = QDateTime::currentDateTime();
}
QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getAccountInfo().getUsername()));
if (isHidden() && message.body().contains(usernameMention)) {
if (_effectPlayer.state() != QMediaPlayer::PlayingState) {
// get random sound
QFileInfo inf = QFileInfo(Application::resourcesPath() +
mentionSoundsPath +
_mentionSounds.at(rand() % _mentionSounds.size()));
_effectPlayer.setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
_effectPlayer.play();
}
_trayIcon.show();
_trayIcon.showMessage(windowTitle(), message.body());
}
}
#endif

View file

@ -14,6 +14,8 @@
#include <QDateTime>
#include <QDockWidget>
#include <QMediaPlayer>
#include <QSystemTrayIcon>
#include <QTimer>
#include <Application.h>
@ -63,6 +65,9 @@ private:
QDateTime lastMessageStamp;
bool _mousePressed;
QPoint _mouseStartPosition;
QSystemTrayIcon _trayIcon;
QStringList _mentionSounds;
QMediaPlayer _effectPlayer;
private slots:
void connected();
@ -71,6 +76,7 @@ private slots:
void error(QXmppClient::Error error);
void participantsChanged();
void messageReceived(const QXmppMessage& message);
void notificationClicked();
#endif
};

View file

@ -198,9 +198,6 @@ color: #0e7077</string>
<property name="indent">
<number>25</number>
</property>
<property name="buddy">
<cstring></cstring>
</property>
</widget>
</item>
<item>
@ -510,7 +507,7 @@ color: #0e7077</string>
</widget>
</item>
<item>
<widget class="QLabel" name="headLabel">
<widget class="QLabel" name="headLabel_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -547,7 +544,7 @@ color: #0e7077</string>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="horizontalLayout_1">
<item>
<widget class="QLineEdit" name="snapshotLocationEdit">
<property name="sizePolicy">
@ -564,7 +561,7 @@ color: #0e7077</string>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<spacer name="horizontalSpacer_1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>

View file

@ -46,7 +46,7 @@ const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048;
const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE;
const float MINIMUM_MODEL_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container
const QString MODEL_DEFAULT_MODEL_URL("");
const glm::quat MODEL_DEFAULT_MODEL_ROTATION(0, 0, 0, 0);
const glm::quat MODEL_DEFAULT_MODEL_ROTATION;
/// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model
/// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of