/* * Copyright (C) by Roeland Jago Douma * Copyright (C) 2015 by Klaas Freitag * * 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. */ #include "ui_sharelinkwidget.h" #include "sharelinkwidget.h" #include "account.h" #include "capabilities.h" #include "guiutility.h" #include "sharemanager.h" #include "theme.h" #include "elidedlabel.h" #include "QProgressIndicator.h" #include #include #include #include #include #include #include #include #include namespace { const char *passwordIsSetPlaceholder = "●●●●●●●●"; } namespace OCC { Q_LOGGING_CATEGORY(lcShareLink, "nextcloud.gui.sharelink", QtInfoMsg) ShareLinkWidget::ShareLinkWidget(AccountPtr account, const QString &sharePath, const QString &localPath, SharePermissions maxSharingPermissions, QWidget *parent) : QWidget(parent) , _ui(new Ui::ShareLinkWidget) , _account(account) , _sharePath(sharePath) , _localPath(localPath) , _linkShare(nullptr) , _passwordRequired(false) , _expiryRequired(false) , _namesSupported(true) , _noteRequired(false) , _linkContextMenu(nullptr) , _readOnlyLinkAction(nullptr) , _allowEditingLinkAction(nullptr) , _allowUploadEditingLinkAction(nullptr) , _allowUploadLinkAction(nullptr) , _passwordProtectLinkAction(nullptr) , _expirationDateLinkAction(nullptr) , _unshareLinkAction(nullptr) , _noteLinkAction(nullptr) { _ui->setupUi(this); _ui->shareLinkToolButton->hide(); //Is this a file or folder? QFileInfo fi(localPath); _isFile = fi.isFile(); connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink); connect(_ui->lineEdit_password, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotCreatePassword); connect(_ui->confirmPassword, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreatePassword); connect(_ui->confirmNote, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreateNote); connect(_ui->confirmExpirationDate, &QAbstractButton::clicked, this, &ShareLinkWidget::slotSetExpireDate); _ui->errorLabel->hide(); auto sharingPossible = true; if (!_account->capabilities().sharePublicLink()) { qCWarning(lcShareLink) << "Link shares have been disabled"; sharingPossible = false; } else if (!(maxSharingPermissions & SharePermissionShare)) { qCWarning(lcShareLink) << "The file can not be shared because it was shared without sharing permission."; sharingPossible = false; } _ui->enableShareLink->setChecked(false); _ui->shareLinkToolButton->setEnabled(false); _ui->shareLinkToolButton->hide(); // Older servers don't support multiple public link shares if (!_account->capabilities().sharePublicLinkMultiple()) { _namesSupported = false; } togglePasswordOptions(false); toggleExpireDateOptions(false); toggleNoteOptions(false); _ui->noteProgressIndicator->setVisible(false); _ui->passwordProgressIndicator->setVisible(false); _ui->expirationDateProgressIndicator->setVisible(false); _ui->sharelinkProgressIndicator->setVisible(false); // check if the file is already inside of a synced folder if (sharePath.isEmpty()) { qCWarning(lcShareLink) << "Unable to share files not in a sync folder."; return; } } ShareLinkWidget::~ShareLinkWidget() { delete _ui; } void ShareLinkWidget::slotToggleShareLinkAnimation(const bool start) { _ui->sharelinkProgressIndicator->setVisible(start); if (start) { if (!_ui->sharelinkProgressIndicator->isAnimated()) { _ui->sharelinkProgressIndicator->startAnimation(); } } else { _ui->sharelinkProgressIndicator->stopAnimation(); } } void ShareLinkWidget::toggleButtonAnimation(QToolButton *button, QProgressIndicator *progressIndicator, const QAction *checkedAction) const { auto startAnimation = false; const auto actionIsChecked = checkedAction->isChecked(); if (!progressIndicator->isAnimated() && actionIsChecked) { progressIndicator->startAnimation(); startAnimation = true; } else { progressIndicator->stopAnimation(); } button->setVisible(!startAnimation && actionIsChecked); progressIndicator->setVisible(startAnimation && actionIsChecked); } void ShareLinkWidget::setLinkShare(QSharedPointer linkShare) { _linkShare = linkShare; } QSharedPointer ShareLinkWidget::getLinkShare() { return _linkShare; } void ShareLinkWidget::focusPasswordLineEdit() { _ui->lineEdit_password->setFocus(); } void ShareLinkWidget::setupUiOptions() { connect(_linkShare.data(), &LinkShare::noteSet, this, &ShareLinkWidget::slotNoteSet); connect(_linkShare.data(), &LinkShare::passwordSet, this, &ShareLinkWidget::slotPasswordSet); connect(_linkShare.data(), &LinkShare::passwordSetError, this, &ShareLinkWidget::slotPasswordSetError); connect(_linkShare.data(), &LinkShare::labelSet, this, &ShareLinkWidget::slotLabelSet); // Prepare permissions check and create group action const QDate expireDate = _linkShare.data()->getExpireDate().isValid() ? _linkShare.data()->getExpireDate() : QDate(); const SharePermissions perm = _linkShare.data()->getPermissions(); auto checked = false; auto *permissionsGroup = new QActionGroup(this); // Prepare sharing menu _linkContextMenu = new QMenu(this); // radio button style permissionsGroup->setExclusive(true); if (_isFile) { checked = (perm & SharePermissionRead) && (perm & SharePermissionUpdate); _allowEditingLinkAction = _linkContextMenu->addAction(tr("Allow editing")); _allowEditingLinkAction->setCheckable(true); _allowEditingLinkAction->setChecked(checked); } else { checked = (perm == SharePermissionRead); _readOnlyLinkAction = permissionsGroup->addAction(tr("View only")); _readOnlyLinkAction->setCheckable(true); _readOnlyLinkAction->setChecked(checked); checked = (perm & SharePermissionRead) && (perm & SharePermissionCreate) && (perm & SharePermissionUpdate) && (perm & SharePermissionDelete); _allowUploadEditingLinkAction = permissionsGroup->addAction(tr("Allow upload and editing")); _allowUploadEditingLinkAction->setCheckable(true); _allowUploadEditingLinkAction->setChecked(checked); checked = (perm == SharePermissionCreate); _allowUploadLinkAction = permissionsGroup->addAction(tr("File drop (upload only)")); _allowUploadLinkAction->setCheckable(true); _allowUploadLinkAction->setChecked(checked); } _shareLinkElidedLabel = new OCC::ElidedLabel(this); _shareLinkElidedLabel->setElideMode(Qt::ElideRight); displayShareLinkLabel(); _ui->horizontalLayout->insertWidget(2, _shareLinkElidedLabel); _shareLinkLayout = new QHBoxLayout(this); _shareLinkLabel = new QLabel(this); _shareLinkLabel->setPixmap(QString(":/client/theme/black/edit.svg")); _shareLinkLayout->addWidget(_shareLinkLabel); _shareLinkEdit = new QLineEdit(this); connect(_shareLinkEdit, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotCreateLabel); _shareLinkEdit->setPlaceholderText(tr("Link name")); _shareLinkEdit->setText(_linkShare.data()->getLabel()); _shareLinkLayout->addWidget(_shareLinkEdit); _shareLinkButton = new QToolButton(this); connect(_shareLinkButton, &QToolButton::clicked, this, &ShareLinkWidget::slotCreateLabel); _shareLinkButton->setIcon(QIcon(":/client/theme/confirm.svg")); _shareLinkButton->setToolButtonStyle(Qt::ToolButtonIconOnly); _shareLinkLayout->addWidget(_shareLinkButton); _shareLinkProgressIndicator = new QProgressIndicator(this); _shareLinkProgressIndicator->setVisible(false); _shareLinkLayout->addWidget(_shareLinkProgressIndicator); _shareLinkDefaultWidget = new QWidget(this); _shareLinkDefaultWidget->setLayout(_shareLinkLayout); _shareLinkWidgetAction = new QWidgetAction(this); _shareLinkWidgetAction->setDefaultWidget(_shareLinkDefaultWidget); _shareLinkWidgetAction->setCheckable(true); _linkContextMenu->addAction(_shareLinkWidgetAction); // Adds permissions actions (radio button style) if (_isFile) { _linkContextMenu->addAction(_allowEditingLinkAction); } else { _linkContextMenu->addAction(_readOnlyLinkAction); _linkContextMenu->addAction(_allowUploadEditingLinkAction); _linkContextMenu->addAction(_allowUploadLinkAction); } // Adds action to display note widget (check box) _noteLinkAction = _linkContextMenu->addAction(tr("Note to recipient")); _noteLinkAction->setCheckable(true); if (_linkShare->getNote().isSimpleText() && !_linkShare->getNote().isEmpty()) { _ui->textEdit_note->setText(_linkShare->getNote()); _noteLinkAction->setChecked(true); toggleNoteOptions(); } // Adds action to display password widget (check box) _passwordProtectLinkAction = _linkContextMenu->addAction(tr("Password protect")); _passwordProtectLinkAction->setCheckable(true); if (_linkShare.data()->isPasswordSet()) { _passwordProtectLinkAction->setChecked(true); _ui->lineEdit_password->setPlaceholderText(QString::fromUtf8(passwordIsSetPlaceholder)); togglePasswordOptions(); } // If password is enforced then don't allow users to disable it if (_account->capabilities().sharePublicLinkEnforcePassword()) { if (_linkShare.data()->isPasswordSet()) { _passwordProtectLinkAction->setChecked(true); _passwordProtectLinkAction->setEnabled(false); } _passwordRequired = true; } // Adds action to display expiration date widget (check box) _expirationDateLinkAction = _linkContextMenu->addAction(tr("Set expiration date")); _expirationDateLinkAction->setCheckable(true); if (!expireDate.isNull()) { _ui->calendar->setDate(expireDate); _expirationDateLinkAction->setChecked(true); toggleExpireDateOptions(); } connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareLinkWidget::slotSetExpireDate); connect(_linkShare.data(), &LinkShare::expireDateSet, this, &ShareLinkWidget::slotExpireDateSet); // If expiredate is enforced do not allow disable and set max days if (_account->capabilities().sharePublicLinkEnforceExpireDate()) { _ui->calendar->setMaximumDate(QDate::currentDate().addDays( _account->capabilities().sharePublicLinkExpireDateDays())); _expirationDateLinkAction->setChecked(true); _expirationDateLinkAction->setEnabled(false); _expiryRequired = true; } // Adds action to unshare widget (check box) _unshareLinkAction = _linkContextMenu->addAction(QIcon(":/client/theme/delete.svg"), tr("Delete link")); _linkContextMenu->addSeparator(); _addAnotherLinkAction = _linkContextMenu->addAction(QIcon(":/client/theme/add.svg"), tr("Add another link")); _ui->enableShareLink->setIcon(QIcon(":/client/theme/copy.svg")); disconnect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink); connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCopyLinkShare); connect(_linkContextMenu, &QMenu::triggered, this, &ShareLinkWidget::slotLinkContextMenuActionTriggered); _ui->shareLinkToolButton->setMenu(_linkContextMenu); _ui->shareLinkToolButton->setEnabled(true); _ui->enableShareLink->setEnabled(true); _ui->enableShareLink->setChecked(true); // show sharing options _ui->shareLinkToolButton->show(); customizeStyle(); } void ShareLinkWidget::slotCreateNote() { const auto note = _ui->textEdit_note->toPlainText(); if (!_linkShare || _linkShare->getNote() == note || note.isEmpty()) { return; } toggleButtonAnimation(_ui->confirmNote, _ui->noteProgressIndicator, _noteLinkAction); _ui->errorLabel->hide(); _linkShare->setNote(note); } void ShareLinkWidget::slotNoteSet() { toggleButtonAnimation(_ui->confirmNote, _ui->noteProgressIndicator, _noteLinkAction); } void ShareLinkWidget::slotCopyLinkShare(const bool clicked) const { Q_UNUSED(clicked); QApplication::clipboard()->setText(_linkShare->getLink().toString()); } void ShareLinkWidget::slotExpireDateSet() { toggleButtonAnimation(_ui->confirmExpirationDate, _ui->expirationDateProgressIndicator, _expirationDateLinkAction); } void ShareLinkWidget::slotSetExpireDate() { if (!_linkShare) { return; } toggleButtonAnimation(_ui->confirmExpirationDate, _ui->expirationDateProgressIndicator, _expirationDateLinkAction); _ui->errorLabel->hide(); _linkShare->setExpireDate(_ui->calendar->date()); } void ShareLinkWidget::slotCreatePassword() { if (!_linkShare || _ui->lineEdit_password->text().isEmpty()) { return; } toggleButtonAnimation(_ui->confirmPassword, _ui->passwordProgressIndicator, _passwordProtectLinkAction); _ui->errorLabel->hide(); emit createPassword(_ui->lineEdit_password->text()); } void ShareLinkWidget::slotCreateShareLink(const bool clicked) { Q_UNUSED(clicked); slotToggleShareLinkAnimation(true); emit createLinkShare(); } void ShareLinkWidget::slotPasswordSet() { toggleButtonAnimation(_ui->confirmPassword, _ui->passwordProgressIndicator, _passwordProtectLinkAction); _ui->lineEdit_password->setText({}); if (_linkShare->isPasswordSet()) { _ui->lineEdit_password->setEnabled(true); _ui->lineEdit_password->setPlaceholderText(QString::fromUtf8(passwordIsSetPlaceholder)); } else { _ui->lineEdit_password->setPlaceholderText({}); } emit createPasswordProcessed(); } void ShareLinkWidget::slotPasswordSetError(const int code, const QString &message) { toggleButtonAnimation(_ui->confirmPassword, _ui->passwordProgressIndicator, _passwordProtectLinkAction); slotServerError(code, message); togglePasswordOptions(); _ui->lineEdit_password->setFocus(); emit createPasswordProcessed(); } void ShareLinkWidget::startAnimation(const int start, const int end) { auto *animation = new QPropertyAnimation(this, "maximumHeight", this); animation->setDuration(500); animation->setStartValue(start); animation->setEndValue(end); connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotAnimationFinished); if (end < start) // that is to remove the widget, not to show it connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotDeleteAnimationFinished); connect(animation, &QVariantAnimation::valueChanged, this, &ShareLinkWidget::resizeRequested); animation->start(); } void ShareLinkWidget::slotDeleteShareFetched() { slotToggleShareLinkAnimation(false); _linkShare.clear(); togglePasswordOptions(false); toggleNoteOptions(false); toggleExpireDateOptions(false); emit deleteLinkShare(); } void ShareLinkWidget::toggleNoteOptions(const bool enable) { _ui->noteLabel->setVisible(enable); _ui->textEdit_note->setVisible(enable); _ui->confirmNote->setVisible(enable); _ui->textEdit_note->setText(enable && _linkShare ? _linkShare->getNote() : QString()); if (!enable && _linkShare && !_linkShare->getNote().isEmpty()) { _linkShare->setNote({}); } } void ShareLinkWidget::slotAnimationFinished() { emit resizeRequested(); deleteLater(); } void ShareLinkWidget::slotCreateLabel() { const auto labelText = _shareLinkEdit->text(); if (!_linkShare || _linkShare->getLabel() == labelText || labelText.isEmpty()) { return; } _shareLinkWidgetAction->setChecked(true); toggleButtonAnimation(_shareLinkButton, _shareLinkProgressIndicator, _shareLinkWidgetAction); _ui->errorLabel->hide(); _linkShare->setLabel(_shareLinkEdit->text()); } void ShareLinkWidget::slotLabelSet() { toggleButtonAnimation(_shareLinkButton, _shareLinkProgressIndicator, _shareLinkWidgetAction); displayShareLinkLabel(); } void ShareLinkWidget::slotDeleteAnimationFinished() { // There is a painting bug where a small line of this widget isn't // properly cleared. This explicit repaint() call makes sure any trace of // the share widget is removed once it's destroyed. #4189 connect(this, SIGNAL(destroyed(QObject *)), parentWidget(), SLOT(repaint())); } void ShareLinkWidget::slotCreateShareRequiresPassword(const QString &message) { slotToggleShareLinkAnimation(message.isEmpty()); if (!message.isEmpty()) { _ui->errorLabel->setText(message); _ui->errorLabel->show(); } _passwordRequired = true; togglePasswordOptions(); } void ShareLinkWidget::togglePasswordOptions(const bool enable) { _ui->passwordLabel->setVisible(enable); _ui->lineEdit_password->setVisible(enable); _ui->confirmPassword->setVisible(enable); _ui->lineEdit_password->setFocus(); if (!enable && _linkShare && _linkShare->isPasswordSet()) { _linkShare->setPassword({}); } } void ShareLinkWidget::toggleExpireDateOptions(const bool enable) { _ui->expirationLabel->setVisible(enable); _ui->calendar->setVisible(enable); _ui->confirmExpirationDate->setVisible(enable); const auto date = enable ? _linkShare->getExpireDate() : QDate::currentDate().addDays(1); _ui->calendar->setDate(date); _ui->calendar->setMinimumDate(date); _ui->calendar->setFocus(); if (!enable && _linkShare && _linkShare->getExpireDate().isValid()) { _linkShare->setExpireDate({}); } } void ShareLinkWidget::confirmAndDeleteShare() { auto messageBox = new QMessageBox( QMessageBox::Question, tr("Confirm Link Share Deletion"), tr("

