/* -*- 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/.
 */

#include "limitboxcontroller.hxx"

#include <com/sun/star/frame/XDispatchProvider.hpp>
#include <com/sun/star/frame/XFrame.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/util/XURLTransformer.hpp>

#include <comphelper/propertyvalue.hxx>
#include <vcl/InterimItemWindow.hxx>
#include <vcl/event.hxx>
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <cppuhelper/queryinterface.hxx>

#include <core_resource.hxx>
#include <strings.hrc>

using namespace ::com::sun::star;

namespace
{

/// Default values
sal_Int64 const aDefLimitAry[] =
{
    5,
    10,
    20,
    50
};

}

namespace dbaui
{

/**
 * Input box to add limit to an SQL query (maximum number of result's rows)
 * This box is reachable on the Query Design Toolbar
 */
class LimitBox final : public InterimItemWindow
{
public:
    LimitBox(vcl::Window* pParent, LimitBoxController* pCtrl)
        : InterimItemWindow(pParent, u"dbaccess/ui/limitbox.ui"_ustr, u"LimitBox"_ustr)
        , m_pControl( pCtrl )
        , m_xWidget(m_xBuilder->weld_combo_box(u"limit"_ustr))
    {
        InitControlBase(m_xWidget.get());

        LoadDefaultLimits();

        m_xWidget->connect_key_press(LINK(this, LimitBox, KeyInputHdl));
        m_xWidget->connect_entry_activate(LINK(this, LimitBox, ActivateHdl));
        m_xWidget->connect_changed(LINK(this, LimitBox, ChangeHdl));
        m_xWidget->connect_focus_out(LINK(this, LimitBox, FocusOutHdl));
        m_xWidget->set_entry_width_chars(6);
        SetSizePixel(m_xContainer->get_preferred_size());
    }

    virtual void dispose() override
    {
        m_xWidget.reset();
        InterimItemWindow::dispose();
    }

    virtual ~LimitBox() override
    {
        disposeOnce();
    }

    void set_sensitive(bool bSensitive)
    {
        m_xWidget->set_sensitive(bSensitive);
    }

    void set_value(int nLimit)
    {
        if (nLimit < 0)
            m_xWidget->set_active(0);
        else
            m_xWidget->set_entry_text(OUString::number(nLimit));
        m_xWidget->save_value();
    }

private:
    LimitBoxController* m_pControl;
    std::unique_ptr<weld::ComboBox> m_xWidget;

    DECL_LINK(KeyInputHdl, const KeyEvent&, bool);
    DECL_LINK(ActivateHdl, weld::ComboBox&, bool);
    DECL_LINK(ChangeHdl, weld::ComboBox&, void);
    DECL_LINK(FocusOutHdl, weld::Widget&, void);

    void Apply()
    {
        if (!m_xWidget->get_value_changed_from_saved())
            return;
        sal_Int64 nLimit;
        OUString sActiveText = m_xWidget->get_active_text();
        if (sActiveText == DBA_RES(STR_QUERY_LIMIT_ALL))
            nLimit = -1;
        else
        {
            nLimit = m_xWidget->get_active_text().toInt64();
            if (nLimit < 0)
                nLimit = -1;
        }
        set_value(nLimit);
        m_pControl->dispatchCommand({ comphelper::makePropertyValue(u"DBLimit.Value"_ustr, nLimit) });
    }

