/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <vcl/svapp.hxx>
#include <strings.hrc>
#include <retypepassdlg.hxx>
#include <scresid.hxx>
#include <document.hxx>
#include <tabprotection.hxx>
 
ScRetypePassDlg::ScRetypePassDlg(weld::Window* pParent)
    : GenericDialogController(pParent, u"modules/scalc/ui/retypepassdialog.ui"_ustr,
                              u"RetypePass"_ustr)
    , maTextNotProtected(ScResId(STR_NOT_PROTECTED))
    , maTextNotPassProtected(ScResId(STR_NOT_PASS_PROTECTED))
    , maTextHashBad(ScResId(STR_HASH_BAD))
    , maTextHashGood(ScResId(STR_HASH_GOOD))
    , meDesiredHash(PASSHASH_SHA1)
    , mxBtnOk(m_xBuilder->weld_button(u"ok"_ustr))
    , mxTextDocStatus(m_xBuilder->weld_label(u"docStatusLabel"_ustr))
    , mxBtnRetypeDoc(m_xBuilder->weld_button(u"retypeDocButton"_ustr))
    , mxScrolledWindow(m_xBuilder->weld_scrolled_window(u"scrolledwindow"_ustr))
    , mxSheetsBox(m_xBuilder->weld_container(u"sheetsBox"_ustr))
{
    mxScrolledWindow->set_size_request(mxScrolledWindow->get_approximate_digit_width() * 46,
                                       mxScrolledWindow->get_text_height() * 10);
    Init();
}
 
ScRetypePassDlg::~ScRetypePassDlg() {}
 
void ScRetypePassDlg::DeleteSheets() { maSheets.clear(); }
 
short ScRetypePassDlg::run()
{
    PopulateDialog();
    CheckHashStatus();
    return GenericDialogController::run();
}
 
PassFragment::PassFragment(weld::Widget* pParent)
    : m_xBuilder(Application::CreateBuilder(pParent, u"modules/scalc/ui/passfragment.ui"_ustr))
    , m_xSheetsBox(m_xBuilder->weld_container(u"PassEntry"_ustr))
    , m_xName(m_xBuilder->weld_label(u"name"_ustr))
    , m_xStatus(m_xBuilder->weld_label(u"status"_ustr))
    , m_xButton(m_xBuilder->weld_button(u"button"_ustr))
{
    m_xButton->set_label(ScResId(STR_RETYPE));
}
 
void ScRetypePassDlg::SetDataFromDocument(const ScDocument& rDoc)
{
    DeleteSheets();
    const ScDocProtection* pDocProtect = rDoc.GetDocProtection();
    if (pDocProtect && pDocProtect->isProtected())
        mpDocItem = std::make_shared<ScDocProtection>(*pDocProtect);
 
    SCTAB nTabCount = rDoc.GetTableCount();
    maTableItems.reserve(nTabCount);
    maSheets.reserve(nTabCount);
    for (SCTAB i = 0; i < nTabCount; ++i)
    {
        TableItem aTabItem;
        rDoc.GetName(i, aTabItem.maName);
 
        const ScTableProtection* pTabProtect = rDoc.GetTabProtection(i);
        if (pTabProtect && pTabProtect->isProtected())
            aTabItem.mpProtect = std::make_shared<ScTableProtection>(*pTabProtect);
 
        maTableItems.push_back(aTabItem);
        maSheets.emplace_back(new PassFragment(mxSheetsBox.get()));
        maSheets.back()->m_xButton->connect_clicked(LINK(this, ScRetypePassDlg, RetypeBtnHdl));
    }
}
 
void ScRetypePassDlg::SetDesiredHash(ScPasswordHash eHash) { meDesiredHash = eHash; }
 
void ScRetypePassDlg::WriteNewDataToDocument(ScDocument& rDoc) const
{
    if (mpDocItem)
        rDoc.SetDocProtection(mpDocItem.get());
 
    size_t nTabCount = static_cast<size_t>(rDoc.GetTableCount());
    size_t n = maTableItems.size();
    for (size_t i = 0; i < n; ++i)
    {
        if (i >= nTabCount)
            break;
 
        ScTableProtection* pTabProtect = maTableItems[i].mpProtect.get();
        if (pTabProtect)
            rDoc.SetTabProtection(static_cast<SCTAB>(i), pTabProtect);
    }
}
 
void ScRetypePassDlg::Init()
{
    Link<weld::Button&, void> aLink = LINK(this, ScRetypePassDlg, OKHdl);
    mxBtnOk->connect_clicked(aLink);
 
    aLink = LINK(this, ScRetypePassDlg, RetypeBtnHdl);
    mxBtnRetypeDoc->connect_clicked(aLink);
 
    mxTextDocStatus->set_label(maTextNotProtected);
    mxBtnRetypeDoc->set_sensitive(false);
}
 