Do you really want to delete the public link share %1?

" "

Note: This action cannot be undone.

") .arg(shareName()), QMessageBox::NoButton, this); QPushButton *yesButton = messageBox->addButton(tr("Delete"), QMessageBox::YesRole); messageBox->addButton(tr("Cancel"), QMessageBox::NoRole); connect(messageBox, &QMessageBox::finished, this, [messageBox, yesButton, this]() { if (messageBox->clickedButton() == yesButton) { this->slotToggleShareLinkAnimation(true); this->_linkShare->deleteShare(); } }); messageBox->open(); } QString ShareLinkWidget::shareName() const { QString name = _linkShare->getName(); if (!name.isEmpty()) return name; if (!_namesSupported) return tr("Public link"); return _linkShare->getToken(); } void ShareLinkWidget::slotContextMenuButtonClicked() { _linkContextMenu->exec(QCursor::pos()); } void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action) { const auto state = action->isChecked(); SharePermissions perm = SharePermissionRead; if (action == _addAnotherLinkAction) { emit createLinkShare(); } else if (action == _readOnlyLinkAction && state) { _linkShare->setPermissions(perm); } else if (action == _allowEditingLinkAction && state) { perm |= SharePermissionUpdate; _linkShare->setPermissions(perm); } else if (action == _allowUploadEditingLinkAction && state) { perm |= SharePermissionCreate | SharePermissionUpdate | SharePermissionDelete; _linkShare->setPermissions(perm); } else if (action == _allowUploadLinkAction && state) { perm = SharePermissionCreate; _linkShare->setPermissions(perm); } else if (action == _passwordProtectLinkAction) { togglePasswordOptions(state); } else if (action == _expirationDateLinkAction) { toggleExpireDateOptions(state); } else if (action == _noteLinkAction) { toggleNoteOptions(state); } else if (action == _unshareLinkAction) { confirmAndDeleteShare(); } } void ShareLinkWidget::slotServerError(const int code, const QString &message) { slotToggleShareLinkAnimation(false); qCWarning(lcSharing) << "Error from server" << code << message; displayError(message); } void ShareLinkWidget::displayError(const QString &errMsg) { _ui->errorLabel->setText(errMsg); _ui->errorLabel->show(); } void ShareLinkWidget::slotStyleChanged() { customizeStyle(); } void ShareLinkWidget::customizeStyle() { _unshareLinkAction->setIcon(Theme::createColorAwareIcon(":/client/theme/delete.svg")); _addAnotherLinkAction->setIcon(Theme::createColorAwareIcon(":/client/theme/add.svg")); _ui->enableShareLink->setIcon(Theme::createColorAwareIcon(":/client/theme/copy.svg")); _ui->shareLinkIconLabel->setPixmap(Theme::createColorAwarePixmap(":/client/theme/public.svg")); _ui->shareLinkToolButton->setIcon(Theme::createColorAwareIcon(":/client/theme/more.svg")); _ui->confirmNote->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg")); _ui->confirmPassword->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg")); _ui->confirmExpirationDate->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg")); _ui->passwordProgressIndicator->setColor(QGuiApplication::palette().color(QPalette::Text)); } void ShareLinkWidget::displayShareLinkLabel() { _shareLinkElidedLabel->clear(); if (!_linkShare->getLabel().isEmpty()) { _shareLinkElidedLabel->setText(QString("(%1)").arg(_linkShare->getLabel())); } } }