diff --git a/interface/resources/images/hifi-logo.svg b/interface/resources/images/hifi-logo.svg new file mode 100644 index 0000000000..e5d66d8f18 --- /dev/null +++ b/interface/resources/images/hifi-logo.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/images/login.svg b/interface/resources/images/login.svg new file mode 100644 index 0000000000..f45097b8e3 --- /dev/null +++ b/interface/resources/images/login.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ff3c6a1f43..00ed3c8eef 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -40,6 +40,7 @@ #include "ui/InfoView.h" #include "ui/MetavoxelEditor.h" #include "ui/ModelsBrowser.h" +#include "ui/LoginDialog.h" Menu* Menu::_instance = NULL; @@ -817,38 +818,9 @@ const int QLINE_MINIMUM_WIDTH = 400; const float DIALOG_RATIO_OF_WINDOW = 0.30f; void Menu::loginForCurrentDomain() { - QDialog loginDialog(Application::getInstance()->getWindow()); - loginDialog.setWindowTitle("Login"); - - QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); - loginDialog.setLayout(layout); - loginDialog.setWindowFlags(Qt::Sheet); - - QFormLayout* form = new QFormLayout(); - layout->addLayout(form, 1); - - QLineEdit* loginLineEdit = new QLineEdit(); - loginLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); - form->addRow("Login:", loginLineEdit); - - QLineEdit* passwordLineEdit = new QLineEdit(); - passwordLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); - passwordLineEdit->setEchoMode(QLineEdit::Password); - form->addRow("Password:", passwordLineEdit); - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - loginDialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); - loginDialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); - layout->addWidget(buttons); - - int dialogReturn = loginDialog.exec(); - - if (dialogReturn == QDialog::Accepted && !loginLineEdit->text().isEmpty() && !passwordLineEdit->text().isEmpty()) { - // attempt to get an access token given this username and password - AccountManager::getInstance().requestAccessToken(loginLineEdit->text(), passwordLineEdit->text()); - } - - sendFakeEnterEvent(); + LoginDialog* loginDialog = new LoginDialog(Application::getInstance()->getWindow()); + loginDialog->show(); + loginDialog->resizeAndPosition(false); } void Menu::editPreferences() { diff --git a/interface/src/ui/FramelessDialog.cpp b/interface/src/ui/FramelessDialog.cpp index 4919e99db6..71c58078d2 100644 --- a/interface/src/ui/FramelessDialog.cpp +++ b/interface/src/ui/FramelessDialog.cpp @@ -19,7 +19,8 @@ FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags, Positio _isResizing(false), _resizeInitialWidth(0), _selfHidden(false), - _position(position) { + _position(position), + _hideOnBlur(true) { setAttribute(Qt::WA_DeleteOnClose); @@ -43,7 +44,7 @@ bool FramelessDialog::eventFilter(QObject* sender, QEvent* event) { } break; case QEvent::WindowStateChange: - if (parentWidget()->isMinimized()) { + if (_hideOnBlur && parentWidget()->isMinimized()) { if (isVisible()) { _selfHidden = true; setHidden(true); @@ -55,7 +56,7 @@ bool FramelessDialog::eventFilter(QObject* sender, QEvent* event) { break; case QEvent::ApplicationDeactivate: // hide on minimize and focus lost - if (isVisible()) { + if (_hideOnBlur && isVisible()) { _selfHidden = true; setHidden(true); } @@ -84,18 +85,23 @@ void FramelessDialog::setStyleSheetFile(const QString& fileName) { void FramelessDialog::showEvent(QShowEvent* event) { resizeAndPosition(); + QDialog::showEvent(event); } void FramelessDialog::resizeAndPosition(bool resizeParent) { - // keep full app height - setFixedHeight(parentWidget()->size().height()); + // keep full app height or width depending on position + if (_position == POSITION_LEFT || _position == POSITION_RIGHT) { + setFixedHeight(parentWidget()->size().height()); + } else { + setFixedWidth(parentWidget()->size().width()); + } // resize parrent if width is smaller than this dialog if (resizeParent && parentWidget()->size().width() < size().width()) { parentWidget()->resize(size().width(), parentWidget()->size().height()); } - if (_position == POSITION_LEFT) { + if (_position == POSITION_LEFT || _position == POSITION_TOP) { // move to upper left corner move(parentWidget()->geometry().topLeft()); } else if (_position == POSITION_RIGHT) { @@ -104,16 +110,26 @@ void FramelessDialog::resizeAndPosition(bool resizeParent) { pos.setX(pos.x() - size().width()); move(pos); } + repaint(); } void FramelessDialog::mousePressEvent(QMouseEvent* mouseEvent) { if (mouseEvent->button() == Qt::LeftButton) { - bool hitLeft = _position == POSITION_LEFT && abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH; - bool hitRight = _position == POSITION_RIGHT && mouseEvent->pos().x() < RESIZE_HANDLE_WIDTH; - if (hitLeft || hitRight) { - _isResizing = true; - _resizeInitialWidth = size().width(); - QApplication::setOverrideCursor(Qt::SizeHorCursor); + if (_position == POSITION_LEFT || _position == POSITION_RIGHT) { + bool hitLeft = (_position == POSITION_LEFT) && (abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH); + bool hitRight = (_position == POSITION_RIGHT) && (mouseEvent->pos().x() < RESIZE_HANDLE_WIDTH); + if (hitLeft || hitRight) { + _isResizing = true; + _resizeInitialWidth = size().width(); + QApplication::setOverrideCursor(Qt::SizeHorCursor); + } + } else { + bool hitTop = (_position == POSITION_TOP) && (abs(mouseEvent->pos().y() - size().height()) < RESIZE_HANDLE_WIDTH); + if (hitTop) { + _isResizing = true; + _resizeInitialWidth = size().height(); + QApplication::setOverrideCursor(Qt::SizeHorCursor); + } } } } @@ -133,6 +149,8 @@ void FramelessDialog::mouseMoveEvent(QMouseEvent* mouseEvent) { resizeAndPosition(); _resizeInitialWidth = size().width(); setUpdatesEnabled(true); + } else if (_position == POSITION_TOP) { + resize(size().width(), mouseEvent->pos().y()); } } } diff --git a/interface/src/ui/FramelessDialog.h b/interface/src/ui/FramelessDialog.h index 828602a5db..2265f3c9e4 100644 --- a/interface/src/ui/FramelessDialog.h +++ b/interface/src/ui/FramelessDialog.h @@ -17,12 +17,15 @@ class FramelessDialog : public QDialog { Q_OBJECT - -public: - enum Position { POSITION_LEFT, POSITION_RIGHT }; - FramelessDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0, Position position = POSITION_LEFT); +public: + enum Position { POSITION_LEFT, POSITION_RIGHT, POSITION_TOP }; + + FramelessDialog(QWidget* parent, Qt::WindowFlags flags = 0, Position position = POSITION_LEFT); void setStyleSheetFile(const QString& fileName); + void setHideOnBlur(bool hideOnBlur) { _hideOnBlur = hideOnBlur; }; + bool getHideOnBlur() { return _hideOnBlur; }; + void resizeAndPosition(bool resizeParent = true); protected: virtual void mouseMoveEvent(QMouseEvent* mouseEvent); @@ -33,12 +36,11 @@ protected: bool eventFilter(QObject* sender, QEvent* event); private: - void resizeAndPosition(bool resizeParent = true); - bool _isResizing; int _resizeInitialWidth; bool _selfHidden; ///< true when the dialog itself because of a window event (deactivation or minimization) Position _position; + bool _hideOnBlur; }; diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp new file mode 100644 index 0000000000..993f355c47 --- /dev/null +++ b/interface/src/ui/LoginDialog.cpp @@ -0,0 +1,91 @@ +// +// LoginDialog.cpp +// interface/src/ui +// +// Created by Ryan Huffman on 4/23/14. +// Copyright 2014 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 +// + + +#include +#include +#include + +#include "Application.h" +#include "Menu.h" +#include "AccountManager.h" +#include "ui_loginDialog.h" + +#include "LoginDialog.h" + +const QString FORGOT_PASSWORD_URL = "https://data-web.highfidelity.io/password/new"; + +LoginDialog::LoginDialog(QWidget* parent) : + FramelessDialog(parent, 0, FramelessDialog::POSITION_TOP), + _ui(new Ui::LoginDialog) { + + _ui->setupUi(this); + _ui->errorLabel->hide(); + _ui->emailLineEdit->setFocus(); + _ui->logoLabel->setPixmap(QPixmap(Application::resourcesPath() + "images/hifi-logo.svg")); + _ui->loginButton->setIcon(QIcon(Application::resourcesPath() + "images/login.svg")); + _ui->infoLabel->setVisible(false); + _ui->errorLabel->setVisible(false); + + setModal(true); + setHideOnBlur(false); + + connect(&AccountManager::getInstance(), &AccountManager::loginComplete, + this, &LoginDialog::handleLoginCompleted); + connect(&AccountManager::getInstance(), &AccountManager::loginFailed, + this, &LoginDialog::handleLoginFailed); + connect(_ui->loginButton, &QPushButton::clicked, + this, &LoginDialog::handleLoginClicked); + connect(_ui->closeButton, &QPushButton::clicked, + this, &LoginDialog::close); +}; + +LoginDialog::~LoginDialog() { + delete _ui; +}; + +void LoginDialog::handleLoginCompleted(const QUrl& authURL) { + _ui->infoLabel->setVisible(false); + _ui->errorLabel->setVisible(false); + close(); +}; + +void LoginDialog::handleLoginFailed() { + _ui->infoLabel->setVisible(false); + _ui->errorLabel->setVisible(true); + + _ui->errorLabel->show(); + _ui->loginArea->setDisabled(false); + + // Move focus to password and select the entire line + _ui->passwordLineEdit->setFocus(); + _ui->passwordLineEdit->setSelection(0, _ui->emailLineEdit->maxLength()); +}; + +void LoginDialog::handleLoginClicked() { + // If the email or password inputs are empty, move focus to them, otherwise attempt to login. + if (_ui->emailLineEdit->text().isEmpty()) { + _ui->emailLineEdit->setFocus(); + } else if (_ui->passwordLineEdit->text().isEmpty()) { + _ui->passwordLineEdit->setFocus(); + } else { + _ui->infoLabel->setVisible(true); + _ui->errorLabel->setVisible(false); + + _ui->loginArea->setDisabled(true); + AccountManager::getInstance().requestAccessToken(_ui->emailLineEdit->text(), _ui->passwordLineEdit->text()); + } +}; + +void LoginDialog::moveEvent(QMoveEvent* event) { + // Modal dialogs seemed to get repositioned automatically. Combat this by moving the window if needed. + resizeAndPosition(); +}; diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h new file mode 100644 index 0000000000..b4aad06614 --- /dev/null +++ b/interface/src/ui/LoginDialog.h @@ -0,0 +1,42 @@ +// +// LoginDialog.h +// interface/src/ui +// +// Created by Ryan Huffman on 4/23/14. +// Copyright 2014 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 +// + +#ifndef hifi_LoginDialog_h +#define hifi_LoginDialog_h + +#include +#include "FramelessDialog.h" + +namespace Ui { + class LoginDialog; +} + +class LoginDialog : public FramelessDialog { + Q_OBJECT + +public: + LoginDialog(QWidget* parent); + ~LoginDialog(); + +public slots: + void handleLoginClicked(); + void handleLoginCompleted(const QUrl& authURL); + void handleLoginFailed(); + +protected: + void moveEvent(QMoveEvent* event); + +private: + Ui::LoginDialog* _ui; + +}; + +#endif // hifi_LoginDialog_h diff --git a/interface/ui/loginDialog.ui b/interface/ui/loginDialog.ui new file mode 100644 index 0000000000..d54b3c833f --- /dev/null +++ b/interface/ui/loginDialog.ui @@ -0,0 +1,491 @@ + + + LoginDialog + + + + 0 + 0 + 1003 + 130 + + + + + 0 + 130 + + + + Dialog + + + font-family: Helvetica, Arial, sans-serif; + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + 5 + + + 0 + + + 10 + + + 10 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 825 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Helvetica,Arial,sans-serif + 17 + + + + Authenticating... + + + + + + + true + + + + Helvetica,Arial,sans-serif + 17 + + + + color: #992800; + + + <style type="text/css"> + a { text-decoration: none; color: #267077;} +</style> +Invalid username or password. <a href="https://data-web.highfidelity.io/password/new">Recover?</a> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 16 + 16 + + + + + 16 + 16 + + + + SplitHCursor + + + + + + + + + + ../resources/images/close.svg../resources/images/close.svg + + + true + + + + + + + + + + true + + + + 0 + 0 + + + + + 0 + 102 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 30 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 12 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + ../resources/images/hifi-logo.png + + + + + + + + + + + 0 + 0 + + + + + 931 + 60 + + + + QLineEdit { + padding: 10px; +border-width: 1px; border-style: solid; border-radius: 3px; border-color: #aaa; +} + + + + 12 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 300 + 54 + + + + + Helvetica,Arial,sans-serif + 20 + + + + padding-top: 14px; + + + + + + Username or Email + + + + + + + + 0 + 0 + + + + + 300 + 54 + + + + + Helvetica,Arial,sans-serif + 20 + + + + padding-top: 14px; + + + + + + QLineEdit::Password + + + Password + + + + + + + + 0 + 0 + + + + + 141 + 54 + + + + + Helvetica,Arial,sans-serif + 20 + + + + PointingHandCursor + + + +background: #0e7077; +color: #e7eeee; +border-radius: 4px; padding-top: 1px; + + + Login + + + + ../resources/images/login.svg../resources/images/login.svg + + + + 32 + 32 + + + + true + + + true + + + + + + + + Helvetica,Arial,sans-serif + 17 + + + + <style type="text/css"> + a { text-decoration: none; color: #267077;} +</style> +<a href="https://data-web.highfidelity.io/password/new">Recover password?</a> + + + true + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 278923026d..547768ec48 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -337,6 +337,7 @@ void AccountManager::requestFinished() { } else { // TODO: error handling qDebug() << "Error in response for password grant -" << rootObject["error_description"].toString(); + emit loginFailed(); } } diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index cb76786f4e..8df75195cf 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -71,6 +71,7 @@ signals: void usernameChanged(const QString& username); void accessTokenChanged(); void loginComplete(const QUrl& authURL); + void loginFailed(); void logoutComplete(); private slots: void processReply();