void ScRetypePassDlg::PopulateDialog()
{
    // Document protection first.
    SetDocData();
 
    // Sheet protection next.
    for (size_t i = 0; i < maTableItems.size(); ++i)
        SetTableData(i, static_cast<SCTAB>(i));
}
 
void ScRetypePassDlg::SetDocData()
{
    bool bBtnEnabled = false;
    if (mpDocItem && mpDocItem->isProtected())
    {
        if (mpDocItem->isPasswordEmpty())
            mxTextDocStatus->set_label(maTextNotPassProtected);
        else if (mpDocItem->hasPasswordHash(meDesiredHash))
            mxTextDocStatus->set_label(maTextHashGood);
        else
        {
            // incompatible hash
            mxTextDocStatus->set_label(maTextHashBad);
            bBtnEnabled = true;
        }
    }
    mxBtnRetypeDoc->set_sensitive(bBtnEnabled);
}
 
void ScRetypePassDlg::SetTableData(size_t nRowPos, SCTAB nTab)
{
    if (nRowPos >= maSheets.size())
        return;
 
    weld::Label& rName = *maSheets[nRowPos]->m_xName;
    weld::Label& rStatus = *maSheets[nRowPos]->m_xStatus;
    weld::Button& rBtn = *maSheets[nRowPos]->m_xButton;
 
    bool bBtnEnabled = false;
    rName.set_label(maTableItems[nTab].maName);
    const ScTableProtection* pTabProtect = maTableItems[nTab].mpProtect.get();
    if (pTabProtect && pTabProtect->isProtected())
    {
        if (pTabProtect->isPasswordEmpty())
            rStatus.set_label(maTextNotPassProtected);
        else if (pTabProtect->hasPasswordHash(meDesiredHash))
            rStatus.set_label(maTextHashGood);
        else
        {
            // incompatible hash
            rStatus.set_label(maTextHashBad);
            bBtnEnabled = true;
        }
    }
    else
        rStatus.set_label(maTextNotProtected);
 
    rBtn.set_sensitive(bBtnEnabled);
}
 
static bool lcl_IsInGoodStatus(const ScPassHashProtectable* pProtected, ScPasswordHash eDesiredHash)
{
    if (!pProtected || !pProtected->isProtected())
        // Not protected.
        return true;
 
    if (pProtected->isPasswordEmpty())
        return true;
 
    if (pProtected->hasPasswordHash(eDesiredHash))
        return true;
 
    return false;
}
 
void ScRetypePassDlg::CheckHashStatus()
{
    do
    {
        if (!lcl_IsInGoodStatus(mpDocItem.get(), meDesiredHash))
            break;
 
        bool bStatusGood = true;
        size_t nTabCount = maTableItems.size();
        for (size_t i = 0; i < nTabCount && bStatusGood; ++i)
        {
            if (!lcl_IsInGoodStatus(maTableItems[i].mpProtect.get(), meDesiredHash))
                bStatusGood = false;
        }
        if (!bStatusGood)
            break;
 
        mxBtnOk->set_sensitive(true);
        return;
    } while (false);
 
    mxBtnOk->set_sensitive(false);
}
 
IMPL_LINK_NOARG(ScRetypePassDlg, OKHdl, weld::Button&, void) { m_xDialog->response(RET_OK); }
 
IMPL_LINK(ScRetypePassDlg, RetypeBtnHdl, weld::Button&, rBtn, void)
{
    ScPassHashProtectable* pProtected = nullptr;
    if (&rBtn == mxBtnRetypeDoc.get())
    {
        // document protection.
        pProtected = mpDocItem.get();
    }
    else
    {
        // sheet protection.
        size_t aPos = 0;
        while (aPos < maSheets.size() && &rBtn != maSheets[aPos]->m_xButton.get())
            ++aPos;
 
        pProtected = aPos < maSheets.size() ? maTableItems[aPos].mpProtect.get() : nullptr;
    }
 
    if (!pProtected)
        // What the ... !?
        return;
 
    ScRetypePassInputDlg aDlg(m_xDialog.get(), pProtected);
    if (aDlg.run() != RET_OK)
        return;
 
    // OK is pressed.  Update the protected item.
    if (aDlg.IsRemovePassword())
    {
        // Remove password from this item.
        pProtected->setPassword(OUString());
    }
    else
    {
        // Set a new password.
        OUString aNewPass = aDlg.GetNewPassword();
        pProtected->setPassword(aNewPass);
    }
 
    SetDocData();
    CheckHashStatus();
}
 