    ///Initialize entries
    void LoadDefaultLimits()
    {
        m_xWidget->freeze();
        m_xWidget->append_text(DBA_RES(STR_QUERY_LIMIT_ALL));
        for (auto nIndex : aDefLimitAry)
        {
            m_xWidget->append_text(OUString::number(nIndex));
        }
        m_xWidget->thaw();
    }
};

IMPL_LINK(LimitBox, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    bool bHandled = false;
    const sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
    switch (nCode)
    {
        case KEY_ESCAPE:
            m_xWidget->set_entry_text(m_xWidget->get_saved_value());
            bHandled = true;
            break;
        case KEY_RETURN:
        {
            bHandled = ActivateHdl(*m_xWidget);
            break;
        }
    }
    return bHandled || ChildKeyInput(rKEvt);
}

IMPL_LINK_NOARG(LimitBox, FocusOutHdl, weld::Widget&, void)
{
    if (!m_xWidget || m_xWidget->has_focus()) // comboboxes can be comprised of multiple widgets, ensure all have lost focus
        return;
    Apply();
}

IMPL_LINK(LimitBox, ChangeHdl, weld::ComboBox&, rComboBox, void)
{
    if (rComboBox.changed_by_direct_pick())
        ActivateHdl(rComboBox);
}

IMPL_LINK_NOARG(LimitBox, ActivateHdl, weld::ComboBox&, bool)
{
    GrabFocusToDocument();
    Apply();
    return true;
}

LimitBoxController::LimitBoxController(
    const uno::Reference< uno::XComponentContext >& rxContext ) :
    LimitBoxController_Base( rxContext,
                            uno::Reference< frame::XFrame >(),
                            ".uno:DBLimit" ),
    m_xLimitBox( nullptr )
{
}

LimitBoxController::~LimitBoxController()
{
}

/// XServiceInfo
OUString SAL_CALL LimitBoxController::getImplementationName()
{
    return u"org.libreoffice.comp.dbu.LimitBoxController"_ustr;
}

sal_Bool SAL_CALL LimitBoxController::supportsService(const OUString& _rServiceName)
    {
        const css::uno::Sequence< OUString > aSupported(getSupportedServiceNames());
        for (const OUString& s : aSupported)
            if (s == _rServiceName)
                return true;

        return false;
    }

css::uno::Sequence< OUString > SAL_CALL LimitBoxController::getSupportedServiceNames()
{
    return { u"com.sun.star.frame.ToolbarController"_ustr };
}

/// XComponent
void SAL_CALL LimitBoxController::dispose()
{
    svt::ToolboxController::dispose();

    SolarMutexGuard aSolarMutexGuard;
    m_xLimitBox.disposeAndClear();
}

/// XStatusListener
void SAL_CALL LimitBoxController::statusChanged(
    const frame::FeatureStateEvent& rEvent )
{
    if ( !m_xLimitBox )
        return;

    SolarMutexGuard aSolarMutexGuard;
    if ( rEvent.FeatureURL.Path == "DBLimit" )
    {
        if ( rEvent.IsEnabled )
        {
            m_xLimitBox->set_sensitive(true);
            sal_Int64 nLimit = 0;
            if (rEvent.State >>= nLimit)
                m_xLimitBox->set_value(nLimit);
        }
        else
            m_xLimitBox->set_sensitive(false);
    }
}

/// XToolbarController
void SAL_CALL LimitBoxController::execute( sal_Int16 /*KeyModifier*/ )
{
}

void SAL_CALL LimitBoxController::click()
{
}

void SAL_CALL LimitBoxController::doubleClick()
{
}

uno::Reference< awt::XWindow > SAL_CALL LimitBoxController::createPopupWindow()
{
    return uno::Reference< awt::XWindow >();
}

uno::Reference< awt::XWindow > SAL_CALL LimitBoxController::createItemWindow(
    const uno::Reference< awt::XWindow >& xParent )
{
    uno::Reference< awt::XWindow > xItemWindow;

    VclPtr<vcl::Window> pParent = VCLUnoHelper::GetWindow( xParent );
    if ( pParent )
    {
        SolarMutexGuard aSolarMutexGuard;
        m_xLimitBox = VclPtr<LimitBox>::Create(pParent, this);
        xItemWindow = VCLUnoHelper::GetInterface(m_xLimitBox);
    }

    return xItemWindow;
}

void LimitBoxController::dispatchCommand(
    const uno::Sequence< beans::PropertyValue >& rArgs )
{
    uno::Reference< frame::XDispatchProvider > xDispatchProvider( m_xFrame, uno::UNO_QUERY );
    if ( xDispatchProvider.is() )
    {
        util::URL                               aURL;
        uno::Reference< frame::XDispatch >      xDispatch;
        uno::Reference< util::XURLTransformer > xURLTransformer = getURLTransformer();

        aURL.Complete = ".uno:DBLimit";
        xURLTransformer->parseStrict( aURL );
        xDispatch = xDispatchProvider->queryDispatch( aURL, OUString(), 0 );
        if ( xDispatch.is() )
            xDispatch->dispatch( aURL, rArgs );
    }
}

} // dbaui namespace

extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
org_libreoffice_comp_dbu_LimitBoxController_get_implementation(
    css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
{
    return cppu::acquire(new ::dbaui::LimitBoxController(context));
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