ScRetypePassInputDlg::ScRetypePassInputDlg(weld::Window* pParent, ScPassHashProtectable* pProtected)
    : GenericDialogController(pParent, u"modules/scalc/ui/retypepassworddialog.ui"_ustr,
                              u"RetypePasswordDialog"_ustr)
    , m_pProtected(pProtected)
    , m_xBtnOk(m_xBuilder->weld_button(u"ok"_ustr))
    , m_xBtnRetypePassword(m_xBuilder->weld_radio_button(u"retypepassword"_ustr))
    , m_xPasswordGrid(m_xBuilder->weld_widget(u"passwordgrid"_ustr))
    , m_xPassword1Edit(m_xBuilder->weld_entry(u"newpassEntry"_ustr))
    , m_xPassword2Edit(m_xBuilder->weld_entry(u"confirmpassEntry"_ustr))
    , m_xBtnMatchOldPass(m_xBuilder->weld_check_button(u"mustmatch"_ustr))
    , m_xBtnRemovePassword(m_xBuilder->weld_radio_button(u"removepassword"_ustr))
{
    Init();
}
 
ScRetypePassInputDlg::~ScRetypePassInputDlg() {}
 
bool ScRetypePassInputDlg::IsRemovePassword() const { return m_xBtnRemovePassword->get_active(); }
 
OUString ScRetypePassInputDlg::GetNewPassword() const { return m_xPassword1Edit->get_text(); }
 
void ScRetypePassInputDlg::Init()
{
    m_xBtnOk->connect_clicked(LINK(this, ScRetypePassInputDlg, OKHdl));
    m_xBtnRetypePassword->connect_toggled(LINK(this, ScRetypePassInputDlg, RadioBtnHdl));
    m_xBtnRemovePassword->connect_toggled(LINK(this, ScRetypePassInputDlg, RadioBtnHdl));
    m_xBtnMatchOldPass->connect_toggled(LINK(this, ScRetypePassInputDlg, CheckBoxHdl));
    Link<weld::Entry&, void> aLink2 = LINK(this, ScRetypePassInputDlg, PasswordModifyHdl);
    m_xPassword1Edit->connect_changed(aLink2);
    m_xPassword2Edit->connect_changed(aLink2);
 
    m_xBtnOk->set_sensitive(false);
    m_xBtnRetypePassword->set_active(true);
    m_xBtnMatchOldPass->set_active(true);
    m_xPassword1Edit->grab_focus();
}
 
void ScRetypePassInputDlg::CheckPasswordInput()
{
    OUString aPass1 = m_xPassword1Edit->get_text();
    OUString aPass2 = m_xPassword2Edit->get_text();
 
    if (aPass1.isEmpty() || aPass2.isEmpty())
    {
        // Empty password is not allowed.
        m_xBtnOk->set_sensitive(false);
        return;
    }
 
    if (aPass1 != aPass2)
    {
        // The two passwords differ.
        m_xBtnOk->set_sensitive(false);
        return;
    }
 
    if (!m_xBtnMatchOldPass->get_active())
    {
        m_xBtnOk->set_sensitive(true);
        return;
    }
 
    if (!m_pProtected)
    {
        // This should never happen!
        m_xBtnOk->set_sensitive(false);
        return;
    }
 
    bool bPassGood = m_pProtected->verifyPassword(aPass1);
    m_xBtnOk->set_sensitive(bPassGood);
}
 
IMPL_LINK_NOARG(ScRetypePassInputDlg, OKHdl, weld::Button&, void) { m_xDialog->response(RET_OK); }
 
IMPL_LINK_NOARG(ScRetypePassInputDlg, RadioBtnHdl, weld::Toggleable&, void)
{
    if (m_xBtnRetypePassword->get_active())
    {
        m_xPasswordGrid->set_sensitive(true);
        CheckPasswordInput();
    }
    else
    {
        m_xPasswordGrid->set_sensitive(false);
        m_xBtnOk->set_sensitive(true);
    }
}
 
IMPL_LINK_NOARG(ScRetypePassInputDlg, CheckBoxHdl, weld::Toggleable&, void)
{
    CheckPasswordInput();
}
 
IMPL_LINK_NOARG(ScRetypePassInputDlg, PasswordModifyHdl, weld::Entry&, void)
{
    CheckPasswordInput();
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V1023 A pointer without owner is added to the 'maSheets' container by the 'emplace_back' method. A memory leak will occur in case of an exception.