diff options
Diffstat (limited to 'sc/source/ui/dbgui')
32 files changed, 14446 insertions, 0 deletions
diff --git a/sc/source/ui/dbgui/PivotLayoutDialog.cxx b/sc/source/ui/dbgui/PivotLayoutDialog.cxx new file mode 100644 index 0000000000..de1f6b3b6f --- /dev/null +++ b/sc/source/ui/dbgui/PivotLayoutDialog.cxx @@ -0,0 +1,722 @@ +/* -*- 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: + */ + +#include <PivotLayoutTreeList.hxx> +#include <PivotLayoutDialog.hxx> +#include <reffact.hxx> + +#include <rangeutl.hxx> +#include <uiitems.hxx> +#include <dputil.hxx> +#include <dbdocfun.hxx> +#include <dpsave.hxx> +#include <dpshttab.hxx> +#include <scmod.hxx> + +#include <memory> +#include <utility> +#include <vector> + +#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp> +#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp> + +using namespace css::uno; +using namespace css::sheet; + +ScItemValue::ScItemValue(OUString aName, SCCOL nColumn, PivotFunc nFunctionMask) : + maName(std::move(aName)), + maFunctionData(nColumn, nFunctionMask), + mpOriginalItemValue(this) +{} + +ScItemValue::ScItemValue(const ScItemValue* pInputItemValue) : + maName(pInputItemValue->maName), + maFunctionData(pInputItemValue->maFunctionData), + mpOriginalItemValue(this) +{} + +ScItemValue::~ScItemValue() +{} + +namespace +{ + +ScRange lclGetRangeForNamedRange(OUString const & aName, const ScDocument& rDocument) +{ + ScRange aInvalidRange(ScAddress::INITIALIZE_INVALID); + ScRangeName* pRangeName = rDocument.GetRangeName(); + if (pRangeName == nullptr) + return aInvalidRange; + + const ScRangeData* pData = pRangeName->findByUpperName(aName.toAsciiUpperCase()); + if (pData == nullptr) + return aInvalidRange; + + ScRange aRange; + if (pData->IsReference(aRange)) + return aRange; + + return aInvalidRange; +} + +} + +ScPivotLayoutDialog::ScPivotLayoutDialog( + SfxBindings* pSfxBindings, SfxChildWindow* pChildWindow, weld::Window* pParent, + ScViewData* pViewData, const ScDPObject* pPivotTableObject, bool bNewPivotTable) + : ScAnyRefDlgController(pSfxBindings, pChildWindow, pParent, "modules/scalc/ui/pivottablelayoutdialog.ui", "PivotTableLayout") + , maPivotTableObject(*pPivotTableObject) + , mpPreviouslyFocusedListBox(nullptr) + , mpViewData(pViewData) + , mrDocument(pViewData->GetDocument()) + , mbNewPivotTable(bNewPivotTable) + , maAddressDetails(mrDocument.GetAddressConvention(), 0, 0) + , mbDialogLostFocus(false) + , mpActiveEdit(nullptr) + , mxListBoxField(new ScPivotLayoutTreeListLabel(m_xBuilder->weld_tree_view("listbox-fields"))) + , mxListBoxPage(new ScPivotLayoutTreeList(m_xBuilder->weld_tree_view("listbox-page"))) + , mxListBoxColumn(new ScPivotLayoutTreeList(m_xBuilder->weld_tree_view("listbox-column"))) + , mxListBoxRow(new ScPivotLayoutTreeList(m_xBuilder->weld_tree_view("listbox-row"))) + , mxListBoxData(new ScPivotLayoutTreeListData(m_xBuilder->weld_tree_view("listbox-data"))) + , mxCheckIgnoreEmptyRows(m_xBuilder->weld_check_button("check-ignore-empty-rows")) + , mxCheckTotalColumns(m_xBuilder->weld_check_button("check-total-columns")) + , mxCheckAddFilter(m_xBuilder->weld_check_button("check-add-filter")) + , mxCheckIdentifyCategories(m_xBuilder->weld_check_button("check-identify-categories")) + , mxCheckTotalRows(m_xBuilder->weld_check_button("check-total-rows")) + , mxCheckDrillToDetail(m_xBuilder->weld_check_button("check-drill-to-details")) + , mxCheckExpandCollapse(m_xBuilder->weld_check_button("check-show-expand-collapse")) + , mxSourceRadioNamedRange(m_xBuilder->weld_radio_button("source-radio-named-range")) + , mxSourceRadioSelection(m_xBuilder->weld_radio_button("source-radio-selection")) + , mxSourceListBox(m_xBuilder->weld_combo_box("source-list")) + , mxSourceEdit(new formula::RefEdit(m_xBuilder->weld_entry("source-edit"))) + , mxSourceButton(new formula::RefButton(m_xBuilder->weld_button("source-button"))) + , mxDestinationRadioNewSheet(m_xBuilder->weld_radio_button("destination-radio-new-sheet")) + , mxDestinationRadioNamedRange(m_xBuilder->weld_radio_button("destination-radio-named-range")) + , mxDestinationRadioSelection(m_xBuilder->weld_radio_button("destination-radio-selection")) + , mxDestinationListBox(m_xBuilder->weld_combo_box("destination-list")) + , mxDestinationEdit(new formula::RefEdit(m_xBuilder->weld_entry("destination-edit"))) + , mxDestinationButton(new formula::RefButton(m_xBuilder->weld_button("destination-button"))) + , mxBtnOK(m_xBuilder->weld_button("ok")) + , mxBtnCancel(m_xBuilder->weld_button("cancel")) + , mxSourceFrame(m_xBuilder->weld_frame("frame2")) + , mxSourceLabel(mxSourceFrame->weld_label_widget()) + , mxDestFrame(m_xBuilder->weld_frame("frame1")) + , mxDestLabel(mxDestFrame->weld_label_widget()) +{ + // Source UI + Link<weld::Toggleable&,void> aLink2 = LINK(this, ScPivotLayoutDialog, ToggleSource); + mxSourceRadioNamedRange->connect_toggled(aLink2); + mxSourceRadioSelection->connect_toggled(aLink2); + + mxSourceEdit->SetReferences(this, mxSourceLabel.get()); + mxSourceButton->SetReferences(this, mxSourceEdit.get()); + + Link<formula::RefEdit&,void> aEditLink = LINK(this, ScPivotLayoutDialog, GetEditFocusHandler); + mxDestinationEdit->SetGetFocusHdl(aEditLink); + mxSourceEdit->SetGetFocusHdl(aEditLink); + + aEditLink = LINK(this, ScPivotLayoutDialog, LoseEditFocusHandler); + mxDestinationEdit->SetLoseFocusHdl(aEditLink); + mxSourceEdit->SetLoseFocusHdl(aEditLink); + + mxSourceEdit->SetModifyHdl(LINK(this, ScPivotLayoutDialog, SourceEditModified)); + mxSourceListBox->connect_changed(LINK(this, ScPivotLayoutDialog, SourceListSelected)); + + // Destination UI + aLink2 = LINK(this, ScPivotLayoutDialog, ToggleDestination); + mxDestinationRadioNewSheet->connect_toggled(aLink2); + mxDestinationRadioNamedRange->connect_toggled(aLink2); + mxDestinationRadioSelection->connect_toggled(aLink2); + + mxDestinationEdit->SetReferences(this, mxDestLabel.get()); + mxDestinationButton->SetReferences(this, mxDestinationEdit.get()); + + Link<formula::RefButton&,void> aButtonLink = LINK(this, ScPivotLayoutDialog, GetButtonFocusHandler); + mxSourceButton->SetGetFocusHdl(aButtonLink); + mxDestinationButton->SetGetFocusHdl(aButtonLink); + + aButtonLink = LINK(this, ScPivotLayoutDialog, LoseButtonFocusHandler); + mxSourceButton->SetLoseFocusHdl(aButtonLink); + mxDestinationButton->SetLoseFocusHdl(aButtonLink); + + // Buttons + mxBtnCancel->connect_clicked(LINK(this, ScPivotLayoutDialog, CancelClicked)); + mxBtnOK->connect_clicked(LINK(this, ScPivotLayoutDialog, OKClicked)); + + // Initialize Data + maPivotTableObject.FillOldParam(maPivotParameters); + maPivotTableObject.FillLabelData(maPivotParameters); + + mxListBoxField->Setup (this); + mxListBoxPage->Setup (this, ScPivotLayoutTreeList::PAGE_LIST); + mxListBoxColumn->Setup(this, ScPivotLayoutTreeList::COLUMN_LIST); + mxListBoxRow->Setup (this, ScPivotLayoutTreeList::ROW_LIST); + mxListBoxData->Setup (this); + + FillValuesToListBoxes(); + + // Initialize Options + const ScDPSaveData* pSaveData = maPivotTableObject.GetSaveData(); + if (pSaveData == nullptr) + { + mxCheckAddFilter->set_active(false); + mxCheckDrillToDetail->set_active(false); + mxCheckExpandCollapse->set_active(false); + } + else + { + mxCheckAddFilter->set_active(pSaveData->GetFilterButton()); + mxCheckDrillToDetail->set_active(pSaveData->GetDrillDown()); + mxCheckExpandCollapse->set_active(pSaveData->GetExpandCollapse()); + } + + mxCheckIgnoreEmptyRows->set_active(maPivotParameters.bIgnoreEmptyRows); + mxCheckIdentifyCategories->set_active(maPivotParameters.bDetectCategories); + mxCheckTotalColumns->set_active(maPivotParameters.bMakeTotalCol); + mxCheckTotalRows->set_active(maPivotParameters.bMakeTotalRow); + + SetupSource(); + SetupDestination(); +} + +ScPivotLayoutDialog::~ScPivotLayoutDialog() +{ +} + +void ScPivotLayoutDialog::SetupSource() +{ + mxSourceListBox->clear(); + + ScRange aSourceRange; + OUString sSourceNamedRangeName; + + if (maPivotTableObject.GetSheetDesc()) + { + const ScSheetSourceDesc* pSheetSourceDesc = maPivotTableObject.GetSheetDesc(); + aSourceRange = pSheetSourceDesc->GetSourceRange(); + + if(!aSourceRange.IsValid()) + { + // Source is probably a DB Range + mxSourceRadioNamedRange->set_sensitive(false); + mxSourceRadioSelection->set_sensitive(false); + ToggleSource(); + return; + } + else + { + OUString aSourceRangeName = aSourceRange.Format(mrDocument, ScRefFlags::RANGE_ABS_3D, maAddressDetails); + mxSourceEdit->SetText(aSourceRangeName); + } + } + else + { + mxSourceRadioNamedRange->set_sensitive(false); + mxSourceRadioSelection->set_sensitive(false); + ToggleSource(); + return; + } + + // Setup Named Ranges + bool bIsNamedRange = false; + + ScAreaNameIterator aIterator(mrDocument); + OUString aEachName; + ScRange aEachRange; + + while (aIterator.Next(aEachName, aEachRange)) + { + if (!aIterator.WasDBName()) + { + mxSourceListBox->append_text(aEachName); + if (aEachRange == aSourceRange) + { + sSourceNamedRangeName = aEachName; + bIsNamedRange = true; + } + } + } + + bool bSourceBoxHasEntries = mxSourceListBox->get_count() > 0; + + if (bIsNamedRange) + { + mxSourceListBox->set_active_text(sSourceNamedRangeName); + mxSourceRadioNamedRange->set_active(true); + } + else + { + // If entries - select first entry + mxSourceListBox->set_active(bSourceBoxHasEntries ? 0 : -1); + mxSourceRadioSelection->set_active(true); + } + + // If no entries disable the radio button. + if (!bSourceBoxHasEntries) + mxSourceRadioNamedRange->set_sensitive(false); + + ToggleSource(); +} + +void ScPivotLayoutDialog::SetupDestination() +{ + mxDestinationListBox->clear(); + + // Fill up named ranges + ScAreaNameIterator aIterator(mrDocument); + OUString aName; + ScRange aRange; + + while (aIterator.Next(aName, aRange)) + { + if (!aIterator.WasDBName()) + { + mxDestinationListBox->append_text(aName); + } + } + + // If entries - select first entry, otherwise disable the radio button. + if (mxDestinationListBox->get_count() > 0) + mxDestinationListBox->set_active(0); + else + mxDestinationRadioNamedRange->set_sensitive(false); + + // + if (mbNewPivotTable) + { + mxDestinationRadioNewSheet->set_active(true); + } + else + { + if (maPivotParameters.nTab != MAXTAB + 1) + { + ScAddress aAddress(maPivotParameters.nCol, maPivotParameters.nRow, maPivotParameters.nTab); + OUString aAddressString = aAddress.Format(ScRefFlags::ADDR_ABS_3D, &mrDocument, maAddressDetails); + mxDestinationEdit->SetText(aAddressString); + mxDestinationRadioSelection->set_active(true); + } + } + + ToggleDestination(); +} + +void ScPivotLayoutDialog::FillValuesToListBoxes() +{ + mxListBoxField->FillLabelFields(maPivotParameters.maLabelArray); + mxListBoxData->FillDataField(maPivotParameters.maDataFields); + mxListBoxColumn->FillFields(maPivotParameters.maColFields); + mxListBoxRow->FillFields(maPivotParameters.maRowFields); + mxListBoxPage->FillFields(maPivotParameters.maPageFields); +} + +void ScPivotLayoutDialog::SetActive() +{ + if (mbDialogLostFocus) + { + mbDialogLostFocus = false; + if(mpActiveEdit != nullptr) + { + mpActiveEdit->GrabFocus(); + if (mpActiveEdit == mxSourceEdit.get()) + UpdateSourceRange(); + } + } + else + { + m_xDialog->grab_focus(); + } + + RefInputDone(); +} + +void ScPivotLayoutDialog::SetReference(const ScRange& rReferenceRange, ScDocument& rDocument) +{ + if (!mbDialogLostFocus) + return; + + if (mpActiveEdit == nullptr) + return; + + if (rReferenceRange.aStart != rReferenceRange.aEnd) + RefInputStart(mpActiveEdit); + + OUString aReferenceString = rReferenceRange.Format(rDocument, ScRefFlags::RANGE_ABS_3D, maAddressDetails); + + if (mpActiveEdit == mxSourceEdit.get()) + { + mxSourceEdit->SetRefString(aReferenceString); + } + else if (mpActiveEdit == mxDestinationEdit.get()) + { + mxDestinationEdit->SetRefString(aReferenceString); + } +} + +bool ScPivotLayoutDialog::IsRefInputMode() const +{ + return mbDialogLostFocus; +} + +void ScPivotLayoutDialog::ItemInserted(const ScItemValue* pItemValue, ScPivotLayoutTreeList::SvPivotTreeListType eType) +{ + if (pItemValue == nullptr) + return; + + switch (eType) + { + case ScPivotLayoutTreeList::ROW_LIST: + case ScPivotLayoutTreeList::COLUMN_LIST: + case ScPivotLayoutTreeList::PAGE_LIST: + { + mxListBoxRow->RemoveEntryForItem(pItemValue); + mxListBoxColumn->RemoveEntryForItem(pItemValue); + mxListBoxPage->RemoveEntryForItem(pItemValue); + } + break; + case ScPivotLayoutTreeList::LABEL_LIST: + { + mxListBoxRow->RemoveEntryForItem(pItemValue); + mxListBoxColumn->RemoveEntryForItem(pItemValue); + mxListBoxPage->RemoveEntryForItem(pItemValue); + mxListBoxData->RemoveEntryForItem(pItemValue); + } + break; + default: + break; + } +} + +void ScPivotLayoutDialog::UpdateSourceRange() +{ + if (!maPivotTableObject.GetSheetDesc()) + return; + + ScSheetSourceDesc aSourceSheet = *maPivotTableObject.GetSheetDesc(); + + if (mxSourceRadioNamedRange->get_active()) + { + OUString aEntryString = mxSourceListBox->get_active_text(); + ScRange aSourceRange = lclGetRangeForNamedRange(aEntryString, mrDocument); + if (!aSourceRange.IsValid() || aSourceSheet.GetSourceRange() == aSourceRange) + return; + aSourceSheet.SetRangeName(aEntryString); + } + else if (mxSourceRadioSelection->get_active()) + { + OUString aSourceString = mxSourceEdit->GetText(); + ScRange aSourceRange; + ScRefFlags nResult = aSourceRange.Parse(aSourceString, mrDocument, maAddressDetails); + + bool bIsValid = (nResult & ScRefFlags::VALID) == ScRefFlags::VALID; // aSourceString is valid + + mxSourceEdit->SetRefValid(true); + + if (bIsValid) + { + ScRefAddress aStart; + ScRefAddress aEnd; + + ConvertDoubleRef(mrDocument, aSourceString, 1, aStart, aEnd, maAddressDetails); + aSourceRange.aStart = aStart.GetAddress(); + aSourceRange.aEnd = aEnd.GetAddress(); + } + else + { + aSourceRange = lclGetRangeForNamedRange(aSourceString, mrDocument); + } + + if (!aSourceRange.IsValid()) + { + mxSourceEdit->SetRefValid(false); + return; + } + + if (aSourceSheet.GetSourceRange() == aSourceRange) + return; + + aSourceSheet.SetSourceRange(aSourceRange); + if (aSourceSheet.CheckSourceRange()) + { + mxSourceEdit->SetRefValid(false); + return; + } + } + else + { + return; + } + + maPivotTableObject.SetSheetDesc(aSourceSheet); + maPivotTableObject.FillOldParam(maPivotParameters); + maPivotTableObject.FillLabelData(maPivotParameters); + + FillValuesToListBoxes(); +} + +void ScPivotLayoutDialog::ApplyChanges() +{ + ScDPSaveData aSaveData; + ApplySaveData(aSaveData); + ApplyLabelData(aSaveData); + + ScDPObject *pOldDPObj = mrDocument.GetDPAtCursor( maPivotParameters.nCol, maPivotParameters.nRow, maPivotParameters.nTab); + ScRange aDestinationRange; + bool bToNewSheet = false; + + if (!GetDestination(aDestinationRange, bToNewSheet)) + return; + + SetDispatcherLock(false); + SwitchToDocument(); + + sal_uInt16 nWhichPivot = SC_MOD()->GetPool().GetWhich(SID_PIVOT_TABLE); + ScPivotItem aPivotItem(nWhichPivot, &aSaveData, &aDestinationRange, bToNewSheet); + mpViewData->GetViewShell()->SetDialogDPObject(std::make_unique<ScDPObject>(maPivotTableObject)); + + + SfxDispatcher* pDispatcher = GetBindings().GetDispatcher(); + SfxCallMode const nCallMode = SfxCallMode::SLOT | SfxCallMode::RECORD; + const SfxPoolItemHolder aResult(pDispatcher->ExecuteList(SID_PIVOT_TABLE, + nCallMode, { &aPivotItem })); + + if (nullptr != aResult.getItem()) + { + // existing pivot table might have moved to a new range or a new sheet + if ( pOldDPObj != nullptr ) + { + const ScRange& rOldRange = pOldDPObj->GetOutRange(); + + ScDPObject *pDPObj = nullptr; + // FIXME: if the new range overlaps with the old one, the table actually doesn't move + // and shouldn't therefore be deleted + if ( ( ( rOldRange != aDestinationRange ) && !rOldRange.Contains( aDestinationRange ) ) + || bToNewSheet ) + { + pDPObj = mrDocument.GetDPAtCursor( maPivotParameters.nCol, maPivotParameters.nRow, maPivotParameters.nTab); + } + if (pDPObj) + { + ScDBDocFunc aFunc( *(mpViewData->GetDocShell() )); + aFunc.RemovePivotTable( *pDPObj, true, false); + mpViewData->GetView()->CursorPosChanged(); + } + } + return; + } + + SetDispatcherLock(true); +} + +void ScPivotLayoutDialog::ApplySaveData(ScDPSaveData& rSaveData) +{ + rSaveData.SetIgnoreEmptyRows(mxCheckIgnoreEmptyRows->get_active()); + rSaveData.SetRepeatIfEmpty(mxCheckIdentifyCategories->get_active()); + rSaveData.SetColumnGrand(mxCheckTotalColumns->get_active()); + rSaveData.SetRowGrand(mxCheckTotalRows->get_active()); + rSaveData.SetFilterButton(mxCheckAddFilter->get_active()); + rSaveData.SetDrillDown(mxCheckDrillToDetail->get_active()); + rSaveData.SetExpandCollapse(mxCheckExpandCollapse->get_active()); + + Reference<XDimensionsSupplier> xSource = maPivotTableObject.GetSource(); + + ScPivotFieldVector aPageFieldVector; + mxListBoxPage->PushEntriesToPivotFieldVector(aPageFieldVector); + ScDPObject::ConvertOrientation(rSaveData, aPageFieldVector, DataPilotFieldOrientation_PAGE, + xSource, maPivotParameters.maLabelArray); + + ScPivotFieldVector aColFieldVector; + mxListBoxColumn->PushEntriesToPivotFieldVector(aColFieldVector); + ScDPObject::ConvertOrientation(rSaveData, aColFieldVector, DataPilotFieldOrientation_COLUMN, + xSource, maPivotParameters.maLabelArray); + + ScPivotFieldVector aRowFieldVector; + mxListBoxRow->PushEntriesToPivotFieldVector(aRowFieldVector); + ScDPObject::ConvertOrientation(rSaveData, aRowFieldVector, DataPilotFieldOrientation_ROW, + xSource, maPivotParameters.maLabelArray); + + ScPivotFieldVector aDataFieldVector; + mxListBoxData->PushEntriesToPivotFieldVector(aDataFieldVector); + ScDPObject::ConvertOrientation(rSaveData, aDataFieldVector, DataPilotFieldOrientation_DATA, + xSource, maPivotParameters.maLabelArray, + &aColFieldVector, &aRowFieldVector, &aPageFieldVector); +} + +void ScPivotLayoutDialog::ApplyLabelData(const ScDPSaveData& rSaveData) +{ + ScDPLabelDataVector& rLabelDataVector = GetLabelDataVector(); + + for (std::unique_ptr<ScDPLabelData> const & pLabelData : rLabelDataVector) + { + OUString aUnoName = ScDPUtil::createDuplicateDimensionName(pLabelData->maName, pLabelData->mnDupCount); + ScDPSaveDimension* pSaveDimensions = rSaveData.GetExistingDimensionByName(aUnoName); + + if (pSaveDimensions == nullptr) + continue; + + pSaveDimensions->SetUsedHierarchy(pLabelData->mnUsedHier); + pSaveDimensions->SetShowEmpty(pLabelData->mbShowAll); + pSaveDimensions->SetRepeatItemLabels(pLabelData->mbRepeatItemLabels); + pSaveDimensions->SetSortInfo(&pLabelData->maSortInfo); + pSaveDimensions->SetLayoutInfo(&pLabelData->maLayoutInfo); + pSaveDimensions->SetAutoShowInfo(&pLabelData->maShowInfo); + + bool bManualSort = (pLabelData->maSortInfo.Mode == DataPilotFieldSortMode::MANUAL); + + for (ScDPLabelData::Member const & rLabelMember : pLabelData->maMembers) + { + ScDPSaveMember* pMember = pSaveDimensions->GetMemberByName(rLabelMember.maName); + + if (bManualSort || !rLabelMember.mbVisible || !rLabelMember.mbShowDetails) + { + pMember->SetIsVisible(rLabelMember.mbVisible); + pMember->SetShowDetails(rLabelMember.mbShowDetails); + } + } + } +} + +bool ScPivotLayoutDialog::GetDestination(ScRange& aDestinationRange, bool& bToNewSheet) +{ + bToNewSheet = false; + + if (mxDestinationRadioNamedRange->get_active()) + { + OUString aName = mxDestinationListBox->get_active_text(); + aDestinationRange = lclGetRangeForNamedRange(aName, mrDocument); + if (!aDestinationRange.IsValid()) + return false; + } + else if (mxDestinationRadioSelection->get_active()) + { + ScAddress aAddress; + aAddress.Parse(mxDestinationEdit->GetText(), mrDocument, maAddressDetails); + aDestinationRange = ScRange(aAddress); + } + else + { + bToNewSheet = true; + aDestinationRange = ScRange(maPivotParameters.nCol, maPivotParameters.nRow, maPivotParameters.nTab); + } + return true; +} + +ScItemValue* ScPivotLayoutDialog::GetItem(SCCOL nColumn) +{ + return mxListBoxField->GetItem(nColumn); +} + +bool ScPivotLayoutDialog::IsDataElement(SCCOL nColumn) +{ + return mxListBoxField->IsDataElement(nColumn); +} + +ScDPLabelData& ScPivotLayoutDialog::GetLabelData(SCCOL nColumn) +{ + return *maPivotParameters.maLabelArray[nColumn]; +} + +void ScPivotLayoutDialog::PushDataFieldNames(std::vector<ScDPName>& rDataFieldNames) +{ + mxListBoxData->PushDataFieldNames(rDataFieldNames); +} + +void ScPivotLayoutDialog::Close() +{ + DoClose(ScPivotLayoutWrapper::GetChildWindowId()); + SfxDialogController::Close(); +} + +IMPL_LINK_NOARG( ScPivotLayoutDialog, OKClicked, weld::Button&, void ) +{ + /* tdf#137726 hide so it's not a candidate to be parent of any error + messages that may appear because this dialog is going to disappear on + response(RET_OK) and the error dialog is not run in its own event loop + but instead async */ + m_xDialog->hide(); + + ApplyChanges(); + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG( ScPivotLayoutDialog, CancelClicked, weld::Button&, void ) +{ + m_xDialog->response(RET_CANCEL); +} + +IMPL_LINK(ScPivotLayoutDialog, GetEditFocusHandler, formula::RefEdit&, rCtrl, void) +{ + mpActiveEdit = &rCtrl; + mpActiveEdit->SelectAll(); +} + +IMPL_LINK(ScPivotLayoutDialog, GetButtonFocusHandler, formula::RefButton&, rCtrl, void) +{ + mpActiveEdit = nullptr; + + if (&rCtrl == mxSourceButton.get()) + mpActiveEdit = mxSourceEdit.get(); + else if (&rCtrl == mxDestinationButton.get()) + mpActiveEdit = mxDestinationEdit.get(); + + if (mpActiveEdit) + mpActiveEdit->SelectAll(); +} + +IMPL_LINK_NOARG(ScPivotLayoutDialog, LoseEditFocusHandler, formula::RefEdit&, void) +{ + mbDialogLostFocus = !m_xDialog->has_toplevel_focus(); +} + +IMPL_LINK_NOARG(ScPivotLayoutDialog, LoseButtonFocusHandler, formula::RefButton&, void) +{ + mbDialogLostFocus = !m_xDialog->has_toplevel_focus(); +} + +IMPL_LINK_NOARG(ScPivotLayoutDialog, SourceListSelected, weld::ComboBox&, void) +{ + UpdateSourceRange(); +} + +IMPL_LINK_NOARG(ScPivotLayoutDialog, SourceEditModified, formula::RefEdit&, void) +{ + UpdateSourceRange(); +} + +IMPL_LINK_NOARG(ScPivotLayoutDialog, ToggleSource, weld::Toggleable&, void) +{ + ToggleSource(); +} + +void ScPivotLayoutDialog::ToggleSource() +{ + bool bNamedRange = mxSourceRadioNamedRange->get_active(); + bool bSelection = mxSourceRadioSelection->get_active(); + mxSourceListBox->set_sensitive(bNamedRange); + mxSourceButton->GetWidget()->set_sensitive(bSelection); + mxSourceEdit->GetWidget()->set_sensitive(bSelection); + UpdateSourceRange(); +} + +IMPL_LINK_NOARG(ScPivotLayoutDialog, ToggleDestination, weld::Toggleable&, void) +{ + ToggleDestination(); +} + +void ScPivotLayoutDialog::ToggleDestination() +{ + bool bNamedRange = mxDestinationRadioNamedRange->get_active(); + bool bSelection = mxDestinationRadioSelection->get_active(); + mxDestinationListBox->set_sensitive(bNamedRange); + mxDestinationButton->GetWidget()->set_sensitive(bSelection); + mxDestinationEdit->GetWidget()->set_sensitive(bSelection); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/PivotLayoutTreeList.cxx b/sc/source/ui/dbgui/PivotLayoutTreeList.cxx new file mode 100644 index 0000000000..c173d7ca7d --- /dev/null +++ b/sc/source/ui/dbgui/PivotLayoutTreeList.cxx @@ -0,0 +1,132 @@ +/* -*- 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: + */ + +#include <memory> +#include <PivotLayoutTreeList.hxx> +#include <PivotLayoutDialog.hxx> + +#include <vcl/event.hxx> +#include <pivot.hxx> + +ScPivotLayoutTreeList::ScPivotLayoutTreeList(std::unique_ptr<weld::TreeView> xControl) + : ScPivotLayoutTreeListBase(std::move(xControl)) +{ + mxControl->connect_key_press(LINK(this, ScPivotLayoutTreeList, KeyInputHdl)); + mxControl->connect_row_activated(LINK(this, ScPivotLayoutTreeList, DoubleClickHdl)); +} + +ScPivotLayoutTreeList::~ScPivotLayoutTreeList() +{ + if (mpSubtotalDlg) + { + mpSubtotalDlg->Response(RET_CANCEL); + mpSubtotalDlg.clear(); + } +} + +void ScPivotLayoutTreeList::Setup(ScPivotLayoutDialog* pParent, SvPivotTreeListType eType) +{ + mpParent = pParent; + meType = eType; +} + +IMPL_LINK_NOARG(ScPivotLayoutTreeList, DoubleClickHdl, weld::TreeView&, bool) +{ + int nEntry = mxControl->get_cursor_index(); + if (nEntry == -1) + return true; + + ScItemValue* pCurrentItemValue = weld::fromId<ScItemValue*>(mxControl->get_id(nEntry)); + ScPivotFuncData& rCurrentFunctionData = pCurrentItemValue->maFunctionData; + SCCOL nCurrentColumn = rCurrentFunctionData.mnCol; + + if (mpParent->IsDataElement(nCurrentColumn)) + return true; + + ScDPLabelData& rCurrentLabelData = mpParent->GetLabelData(nCurrentColumn); + + ScAbstractDialogFactory* pFactory = ScAbstractDialogFactory::Create(); + + maDataFieldNames.clear(); + mpParent->PushDataFieldNames(maDataFieldNames); + + mpSubtotalDlg = pFactory->CreateScDPSubtotalDlg(mxControl.get(), mpParent->maPivotTableObject, + rCurrentLabelData, rCurrentFunctionData, + maDataFieldNames); + + mpSubtotalDlg->StartExecuteAsync([this, pCurrentItemValue, nCurrentColumn](int nResult) { + if (nResult == RET_OK) + { + mpSubtotalDlg->FillLabelData(mpParent->GetLabelData(nCurrentColumn)); + pCurrentItemValue->maFunctionData.mnFuncMask = mpSubtotalDlg->GetFuncMask(); + } + + mpSubtotalDlg.disposeAndClear(); + }); + + return true; +} + +void ScPivotLayoutTreeList::FillFields(ScPivotFieldVector& rFieldVector) +{ + mxControl->clear(); + maItemValues.clear(); + + for (const ScPivotField& rField : rFieldVector) + { + OUString aLabel = mpParent->GetItem(rField.nCol)->maName; + ScItemValue* pItemValue = new ScItemValue(aLabel, rField.nCol, rField.nFuncMask); + maItemValues.push_back(std::unique_ptr<ScItemValue>(pItemValue)); + OUString sId(weld::toId(pItemValue)); + mxControl->append(sId, pItemValue->maName); + } +} + +void ScPivotLayoutTreeList::InsertEntryForSourceTarget(weld::TreeView& rSource, int nTarget) +{ + ScItemValue* pItemValue = weld::fromId<ScItemValue*>(rSource.get_selected_id()); + ScItemValue* pOriginalItemValue = pItemValue->mpOriginalItemValue; + + // Don't allow to add "Data" element to page fields + if (meType == PAGE_LIST && mpParent->IsDataElement(pItemValue->maFunctionData.mnCol)) + return; + + mpParent->ItemInserted(pOriginalItemValue, meType); + + InsertEntryForItem(pOriginalItemValue, nTarget); +} + +void ScPivotLayoutTreeList::InsertEntryForItem(const ScItemValue* pItemValue, int nPosition) +{ + ScItemValue* pListItemValue = new ScItemValue(pItemValue); + maItemValues.push_back(std::unique_ptr<ScItemValue>(pListItemValue)); + OUString sName = pListItemValue->maName; + OUString sId(weld::toId(pListItemValue)); + mxControl->insert(nullptr, nPosition, &sName, &sId, nullptr, nullptr, false, nullptr); +} + +IMPL_LINK(ScPivotLayoutTreeList, KeyInputHdl, const KeyEvent&, rKeyEvent, bool) +{ + vcl::KeyCode aCode = rKeyEvent.GetKeyCode(); + sal_uInt16 nCode = aCode.GetCode(); + + if (nCode == KEY_DELETE) + { + const int nEntry = mxControl->get_cursor_index(); + if (nEntry != -1) + mxControl->remove(nEntry); + return true; + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/PivotLayoutTreeListBase.cxx b/sc/source/ui/dbgui/PivotLayoutTreeListBase.cxx new file mode 100644 index 0000000000..45af29a4f1 --- /dev/null +++ b/sc/source/ui/dbgui/PivotLayoutTreeListBase.cxx @@ -0,0 +1,122 @@ +/* -*- 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: + */ + +#include <PivotLayoutTreeListBase.hxx> +#include <PivotLayoutDialog.hxx> + +ScPivotLayoutTreeListBase::ScPivotLayoutTreeListBase(std::unique_ptr<weld::TreeView> xControl, SvPivotTreeListType eType) + : mxControl(std::move(xControl)) + , maDropTargetHelper(*this) + , meType(eType) + , mpParent(nullptr) +{ + mxControl->connect_focus_in(LINK(this, ScPivotLayoutTreeListBase, GetFocusHdl)); + mxControl->connect_mnemonic_activate(LINK(this, ScPivotLayoutTreeListBase, MnemonicActivateHdl)); + mxControl->connect_focus_out(LINK(this, ScPivotLayoutTreeListBase, LoseFocusHdl)); +} + +ScPivotLayoutTreeListBase::~ScPivotLayoutTreeListBase() +{ +} + +void ScPivotLayoutTreeListBase::Setup(ScPivotLayoutDialog* pParent) +{ + mpParent = pParent; +} + +ScPivotLayoutTreeDropTarget::ScPivotLayoutTreeDropTarget(ScPivotLayoutTreeListBase& rTreeView) + : DropTargetHelper(rTreeView.get_widget().get_drop_target()) + , m_rTreeView(rTreeView) +{ +} + +sal_Int8 ScPivotLayoutTreeDropTarget::AcceptDrop(const AcceptDropEvent& rEvt) +{ + // to enable the autoscroll when we're close to the edges + weld::TreeView& rWidget = m_rTreeView.get_widget(); + rWidget.get_dest_row_at_pos(rEvt.maPosPixel, nullptr, true); + return DND_ACTION_MOVE; +} + +sal_Int8 ScPivotLayoutTreeDropTarget::ExecuteDrop( const ExecuteDropEvent& rEvt ) +{ + weld::TreeView& rWidget = m_rTreeView.get_widget(); + weld::TreeView* pSource = rWidget.get_drag_source(); + if (!pSource) + return DND_ACTION_NONE; + + std::unique_ptr<weld::TreeIter> xTarget(rWidget.make_iterator()); + int nTargetPos = -1; + if (rWidget.get_dest_row_at_pos(rEvt.maPosPixel, xTarget.get(), true)) + nTargetPos = rWidget.get_iter_index_in_parent(*xTarget); + m_rTreeView.InsertEntryForSourceTarget(*pSource, nTargetPos); + rWidget.unset_drag_dest_row(); + return DND_ACTION_NONE; +} + +void ScPivotLayoutTreeListBase::PushEntriesToPivotFieldVector(ScPivotFieldVector& rVector) +{ + std::unique_ptr<weld::TreeIter> xEachEntry(mxControl->make_iterator()); + if (!mxControl->get_iter_first(*xEachEntry)) + return; + do + { + ScItemValue* pItemValue = weld::fromId<ScItemValue*>(mxControl->get_id(*xEachEntry)); + ScPivotFuncData& rFunctionData = pItemValue->maFunctionData; + + ScPivotField aField; + aField.nCol = rFunctionData.mnCol; + aField.mnOriginalDim = rFunctionData.mnOriginalDim; + aField.nFuncMask = rFunctionData.mnFuncMask; + aField.mnDupCount = rFunctionData.mnDupCount; + aField.maFieldRef = rFunctionData.maFieldRef; + rVector.push_back(aField); + } while (mxControl->iter_next(*xEachEntry)); +} + +void ScPivotLayoutTreeListBase::InsertEntryForSourceTarget(weld::TreeView& /*pSource*/, int /*nTarget*/) +{ +} + +void ScPivotLayoutTreeListBase::RemoveEntryForItem(const ScItemValue* pItemValue) +{ + OUString sId(weld::toId(pItemValue)); + int nPos = mxControl->find_id(sId); + if (nPos == -1) + return; + mxControl->remove(nPos); +} + +IMPL_LINK_NOARG(ScPivotLayoutTreeListBase, GetFocusHdl, weld::Widget&, void) +{ + if (!mpParent) + return; + mpParent->mpPreviouslyFocusedListBox = this; +} + +IMPL_LINK_NOARG(ScPivotLayoutTreeListBase, MnemonicActivateHdl, weld::Widget&, bool) +{ + if (!mpParent || !mpParent->mpPreviouslyFocusedListBox) + return false; + weld::TreeView& rSource = mpParent->mpPreviouslyFocusedListBox->get_widget(); + int nEntry = rSource.get_cursor_index(); + if (nEntry != -1) + InsertEntryForSourceTarget(rSource, -1); + return true; +} + +IMPL_LINK_NOARG(ScPivotLayoutTreeListBase, LoseFocusHdl, weld::Widget&, void) +{ + if (!mpParent) + return; + mpParent->mpPreviouslyFocusedListBox = nullptr; +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/PivotLayoutTreeListData.cxx b/sc/source/ui/dbgui/PivotLayoutTreeListData.cxx new file mode 100644 index 0000000000..7455613274 --- /dev/null +++ b/sc/source/ui/dbgui/PivotLayoutTreeListData.cxx @@ -0,0 +1,287 @@ +/* -*- 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: + */ + +#include <memory> +#include <string_view> + +#include <PivotLayoutTreeListData.hxx> +#include <PivotLayoutDialog.hxx> + +#include <vcl/event.hxx> +#include <pivot.hxx> +#include <globstr.hrc> +#include <scresid.hxx> + +namespace +{ + +OUString lclGetFunctionMaskName(const PivotFunc nFunctionMask) +{ + TranslateId pStrId; + switch (nFunctionMask) + { + case PivotFunc::Sum: pStrId = STR_FUN_TEXT_SUM; break; + case PivotFunc::Count: pStrId = STR_FUN_TEXT_COUNT; break; + case PivotFunc::Average: pStrId = STR_FUN_TEXT_AVG; break; + case PivotFunc::Median: pStrId = STR_FUN_TEXT_MEDIAN; break; + case PivotFunc::Max: pStrId = STR_FUN_TEXT_MAX; break; + case PivotFunc::Min: pStrId = STR_FUN_TEXT_MIN; break; + case PivotFunc::Product: pStrId = STR_FUN_TEXT_PRODUCT; break; + case PivotFunc::CountNum: pStrId = STR_FUN_TEXT_COUNT; break; + case PivotFunc::StdDev: pStrId = STR_FUN_TEXT_STDDEV; break; + case PivotFunc::StdDevP: pStrId = STR_FUN_TEXT_STDDEV; break; + case PivotFunc::StdVar: pStrId = STR_FUN_TEXT_VAR; break; + case PivotFunc::StdVarP: pStrId = STR_FUN_TEXT_VAR; break; + default: + assert(false); + break; + } + if (pStrId) + return ScResId(pStrId); + else + return OUString(); +} + +OUString lclCreateDataItemName(const PivotFunc nFunctionMask, std::u16string_view rName, const sal_uInt8 nDuplicationCount) +{ + OUString aBuffer = lclGetFunctionMaskName(nFunctionMask) + " - " + rName; + if(nDuplicationCount > 0) + { + aBuffer += " " + OUString::number(nDuplicationCount); + } + return aBuffer; +} + +} // anonymous namespace + +ScPivotLayoutTreeListData::ScPivotLayoutTreeListData(std::unique_ptr<weld::TreeView> xControl) + : ScPivotLayoutTreeListBase(std::move(xControl)) +{ + mxControl->connect_key_press(LINK(this, ScPivotLayoutTreeListData, KeyInputHdl)); + mxControl->connect_row_activated(LINK(this, ScPivotLayoutTreeListData, DoubleClickHdl)); +} + +ScPivotLayoutTreeListData::~ScPivotLayoutTreeListData() +{ + if (mpFunctionDlg) + { + mpFunctionDlg->Response(RET_CANCEL); + mpFunctionDlg.clear(); + } +} + +IMPL_LINK_NOARG(ScPivotLayoutTreeListData, DoubleClickHdl, weld::TreeView&, bool) +{ + int nEntry = mxControl->get_cursor_index(); + if (nEntry == -1) + return true; + + ScItemValue* pCurrentItemValue = weld::fromId<ScItemValue*>(mxControl->get_id(nEntry)); + ScPivotFuncData& rCurrentFunctionData = pCurrentItemValue->maFunctionData; + + SCCOL nCurrentColumn = rCurrentFunctionData.mnCol; + ScDPLabelData& rCurrentLabelData = mpParent->GetLabelData(nCurrentColumn); + + ScAbstractDialogFactory* pFactory = ScAbstractDialogFactory::Create(); + + mpFunctionDlg = pFactory->CreateScDPFunctionDlg(mxControl.get(), mpParent->GetLabelDataVector(), rCurrentLabelData, rCurrentFunctionData); + + mpFunctionDlg->StartExecuteAsync([this, pCurrentItemValue, nEntry](int nResult) mutable { + if (nResult == RET_OK) + { + ScPivotFuncData& rFunctionData = pCurrentItemValue->maFunctionData; + rFunctionData.mnFuncMask = mpFunctionDlg->GetFuncMask(); + ScDPLabelData& rLabelData = mpParent->GetLabelData(rFunctionData.mnCol); + rLabelData.mnFuncMask = mpFunctionDlg->GetFuncMask(); + + rFunctionData.maFieldRef = mpFunctionDlg->GetFieldRef(); + + ScDPLabelData& rDFData = mpParent->GetLabelData(rFunctionData.mnCol); + + AdjustDuplicateCount(pCurrentItemValue); + + OUString sDataItemName = lclCreateDataItemName( + rFunctionData.mnFuncMask, + rDFData.maName, + rFunctionData.mnDupCount); + + mxControl->set_text(nEntry, sDataItemName); + } + + mpFunctionDlg->disposeOnce(); + }); + + return true; +} + +void ScPivotLayoutTreeListData::FillDataField(ScPivotFieldVector& rDataFields) +{ + mxControl->clear(); + maDataItemValues.clear(); + + for (const ScPivotField& rField : rDataFields) + { + if (rField.nCol == PIVOT_DATA_FIELD) + continue; + + SCCOL nColumn; + if (rField.mnOriginalDim >= 0) + nColumn = rField.mnOriginalDim; + else + nColumn = rField.nCol; + + ScItemValue* pOriginalItemValue = mpParent->GetItem(nColumn); + ScItemValue* pItemValue = new ScItemValue(pOriginalItemValue->maName, nColumn, rField.nFuncMask); + + pItemValue->mpOriginalItemValue = pOriginalItemValue; + pItemValue->maFunctionData.mnOriginalDim = rField.mnOriginalDim; + pItemValue->maFunctionData.maFieldRef = rField.maFieldRef; + + AdjustDuplicateCount(pItemValue); + OUString sDataItemName = lclCreateDataItemName(pItemValue->maFunctionData.mnFuncMask, + pItemValue->maName, + pItemValue->maFunctionData.mnDupCount); + + maDataItemValues.push_back(std::unique_ptr<ScItemValue>(pItemValue)); + OUString sId(weld::toId(pItemValue)); + mxControl->append(sId, sDataItemName); + } +} + +void ScPivotLayoutTreeListData::PushDataFieldNames(std::vector<ScDPName>& rDataFieldNames) +{ + std::unique_ptr<weld::TreeIter> xLoopEntry(mxControl->make_iterator()); + if (!mxControl->get_iter_first(*xLoopEntry)) + return; + + do + { + ScItemValue* pEachItemValue = weld::fromId<ScItemValue*>(mxControl->get_id(*xLoopEntry)); + SCCOL nColumn = pEachItemValue->maFunctionData.mnCol; + + ScDPLabelData& rLabelData = mpParent->GetLabelData(nColumn); + + if (rLabelData.maName.isEmpty()) + continue; + + OUString sLayoutName = rLabelData.maLayoutName; + if (sLayoutName.isEmpty()) + { + sLayoutName = lclCreateDataItemName( + pEachItemValue->maFunctionData.mnFuncMask, + pEachItemValue->maName, + pEachItemValue->maFunctionData.mnDupCount); + } + + rDataFieldNames.emplace_back(rLabelData.maName, sLayoutName, rLabelData.mnDupCount); + } while (mxControl->iter_next(*xLoopEntry)); +} + +void ScPivotLayoutTreeListData::InsertEntryForSourceTarget(weld::TreeView& rSource, int nTarget) +{ + if (rSource.count_selected_rows() <=0) + return; + + ScItemValue* pItemValue = weld::fromId<ScItemValue*>(rSource.get_selected_id()); + + if (mpParent->IsDataElement(pItemValue->maFunctionData.mnCol)) + return; + + if (&rSource == mxControl.get()) + { + OUString sText = mxControl->get_selected_text(); + OUString sId(weld::toId(pItemValue)); + mxControl->remove_id(sId); + mxControl->insert(nullptr, nTarget, &sText, &sId, nullptr, nullptr, false, nullptr); + } + else + { + InsertEntryForItem(pItemValue->mpOriginalItemValue, nTarget); + } +} + +void ScPivotLayoutTreeListData::InsertEntryForItem(ScItemValue* pItemValue, int nPosition) +{ + ScItemValue* pDataItemValue = new ScItemValue(pItemValue); + pDataItemValue->mpOriginalItemValue = pItemValue; + maDataItemValues.push_back(std::unique_ptr<ScItemValue>(pDataItemValue)); + + ScPivotFuncData& rFunctionData = pDataItemValue->maFunctionData; + + if (rFunctionData.mnFuncMask == PivotFunc::NONE || + rFunctionData.mnFuncMask == PivotFunc::Auto) + { + rFunctionData.mnFuncMask = PivotFunc::Sum; + } + + AdjustDuplicateCount(pDataItemValue); + + OUString sDataName = lclCreateDataItemName( + rFunctionData.mnFuncMask, + pDataItemValue->maName, + rFunctionData.mnDupCount); + + OUString sId(weld::toId(pDataItemValue)); + mxControl->insert(nullptr, nPosition, &sDataName, &sId, nullptr, nullptr, false, nullptr); +} + +void ScPivotLayoutTreeListData::AdjustDuplicateCount(ScItemValue* pInputItemValue) +{ + ScPivotFuncData& rInputFunctionData = pInputItemValue->maFunctionData; + + bool bFoundDuplicate = false; + + rInputFunctionData.mnDupCount = 0; + sal_uInt8 nMaxDuplicateCount = 0; + + std::unique_ptr<weld::TreeIter> xEachEntry(mxControl->make_iterator()); + if (!mxControl->get_iter_first(*xEachEntry)) + return; + do + { + ScItemValue* pItemValue = weld::fromId<ScItemValue*>(mxControl->get_id(*xEachEntry)); + if (pItemValue == pInputItemValue) + continue; + + ScPivotFuncData& rFunctionData = pItemValue->maFunctionData; + + if (rFunctionData.mnCol == rInputFunctionData.mnCol && + rFunctionData.mnFuncMask == rInputFunctionData.mnFuncMask) + { + bFoundDuplicate = true; + if(rFunctionData.mnDupCount > nMaxDuplicateCount) + nMaxDuplicateCount = rFunctionData.mnDupCount; + } + } while (mxControl->iter_next(*xEachEntry)); + + if(bFoundDuplicate) + { + rInputFunctionData.mnDupCount = nMaxDuplicateCount + 1; + } +} + +IMPL_LINK(ScPivotLayoutTreeListData, KeyInputHdl, const KeyEvent&, rKeyEvent, bool) +{ + vcl::KeyCode aCode = rKeyEvent.GetKeyCode(); + sal_uInt16 nCode = aCode.GetCode(); + + if (nCode == KEY_DELETE) + { + int nEntry = mxControl->get_cursor_index(); + if (nEntry != -1) + mxControl->remove(nEntry); + return true; + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/PivotLayoutTreeListLabel.cxx b/sc/source/ui/dbgui/PivotLayoutTreeListLabel.cxx new file mode 100644 index 0000000000..e4a2276dac --- /dev/null +++ b/sc/source/ui/dbgui/PivotLayoutTreeListLabel.cxx @@ -0,0 +1,85 @@ +/* -*- 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: + */ + +#include <memory> +#include <PivotLayoutTreeListLabel.hxx> +#include <PivotLayoutDialog.hxx> + +#include <vcl/event.hxx> +#include <pivot.hxx> + +ScPivotLayoutTreeListLabel::ScPivotLayoutTreeListLabel(std::unique_ptr<weld::TreeView> xControl) + : ScPivotLayoutTreeListBase(std::move(xControl), LABEL_LIST) + , maDataItem(0) +{ + mxControl->connect_key_press(LINK(this, ScPivotLayoutTreeListLabel, KeyInputHdl)); +} + +ScPivotLayoutTreeListLabel::~ScPivotLayoutTreeListLabel() +{} + +void ScPivotLayoutTreeListLabel::FillLabelFields(ScDPLabelDataVector& rLabelVector) +{ + mxControl->clear(); + maItemValues.clear(); + + for (std::unique_ptr<ScDPLabelData> const & pLabelData : rLabelVector) + { + ScItemValue* pValue = new ScItemValue(pLabelData->maName, pLabelData->mnCol, pLabelData->mnFuncMask); + maItemValues.push_back(std::unique_ptr<ScItemValue>(pValue)); + if (pLabelData->mbDataLayout) + { + maDataItem = maItemValues.size() - 1; + } + + if (pLabelData->mnOriginalDim < 0 && !pLabelData->mbDataLayout) + { + mxControl->append(weld::toId(pValue), pLabelData->maName); + } + } +} + +void ScPivotLayoutTreeListLabel::InsertEntryForSourceTarget(weld::TreeView& rSource, int /*nTarget*/) +{ + if (&rSource == mxControl.get()) + return; + rSource.remove(rSource.get_selected_index()); +} + +bool ScPivotLayoutTreeListLabel::IsDataElement(SCCOL nColumn) +{ + return (nColumn == PIVOT_DATA_FIELD || nColumn == maDataItem); +} + +ScItemValue* ScPivotLayoutTreeListLabel::GetItem(SCCOL nColumn) +{ + if (nColumn == PIVOT_DATA_FIELD) + return maItemValues[maDataItem].get(); + return maItemValues[nColumn].get(); +} + +IMPL_LINK(ScPivotLayoutTreeListLabel, KeyInputHdl, const KeyEvent&, rKeyEvent, bool) +{ + vcl::KeyCode aCode = rKeyEvent.GetKeyCode(); + sal_uInt16 nCode = aCode.GetCode(); + + if (nCode == KEY_DELETE) + { + int nEntry = mxControl->get_cursor_index(); + if (nEntry != -1) + mxControl->remove(nEntry); + return true; + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/asciiopt.cxx b/sc/source/ui/dbgui/asciiopt.cxx new file mode 100644 index 0000000000..c9a4d881ba --- /dev/null +++ b/sc/source/ui/dbgui/asciiopt.cxx @@ -0,0 +1,313 @@ +/* -*- 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 <global.hxx> +#include <asciiopt.hxx> +#include <comphelper/string.hxx> +#include <osl/thread.h> +#include <o3tl/string_view.hxx> + +constexpr std::u16string_view pStrFix = u"FIX"; +constexpr std::u16string_view pStrMrg = u"MRG"; + +ScAsciiOptions::ScAsciiOptions() : + bFixedLen ( false ), + aFieldSeps ( OUString(';') ), + bMergeFieldSeps ( false ), + bRemoveSpace ( false ), + bQuotedFieldAsText(false), + bDetectSpecialNumber(false), + bDetectScientificNumber(true), + bEvaluateFormulas(true), + bSkipEmptyCells(false), + bSaveAsShown(true), + bSaveFormulas(false), + bIncludeBOM(false), + cTextSep ( cDefaultTextSep ), + eCharSet ( osl_getThreadTextEncoding() ), + eLang ( LANGUAGE_SYSTEM ), + bCharSetSystem ( false ), + nStartRow ( 1 ) +{ +} + +void ScAsciiOptions::SetColumnInfo( const ScCsvExpDataVec& rDataVec ) +{ + sal_uInt16 nInfoCount = static_cast< sal_uInt16 >( rDataVec.size() ); + mvColStart.resize(nInfoCount); + mvColFormat.resize(nInfoCount); + for( sal_uInt16 nIx = 0; nIx < nInfoCount; ++nIx ) + { + mvColStart[ nIx ] = rDataVec[ nIx ].mnIndex; + mvColFormat[ nIx ] = rDataVec[ nIx ].mnType; + } +} + +static OUString lcl_decodeSepString( std::u16string_view rSepNums, bool & o_bMergeFieldSeps ) +{ + if ( rSepNums.empty() ) + return OUString(); + + OUStringBuffer aFieldSeps; + sal_Int32 nPos = 0; + do + { + const std::u16string_view aCode = o3tl::getToken(rSepNums, 0, '/', nPos ); + if ( aCode == pStrMrg ) + o_bMergeFieldSeps = true; + else + { + sal_Int32 nVal = o3tl::toInt32(aCode); + if ( nVal ) + aFieldSeps.append(sal_Unicode(nVal)); + } + } + while ( nPos >= 0 ); + + return aFieldSeps.makeStringAndClear(); +} + +// The options string must not contain semicolons (because of the pick list), +// use comma as separator. + +void ScAsciiOptions::ReadFromString( std::u16string_view rString ) +{ + sal_Int32 nPos = rString.empty() ? -1 : 0; + + // Token 0: Field separator. + if ( nPos >= 0 ) + { + bFixedLen = bMergeFieldSeps = false; + + const std::u16string_view aToken = o3tl::getToken(rString, 0, ',', nPos); + if ( aToken == pStrFix ) + bFixedLen = true; + aFieldSeps = lcl_decodeSepString( aToken, bMergeFieldSeps); + } + + // Token 1: Text separator. + if ( nPos >= 0 ) + { + const sal_Int32 nVal = o3tl::toInt32(o3tl::getToken(rString, 0, ',', nPos)); + cTextSep = static_cast<sal_Unicode>(nVal); + } + + // Token 2: Text encoding. + if ( nPos >= 0 ) + { + eCharSet = ScGlobal::GetCharsetValue( o3tl::getToken(rString, 0, ',', nPos) ); + } + + // Token 3: Number of start row. + if ( nPos >= 0 ) + { + nStartRow = o3tl::toInt32(o3tl::getToken(rString, 0, ',', nPos)); + } + + // Token 4: Column info. + if ( nPos >= 0 ) + { + const std::u16string_view aToken = o3tl::getToken(rString, 0, ',', nPos); + const sal_Int32 nInfoCount = comphelper::string::getTokenCount(aToken, '/')/2; + mvColStart.resize(nInfoCount); + mvColFormat.resize(nInfoCount); + sal_Int32 nP = 0; + for (sal_Int32 nInfo=0; nInfo<nInfoCount; ++nInfo) + { + mvColStart[nInfo] = o3tl::toInt32(o3tl::getToken(aToken, 0, '/', nP)); + mvColFormat[nInfo] = static_cast<sal_uInt8>(o3tl::toInt32(o3tl::getToken(aToken, 0, '/', nP))); + } + } + + // Token 5: Language. + if (nPos >= 0) + { + eLang = static_cast<LanguageType>(o3tl::toInt32(o3tl::getToken(rString, 0, ',', nPos))); + } + + // Token 6: Import quoted field as text. + if (nPos >= 0) + { + bQuotedFieldAsText = o3tl::getToken(rString, 0, ',', nPos) == u"true"; + } + + // Token 7: Detect special numbers. + if (nPos >= 0) + { + bDetectSpecialNumber = o3tl::getToken(rString, 0, ',', nPos) == u"true"; + } + else + bDetectSpecialNumber = true; // default of versions that didn't add the parameter + + // Token 8: used for "Save as shown" in export options + if ( nPos >= 0 ) + { + bSaveAsShown = o3tl::getToken(rString, 0, ',', nPos) == u"true"; + } + else + bSaveAsShown = true; // default value + + // Token 9: used for "Save cell formulas" in export options + if ( nPos >= 0 ) + { + bSaveFormulas = o3tl::getToken(rString, 0, ',', nPos) == u"true"; + } + else + bSaveFormulas = false; + + // Token 10: Boolean for Trim spaces. + if (nPos >= 0) + { + bRemoveSpace = o3tl::getToken(rString, 0, ',', nPos) == u"true"; + } + else + bRemoveSpace = false; + + // Token 11: sheet to export for --convert-to csv + // Does not need to be evaluated here but may be present. + if (nPos >= 0) + { + o3tl::getToken(rString, 0, ',', nPos); + } + + // Token 12: evaluate formulas. + if (nPos >= 0) + { + // If present, defaults to "false". + bEvaluateFormulas = o3tl::getToken(rString, 0, ',', nPos) == u"true"; + } + else + bEvaluateFormulas = true; // default of versions that didn't add the parameter + + // Token 13: include BOM. + bIncludeBOM = nPos >= 0 && o3tl::getToken(rString, 0, ',', nPos) == u"true"; + + // Token 14: Detect scientific numbers. + if (nPos >= 0) + { + bDetectScientificNumber = o3tl::getToken(rString, 0, ',', nPos) == u"true"; + } + else + bDetectScientificNumber = true; // default of versions that didn't add the parameter + +} + +OUString ScAsciiOptions::WriteToString() const +{ + OUStringBuffer aOutStr; + + // Token 0: Field separator. + if ( bFixedLen ) + aOutStr.append(pStrFix); + else if ( aFieldSeps.isEmpty() ) + aOutStr.append("0"); + else + { + sal_Int32 nLen = aFieldSeps.getLength(); + for (sal_Int32 i=0; i<nLen; i++) + { + if (i) + aOutStr.append("/"); + aOutStr.append(OUString::number(aFieldSeps[i])); + } + if ( bMergeFieldSeps ) + { + aOutStr.append(OUString::Concat("/") + pStrMrg); + } + } + + // Token 1: Text Quote character. + aOutStr.append("," + OUString::number(cTextSep) + ","); + + //Token 2: Text encoding. + if ( bCharSetSystem ) // force "SYSTEM" + aOutStr.append(ScGlobal::GetCharsetString( RTL_TEXTENCODING_DONTKNOW )); + else + aOutStr.append(ScGlobal::GetCharsetString( eCharSet )); + + //Token 3: Number of start row. + aOutStr.append("," + OUString::number(nStartRow) + ","); + + //Token 4: Column info. + for (size_t nInfo=0; nInfo<mvColStart.size(); nInfo++) + { + if (nInfo) + aOutStr.append("/"); + aOutStr.append(OUString::number(mvColStart[nInfo]) + + "/" + + OUString::number(mvColFormat[nInfo])); + } + + // #i112025# the options string is used in macros and linked sheets, + // so new options must be added at the end, to remain compatible + // Always keep in sync with ScImportOptions. + + aOutStr.append("," + + // Token 5: Language + OUString::number(static_cast<sal_uInt16>(eLang)) + "," + + // Token 6: Import quoted field as text. + OUString::boolean( bQuotedFieldAsText ) + "," + + // Token 7: Detect special numbers. + OUString::boolean( bDetectSpecialNumber ) + "," + + // Token 8: used for "Save as shown" in export options + OUString::boolean( bSaveAsShown ) +"," + + // Token 9: used for "Save cell formulas" in export options + OUString::boolean( bSaveFormulas ) + "," + + // Token 10: Trim Space + OUString::boolean( bRemoveSpace ) + + // Token 11: sheet to export, always 0 for current sheet + ",0," + + // Token 12: evaluate formulas in import + OUString::boolean( bEvaluateFormulas ) + "," + + // Token 13: include BOM + OUString::boolean(bIncludeBOM) + "," + + // Token 14: Detect scientific numbers. + OUString::boolean( bDetectScientificNumber ) + ); + return aOutStr.makeStringAndClear(); +} + +// static +sal_Unicode ScAsciiOptions::GetWeightedFieldSep( const OUString & rFieldSeps, bool bDecodeNumbers ) +{ + bool bMergeFieldSeps = false; + OUString aFieldSeps( bDecodeNumbers ? lcl_decodeSepString( rFieldSeps, bMergeFieldSeps) : rFieldSeps); + if (aFieldSeps.isEmpty()) + { + return 0; + } + else if (aFieldSeps.getLength() == 1) + return aFieldSeps[0]; + else + { + // There can be only one separator for output. See also fdo#53449 + if (aFieldSeps.indexOf(',') != -1) + return ','; + else if (aFieldSeps.indexOf('\t') != -1) + return '\t'; + else if (aFieldSeps.indexOf(';') != -1) + return ';'; + else if (aFieldSeps.indexOf(' ') != -1) + return ' '; + else + return aFieldSeps[0]; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/consdlg.cxx b/sc/source/ui/dbgui/consdlg.cxx new file mode 100644 index 0000000000..d0921f3eb9 --- /dev/null +++ b/sc/source/ui/dbgui/consdlg.cxx @@ -0,0 +1,534 @@ +/* -*- 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 <sfx2/dispatch.hxx> + +#include <tabvwsh.hxx> +#include <uiitems.hxx> +#include <dbdata.hxx> +#include <rangenam.hxx> +#include <rangeutl.hxx> +#include <reffact.hxx> +#include <document.hxx> +#include <scresid.hxx> + +#include <globstr.hrc> +#include <strings.hrc> + +#include <consdlg.hxx> +#include <o3tl/safeint.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +namespace +{ + void INFOBOX(weld::Window* pWindow, TranslateId id) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pWindow, + VclMessageType::Info, VclButtonsType::Ok, + ScResId(id))); + xInfoBox->run(); + } +} + +class ScAreaData +{ +public: + ScAreaData() + { + } + + void Set( const OUString& rName, const OUString& rArea ) + { + aStrName = rName; + aStrArea = rArea; + } + + OUString aStrName; + OUString aStrArea; +}; + +ScConsolidateDlg::ScConsolidateDlg(SfxBindings* pB, SfxChildWindow* pCW, weld::Window* pParent, + const SfxItemSet& rArgSet) + + : ScAnyRefDlgController(pB, pCW, pParent, "modules/scalc/ui/consolidatedialog.ui", "ConsolidateDialog") + , aStrUndefined ( ScResId( SCSTR_UNDEFINED ) ) + , theConsData ( static_cast<const ScConsolidateItem&>( + rArgSet.Get( rArgSet.GetPool()-> + GetWhich( SID_CONSOLIDATE ) ) + ).GetData() ) + , rViewData ( static_cast<ScTabViewShell*>(SfxViewShell::Current())-> + GetViewData() ) + , rDoc ( static_cast<ScTabViewShell*>(SfxViewShell::Current())-> + GetViewData().GetDocument() ) + , nAreaDataCount ( 0 ) + , nWhichCons ( rArgSet.GetPool()->GetWhich( SID_CONSOLIDATE ) ) + , bDlgLostFocus ( false ) + , m_xLbFunc(m_xBuilder->weld_combo_box("func")) + , m_xLbConsAreas(m_xBuilder->weld_tree_view("consareas")) + , m_xLbDataArea(m_xBuilder->weld_combo_box("lbdataarea")) + , m_xEdDataArea(new formula::RefEdit(m_xBuilder->weld_entry("eddataarea"))) + , m_xRbDataArea(new formula::RefButton(m_xBuilder->weld_button("rbdataarea"))) + , m_xLbDestArea(m_xBuilder->weld_combo_box("lbdestarea")) + , m_xEdDestArea(new formula::RefEdit(m_xBuilder->weld_entry("eddestarea"))) + , m_xRbDestArea(new formula::RefButton(m_xBuilder->weld_button("rbdestarea"))) + , m_xBtnByRow(m_xBuilder->weld_check_button("byrow")) + , m_xBtnByCol(m_xBuilder->weld_check_button("bycol")) + , m_xBtnRefs(m_xBuilder->weld_check_button("refs")) + , m_xBtnOk(m_xBuilder->weld_button("ok")) + , m_xBtnCancel(m_xBuilder->weld_button("cancel")) + , m_xBtnAdd(m_xBuilder->weld_button("add")) + , m_xBtnRemove(m_xBuilder->weld_button("delete")) + , m_xDataFT(m_xBuilder->weld_label("ftdataarea")) + , m_xDestFT(m_xBuilder->weld_label("ftdestarea")) +{ + m_pRefInputEdit = m_xEdDataArea.get(); + Init(); +} + +ScConsolidateDlg::~ScConsolidateDlg() +{ +} + +void ScConsolidateDlg::Init() +{ + OUString aStr; + sal_uInt16 i=0; + + m_xRbDataArea->SetReferences(this, m_xEdDataArea.get()); + m_xEdDataArea->SetReferences(this, m_xDataFT.get()); + m_xRbDestArea->SetReferences(this, m_xEdDestArea.get()); + m_xEdDestArea->SetReferences(this, m_xDestFT.get()); + + m_xEdDataArea->SetGetFocusHdl( LINK( this, ScConsolidateDlg, GetEditFocusHdl ) ); + m_xEdDestArea->SetGetFocusHdl( LINK( this, ScConsolidateDlg, GetEditFocusHdl ) ); + m_xLbDataArea->connect_focus_in( LINK( this, ScConsolidateDlg, GetFocusHdl ) ); + m_xLbDestArea->connect_focus_in( LINK( this, ScConsolidateDlg, GetFocusHdl ) ); + m_xEdDataArea->SetModifyHdl( LINK( this, ScConsolidateDlg, ModifyHdl ) ); + m_xEdDestArea->SetModifyHdl( LINK( this, ScConsolidateDlg, ModifyHdl ) ); + m_xLbConsAreas->connect_changed( LINK( this, ScConsolidateDlg, SelectTVHdl ) ); + m_xLbDataArea->connect_changed( LINK( this, ScConsolidateDlg, SelectCBHdl ) ); + m_xLbDestArea->connect_changed( LINK( this, ScConsolidateDlg, SelectCBHdl ) ); + m_xBtnOk->connect_clicked( LINK( this, ScConsolidateDlg, OkHdl ) ); + m_xBtnCancel->connect_clicked( LINK( this, ScConsolidateDlg, ClickHdl ) ); + m_xBtnAdd->connect_clicked( LINK( this, ScConsolidateDlg, ClickHdl ) ); + m_xBtnRemove->connect_clicked( LINK( this, ScConsolidateDlg, ClickHdl ) ); + + m_xBtnAdd->set_sensitive(false); + m_xBtnRemove->set_sensitive(false); + + m_xBtnByRow->set_active( theConsData.bByRow ); + m_xBtnByCol->set_active( theConsData.bByCol ); + m_xBtnRefs->set_active( theConsData.bReferenceData ); + + m_xLbFunc->set_active( FuncToLbPos( theConsData.eFunction ) ); + + m_xLbConsAreas->set_selection_mode(SelectionMode::Multiple); + m_xLbConsAreas->set_size_request(m_xLbConsAreas->get_approximate_digit_width() * 16, + m_xLbConsAreas->get_height_rows(5)); + + // read consolidation areas + m_xLbConsAreas->clear(); + const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention(); + for ( i=0; i<theConsData.nDataAreaCount; i++ ) + { + const ScArea& rArea = theConsData.pDataAreas[i]; + if ( rArea.nTab < rDoc.GetTableCount() ) + { + aStr = ScRange( rArea.nColStart, rArea.nRowStart, rArea.nTab, + rArea.nColEnd, rArea.nRowEnd, rArea.nTab ).Format( rDoc, + ScRefFlags::RANGE_ABS_3D, eConv ); + m_xLbConsAreas->append_text(aStr); + } + } + + if ( theConsData.nTab < rDoc.GetTableCount() ) + { + aStr = ScAddress( theConsData.nCol, theConsData.nRow, theConsData.nTab + ).Format( ScRefFlags::ADDR_ABS_3D, &rDoc, eConv ); + m_xEdDestArea->SetText( aStr ); + } + else + m_xEdDestArea->SetText(OUString()); + + // Use the ScAreaData helper class to save those range names from the + // RangeNames and database ranges that appear in the ListBoxes. + + ScRangeName* pRangeNames = rDoc.GetRangeName(); + ScDBCollection* pDbNames = rDoc.GetDBCollection(); + size_t nRangeCount = pRangeNames ? pRangeNames->size() : 0; + size_t nDbCount = pDbNames ? pDbNames->getNamedDBs().size() : 0; + + nAreaDataCount = nRangeCount+nDbCount; + pAreaData = nullptr; + + if ( nAreaDataCount > 0 ) + { + pAreaData.reset( new ScAreaData[nAreaDataCount] ); + + OUString aStrName; + sal_uInt16 nAt = 0; + ScRange aRange; + ScAreaNameIterator aIter( rDoc ); + while ( aIter.Next( aStrName, aRange ) ) + { + OUString aStrArea(aRange.Format(rDoc, ScRefFlags::ADDR_ABS_3D, eConv)); + pAreaData[nAt++].Set( aStrName, aStrArea ); + } + } + + FillAreaLists(); + ModifyHdl( *m_xEdDestArea ); + m_xLbDataArea->set_active( 0 ); + m_xEdDataArea->SetText(OUString()); + m_xEdDataArea->GrabFocus(); + + //aFlSep.SetStyle( aFlSep.GetStyle() | WB_VERT ); + + //@BugID 54702 enable/disable only in base class + //SFX_APPWINDOW->set_sensitive(true); +} + +void ScConsolidateDlg::FillAreaLists() +{ + m_xLbDataArea->clear(); + m_xLbDestArea->clear(); + m_xLbDataArea->append_text( aStrUndefined ); + m_xLbDestArea->append_text( aStrUndefined ); + + if ( pAreaData && (nAreaDataCount > 0) ) + { + for ( size_t i=0; + (i<nAreaDataCount) && (!pAreaData[i].aStrName.isEmpty()); + i++ ) + { + m_xLbDataArea->append_text(pAreaData[i].aStrName); + m_xLbDestArea->append_text(pAreaData[i].aStrName); + } + } +} + +// Handover of a range within a table that has been selected by the mouse. +// This range is then shown in the reference window as new selection. + +void ScConsolidateDlg::SetReference( const ScRange& rRef, ScDocument& rDocP ) +{ + if ( !m_pRefInputEdit ) + return; + + if ( rRef.aStart != rRef.aEnd ) + RefInputStart( m_pRefInputEdit ); + + OUString aStr; + ScRefFlags nFmt = ScRefFlags::RANGE_ABS_3D; //!!! nCurTab is still missing + const formula::FormulaGrammar::AddressConvention eConv = rDocP.GetAddressConvention(); + + if ( rRef.aStart.Tab() != rRef.aEnd.Tab() ) + nFmt |= ScRefFlags::TAB2_3D; + + if ( m_pRefInputEdit == m_xEdDataArea.get()) + aStr = rRef.Format(rDocP, nFmt, eConv); + else if ( m_pRefInputEdit == m_xEdDestArea.get() ) + aStr = rRef.aStart.Format(nFmt, &rDocP, eConv); + + m_pRefInputEdit->SetRefString( aStr ); + ModifyHdl( *m_pRefInputEdit ); +} + +void ScConsolidateDlg::Close() +{ + DoClose( ScConsolidateDlgWrapper::GetChildWindowId() ); +} + +void ScConsolidateDlg::SetActive() +{ + if ( bDlgLostFocus ) + { + bDlgLostFocus = false; + + if ( m_pRefInputEdit ) + { + m_pRefInputEdit->GrabFocus(); + ModifyHdl( *m_pRefInputEdit ); + } + } + else + m_xDialog->grab_focus(); + + RefInputDone(); +} + +void ScConsolidateDlg::Deactivate() +{ + bDlgLostFocus = true; +} + +bool ScConsolidateDlg::VerifyEdit( formula::RefEdit* pEd ) +{ + if (pEd != m_xEdDataArea.get() && pEd != m_xEdDestArea.get()) + return false; + + SCTAB nTab = rViewData.GetTabNo(); + bool bEditOk = false; + OUString theCompleteStr; + const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention(); + + if ( pEd == m_xEdDataArea.get() ) + { + bEditOk = ScRangeUtil::IsAbsArea( pEd->GetText(), rDoc, + nTab, &theCompleteStr, nullptr, nullptr, eConv ); + } + else if ( pEd == m_xEdDestArea.get() ) + { + OUString aPosStr; + + ScRangeUtil::CutPosString( pEd->GetText(), aPosStr ); + bEditOk = ScRangeUtil::IsAbsPos( aPosStr, rDoc, + nTab, &theCompleteStr, nullptr, eConv ); + } + + if ( bEditOk ) + pEd->SetText( theCompleteStr ); + + return bEditOk; +} + +// Handler: + +IMPL_LINK( ScConsolidateDlg, GetEditFocusHdl, formula::RefEdit&, rControl, void ) +{ + m_pRefInputEdit = &rControl; +} + +IMPL_LINK( ScConsolidateDlg, GetFocusHdl, weld::Widget&, rControl, void ) +{ + if (&rControl == m_xLbDataArea.get()) + m_pRefInputEdit = m_xEdDataArea.get(); + else if (&rControl == m_xLbDestArea.get()) + m_pRefInputEdit = m_xEdDestArea.get(); +} + +IMPL_LINK_NOARG(ScConsolidateDlg, OkHdl, weld::Button&, void) +{ + const sal_Int32 nDataAreaCount = m_xLbConsAreas->n_children(); + + if ( nDataAreaCount > 0 ) + { + ScRefAddress aDestAddress; + SCTAB nTab = rViewData.GetTabNo(); + OUString aDestPosStr( m_xEdDestArea->GetText() ); + const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention(); + + if ( ScRangeUtil::IsAbsPos( aDestPosStr, rDoc, nTab, nullptr, &aDestAddress, eConv ) ) + { + ScConsolidateParam theOutParam( theConsData ); + std::unique_ptr<ScArea[]> pDataAreas(new ScArea[nDataAreaCount]); + + for ( sal_Int32 i=0; i<nDataAreaCount; ++i ) + { + ScRangeUtil::MakeArea(m_xLbConsAreas->get_text(i), + pDataAreas[i], rDoc, nTab, eConv); + } + + theOutParam.nCol = aDestAddress.Col(); + theOutParam.nRow = aDestAddress.Row(); + theOutParam.nTab = aDestAddress.Tab(); + theOutParam.eFunction = LbPosToFunc( m_xLbFunc->get_active() ); + theOutParam.bByCol = m_xBtnByCol->get_active(); + theOutParam.bByRow = m_xBtnByRow->get_active(); + theOutParam.bReferenceData = m_xBtnRefs->get_active(); + theOutParam.SetAreas( std::move(pDataAreas), nDataAreaCount ); + + ScConsolidateItem aOutItem( nWhichCons, &theOutParam ); + + SetDispatcherLock( false ); + SwitchToDocument(); + GetBindings().GetDispatcher()->ExecuteList(SID_CONSOLIDATE, + SfxCallMode::SLOT | SfxCallMode::RECORD, + { &aOutItem }); + response(RET_OK); + } + else + { + INFOBOX(m_xDialog.get(), STR_INVALID_TABREF); + m_xEdDestArea->GrabFocus(); + } + } + else + response(RET_CANCEL); // no area defined -> Cancel +} + +IMPL_LINK( ScConsolidateDlg, ClickHdl, weld::Button&, rBtn, void ) +{ + if ( &rBtn == m_xBtnCancel.get() ) + response(RET_CANCEL); + else if ( &rBtn == m_xBtnAdd.get() ) + { + if ( !m_xEdDataArea->GetText().isEmpty() ) + { + OUString aNewEntry( m_xEdDataArea->GetText() ); + std::unique_ptr<ScArea[]> ppAreas; + sal_uInt16 nAreaCount = 0; + const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention(); + + if ( ScRangeUtil::IsAbsTabArea( aNewEntry, &rDoc, &ppAreas, &nAreaCount, true, eConv ) ) + { + // IsAbsTabArea() creates an array of ScArea pointers, + // which have been created dynamically as well. + // These objects need to be deleted here. + + for ( sal_uInt16 i=0; i<nAreaCount; i++ ) + { + const ScArea& rArea = ppAreas[i]; + OUString aNewArea = ScRange( rArea.nColStart, rArea.nRowStart, rArea.nTab, + rArea.nColEnd, rArea.nRowEnd, rArea.nTab + ).Format(rDoc, ScRefFlags::RANGE_ABS_3D, eConv); + + if (m_xLbConsAreas->find_text(aNewArea) == -1) + { + m_xLbConsAreas->append_text( aNewArea ); + } + } + } + else if ( VerifyEdit( m_xEdDataArea.get() ) ) + { + OUString aNewArea( m_xEdDataArea->GetText() ); + + if (m_xLbConsAreas->find_text(aNewArea) == -1) + m_xLbConsAreas->append_text(aNewArea); + else + INFOBOX(m_xDialog.get(), STR_AREA_ALREADY_INSERTED); + } + else + { + INFOBOX(m_xDialog.get(), STR_INVALID_TABREF); + m_xEdDataArea->GrabFocus(); + } + } + } + else if ( &rBtn == m_xBtnRemove.get() ) + { + std::vector<int> aSelectedRows(m_xLbConsAreas->get_selected_rows()); + std::sort(aSelectedRows.begin(), aSelectedRows.end()); + for (auto it = aSelectedRows.rbegin(); it != aSelectedRows.rend(); ++it) + m_xLbConsAreas->remove(*it); + m_xBtnRemove->set_sensitive(false); + } +} + +IMPL_LINK( ScConsolidateDlg, SelectTVHdl, weld::TreeView&, rLb, void ) +{ + if (rLb.get_selected_index() != -1) + m_xBtnRemove->set_sensitive(true); + else + m_xBtnRemove->set_sensitive(false); +} + +IMPL_LINK( ScConsolidateDlg, SelectCBHdl, weld::ComboBox&, rLb, void ) +{ + formula::RefEdit* pEd = (&rLb == m_xLbDataArea.get()) ? m_xEdDataArea.get() : m_xEdDestArea.get(); + const sal_Int32 nSelPos = rLb.get_active(); + + if ( (nSelPos > 0) + && (nAreaDataCount > 0) + && (pAreaData != nullptr) ) + { + if ( o3tl::make_unsigned(nSelPos) <= nAreaDataCount ) + { + OUString aString( pAreaData[nSelPos-1].aStrArea ); + + if ( &rLb == m_xLbDestArea.get() ) + ScRangeUtil::CutPosString( aString, aString ); + + pEd->SetText( aString ); + + if ( pEd == m_xEdDataArea.get() ) + m_xBtnAdd->set_sensitive(true); + } + } + else + { + pEd->SetText( OUString() ); + if ( pEd == m_xEdDataArea.get() ) + m_xBtnAdd->set_sensitive(true); + } +} + +IMPL_LINK( ScConsolidateDlg, ModifyHdl, formula::RefEdit&, rEd, void ) +{ + if ( &rEd == m_xEdDataArea.get() ) + { + OUString aAreaStr( rEd.GetText() ); + if ( !aAreaStr.isEmpty() ) + m_xBtnAdd->set_sensitive(true); + else + m_xBtnAdd->set_sensitive(false); + } + else if ( &rEd == m_xEdDestArea.get() ) + { + m_xLbDestArea->set_active(0); + } +} + +// TODO: generalize! +// Resource of the ListBox and these two conversion methods are also in +// tpsubt and everywhere, where StarCalc functions are selectable. + +ScSubTotalFunc ScConsolidateDlg::LbPosToFunc( sal_Int32 nPos ) +{ + switch ( nPos ) + { + case 2: return SUBTOTAL_FUNC_AVE; + case 6: return SUBTOTAL_FUNC_CNT; + case 1: return SUBTOTAL_FUNC_CNT2; + case 3: return SUBTOTAL_FUNC_MAX; + case 4: return SUBTOTAL_FUNC_MIN; + case 5: return SUBTOTAL_FUNC_PROD; + case 7: return SUBTOTAL_FUNC_STD; + case 8: return SUBTOTAL_FUNC_STDP; + case 9: return SUBTOTAL_FUNC_VAR; + case 10: return SUBTOTAL_FUNC_VARP; + case 0: + default: + return SUBTOTAL_FUNC_SUM; + } +} + +sal_Int32 ScConsolidateDlg::FuncToLbPos( ScSubTotalFunc eFunc ) +{ + switch ( eFunc ) + { + case SUBTOTAL_FUNC_AVE: return 2; + case SUBTOTAL_FUNC_CNT: return 6; + case SUBTOTAL_FUNC_CNT2: return 1; + case SUBTOTAL_FUNC_MAX: return 3; + case SUBTOTAL_FUNC_MIN: return 4; + case SUBTOTAL_FUNC_PROD: return 5; + case SUBTOTAL_FUNC_STD: return 7; + case SUBTOTAL_FUNC_STDP: return 8; + case SUBTOTAL_FUNC_VAR: return 9; + case SUBTOTAL_FUNC_VARP: return 10; + case SUBTOTAL_FUNC_NONE: + case SUBTOTAL_FUNC_SUM: + default: + return 0; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/csvcontrol.cxx b/sc/source/ui/dbgui/csvcontrol.cxx new file mode 100644 index 0000000000..409e898b46 --- /dev/null +++ b/sc/source/ui/dbgui/csvcontrol.cxx @@ -0,0 +1,284 @@ +/* -*- 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 <csvcontrol.hxx> +#include <vcl/keycodes.hxx> +#include <vcl/outdev.hxx> +#include <vcl/settings.hxx> + +ScCsvLayoutData::ScCsvLayoutData() : + mnPosCount( 1 ), + mnPosOffset( 0 ), + mnWinWidth( 1 ), + mnHdrWidth( 0 ), + mnCharWidth( 1 ), + mnLineCount( 1 ), + mnLineOffset( 0 ), + mnWinHeight( 1 ), + mnHdrHeight( 0 ), + mnLineHeight( 1 ), + mnPosCursor( CSV_POS_INVALID ), + mnColCursor( 0 ), + mnNoRepaint( 0 ), + mbAppRTL( AllSettings::GetLayoutRTL() ) +{ +} + +ScCsvDiff ScCsvLayoutData::GetDiff( const ScCsvLayoutData& rData ) const +{ + ScCsvDiff nRet = ScCsvDiff::Equal; + if( mnPosCount != rData.mnPosCount ) nRet |= ScCsvDiff::PosCount; + if( mnPosOffset != rData.mnPosOffset ) nRet |= ScCsvDiff::PosOffset; + if( mnHdrWidth != rData.mnHdrWidth ) nRet |= ScCsvDiff::HeaderWidth; + if( mnCharWidth != rData.mnCharWidth ) nRet |= ScCsvDiff::CharWidth; + if( mnLineCount != rData.mnLineCount ) nRet |= ScCsvDiff::LineCount; + if( mnLineOffset != rData.mnLineOffset ) nRet |= ScCsvDiff::LineOffset; + if( mnHdrHeight != rData.mnHdrHeight ) nRet |= ScCsvDiff::HeaderHeight; + if( mnLineHeight != rData.mnLineHeight ) nRet |= ScCsvDiff::LineHeight; + if( mnPosCursor != rData.mnPosCursor ) nRet |= ScCsvDiff::RulerCursor; + if( mnColCursor != rData.mnColCursor ) nRet |= ScCsvDiff::GridCursor; + return nRet; +} + +ScCsvControl::ScCsvControl(const ScCsvLayoutData& rData) + : mrData(rData) + , mbValidGfx(false) +{ +} + +ScCsvControl::~ScCsvControl() +{ + if( mxAccessible.is() ) + mxAccessible->dispose(); + mxAccessible = nullptr; // lp#1566050: prevent cyclic reference zombies +} + +// event handling ------------------------------------------------------------- + +void ScCsvControl::GetFocus() +{ + weld::CustomWidgetController::GetFocus(); + AccSendFocusEvent( true ); +} + +void ScCsvControl::LoseFocus() +{ + weld::CustomWidgetController::LoseFocus(); + AccSendFocusEvent( false ); +} + +void ScCsvControl::AccSendFocusEvent( bool bFocused ) +{ + if( mxAccessible.is() ) + mxAccessible->SendFocusEvent( bFocused ); +} + +void ScCsvControl::AccSendCaretEvent() +{ + if( mxAccessible.is() ) + mxAccessible->SendCaretEvent(); +} + +void ScCsvControl::AccSendVisibleEvent() +{ + if( mxAccessible.is() ) + mxAccessible->SendVisibleEvent(); +} + +void ScCsvControl::AccSendSelectionEvent() +{ + if( mxAccessible.is() ) + mxAccessible->SendSelectionEvent(); +} + +void ScCsvControl::AccSendTableUpdateEvent( sal_uInt32 nFirstColumn, sal_uInt32 nLastColumn, bool bAllRows ) +{ + if( mxAccessible.is() ) + mxAccessible->SendTableUpdateEvent( nFirstColumn, nLastColumn, bAllRows ); +} + +void ScCsvControl::AccSendInsertColumnEvent( sal_uInt32 nFirstColumn, sal_uInt32 nLastColumn ) +{ + if( mxAccessible.is() ) + mxAccessible->SendInsertColumnEvent( nFirstColumn, nLastColumn ); +} + +void ScCsvControl::AccSendRemoveColumnEvent( sal_uInt32 nFirstColumn, sal_uInt32 nLastColumn ) +{ + if( mxAccessible.is() ) + mxAccessible->SendRemoveColumnEvent( nFirstColumn, nLastColumn ); +} + +// repaint helpers ------------------------------------------------------------ + +void ScCsvControl::Repaint( bool bInvalidate ) +{ + if( bInvalidate ) + InvalidateGfx(); + if( !IsNoRepaint() ) + Execute( CSVCMD_REPAINT ); +} + +void ScCsvControl::DisableRepaint() +{ + ++mrData.mnNoRepaint; +} + +void ScCsvControl::EnableRepaint() +{ + OSL_ENSURE( IsNoRepaint(), "ScCsvControl::EnableRepaint - invalid call" ); + --mrData.mnNoRepaint; + Repaint(); +} + +// command handling ----------------------------------------------------------- + +void ScCsvControl::Execute( ScCsvCmdType eType, sal_Int32 nParam1, sal_Int32 nParam2 ) +{ + maCmd.Set( eType, nParam1, nParam2 ); + maCmdHdl.Call( *this ); +} + +// layout helpers ------------------------------------------------------------- + +sal_Int32 ScCsvControl::GetVisPosCount() const +{ + return (mrData.mnWinWidth - GetHdrWidth()) / GetCharWidth(); +} + +sal_Int32 ScCsvControl::GetMaxPosOffset() const +{ + return std::max<sal_Int32>( GetPosCount() - GetVisPosCount() + 2, 0 ); +} + +bool ScCsvControl::IsValidSplitPos( sal_Int32 nPos ) const +{ + return (0 < nPos) && (nPos < GetPosCount() ); +} + +bool ScCsvControl::IsVisibleSplitPos( sal_Int32 nPos ) const +{ + return IsValidSplitPos( nPos ) && (GetFirstVisPos() <= nPos) && (nPos <= GetLastVisPos()); +} + +sal_Int32 ScCsvControl::GetHdrX() const +{ + return IsRTL() ? (mrData.mnWinWidth - GetHdrWidth()) : 0; +} + +sal_Int32 ScCsvControl::GetFirstX() const +{ + return IsRTL() ? 0 : GetHdrWidth(); +} + +sal_Int32 ScCsvControl::GetLastX() const +{ + return mrData.mnWinWidth - (IsRTL() ? GetHdrWidth() : 0) - 1; +} + +sal_Int32 ScCsvControl::GetX( sal_Int32 nPos ) const +{ + return GetFirstX() + (nPos - GetFirstVisPos()) * GetCharWidth(); +} + +sal_Int32 ScCsvControl::GetPosFromX( sal_Int32 nX ) const +{ + return (nX - GetFirstX() + GetCharWidth() / 2) / GetCharWidth() + GetFirstVisPos(); +} + +sal_Int32 ScCsvControl::GetVisLineCount() const +{ + return (mrData.mnWinHeight - GetHdrHeight() - 2) / GetLineHeight() + 1; +} + +sal_Int32 ScCsvControl::GetLastVisLine() const +{ + return std::min( GetFirstVisLine() + GetVisLineCount(), GetLineCount() ) - 1; +} + +sal_Int32 ScCsvControl::GetMaxLineOffset() const +{ + return std::max<sal_Int32>( GetLineCount() - GetVisLineCount() + 1, 0 ); +} + +bool ScCsvControl::IsValidLine( sal_Int32 nLine ) const +{ + return (0 <= nLine) && (nLine < GetLineCount()); +} + +bool ScCsvControl::IsVisibleLine( sal_Int32 nLine ) const +{ + return IsValidLine( nLine ) && (GetFirstVisLine() <= nLine) && (nLine <= GetLastVisLine()); +} + +sal_Int32 ScCsvControl::GetY( sal_Int32 nLine ) const +{ + return GetHdrHeight() + (nLine - GetFirstVisLine()) * GetLineHeight(); +} + +sal_Int32 ScCsvControl::GetLineFromY( sal_Int32 nY ) const +{ + return (nY - GetHdrHeight()) / GetLineHeight() + GetFirstVisLine(); +} + +// static helpers ------------------------------------------------------------- + +void ScCsvControl::ImplInvertRect( OutputDevice& rOutDev, const tools::Rectangle& rRect ) +{ + rOutDev.Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR | vcl::PushFlags::RASTEROP ); + rOutDev.SetLineColor( COL_BLACK ); + rOutDev.SetFillColor( COL_BLACK ); + rOutDev.SetRasterOp( RasterOp::Invert ); + rOutDev.DrawRect( rRect ); + rOutDev.Pop(); +} + +ScMoveMode ScCsvControl::GetHorzDirection( sal_uInt16 nCode, bool bHomeEnd ) +{ + switch( nCode ) + { + case KEY_LEFT: return MOVE_PREV; + case KEY_RIGHT: return MOVE_NEXT; + } + if( bHomeEnd ) switch( nCode ) + { + case KEY_HOME: return MOVE_FIRST; + case KEY_END: return MOVE_LAST; + } + return MOVE_NONE; +} + +ScMoveMode ScCsvControl::GetVertDirection( sal_uInt16 nCode, bool bHomeEnd ) +{ + switch( nCode ) + { + case KEY_UP: return MOVE_PREV; + case KEY_DOWN: return MOVE_NEXT; + case KEY_PAGEUP: return MOVE_PREVPAGE; + case KEY_PAGEDOWN: return MOVE_NEXTPAGE; + } + if( bHomeEnd ) switch( nCode ) + { + case KEY_HOME: return MOVE_FIRST; + case KEY_END: return MOVE_LAST; + } + return MOVE_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/csvgrid.cxx b/sc/source/ui/dbgui/csvgrid.cxx new file mode 100644 index 0000000000..f81e510fda --- /dev/null +++ b/sc/source/ui/dbgui/csvgrid.cxx @@ -0,0 +1,1416 @@ +/* -*- 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 <comphelper/lok.hxx> +#include <csvgrid.hxx> +#include <csvtablebox.hxx> + +#include <algorithm> +#include <memory> + +#include <svtools/colorcfg.hxx> +#include <sal/macros.h> +#include <tools/poly.hxx> +#include <scmod.hxx> +#include <asciiopt.hxx> +#include <impex.hxx> +#include <AccessibleCsvControl.hxx> + +// *** edit engine *** +#include <editeng/eeitem.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weldutils.hxx> + +#include <editeng/colritem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/langitem.hxx> +#include <svl/itempool.hxx> +#include <svl/itemset.hxx> +#include <editutil.hxx> +// *** edit engine *** + +namespace { + +struct Func_SetType +{ + sal_Int32 mnType; + explicit Func_SetType( sal_Int32 nType ) : mnType( nType ) {} + void operator()( ScCsvColState& rState ) const + { rState.mnType = mnType; } +}; + +struct Func_Select +{ + bool mbSelect; + explicit Func_Select( bool bSelect ) : mbSelect( bSelect ) {} + void operator()( ScCsvColState& rState ) const + { rState.Select( mbSelect ); } +}; + +} + +ScCsvGrid::ScCsvGrid(const ScCsvLayoutData& rData, std::unique_ptr<weld::Menu> xPopup, ScCsvTableBox* pTableBox) + : ScCsvControl(rData) + , mpTableBox(pTableBox) + , mpBackgrDev( VclPtr<VirtualDevice>::Create() ) + , mpGridDev( VclPtr<VirtualDevice>::Create() ) + , mxPopup(std::move(xPopup)) + , mpColorConfig( nullptr ) + , mpEditEngine( new ScEditEngineDefaulter( EditEngine::CreatePool().get(), true ) ) + , maColStates( 1 ) + , maTypeNames( 1 ) + , mnFirstImpLine( 0 ) + , mnRecentSelCol( CSV_COLUMN_INVALID ) + , mnMTCurrCol( SAL_MAX_UINT32 ) + , mbTracking( false ) + , mbMTSelecting( false ) +{ + mpEditEngine->SetRefDevice( mpBackgrDev.get() ); + mpEditEngine->SetRefMapMode( MapMode( MapUnit::MapPixel ) ); + maEdEngSize = mpEditEngine->GetPaperSize(); +} + +void ScCsvGrid::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + OutputDevice& rRefDevice = pDrawingArea->get_ref_device(); + maHeaderFont = Application::GetSettings().GetStyleSettings().GetAppFont(); + + // expand the point size of the desired font to the equivalent pixel size + weld::SetPointFont(rRefDevice, maHeaderFont); + maHeaderFont = rRefDevice.GetFont(); + + // Because this is an always LeftToRight layout widget the initial size of + // this widget needs to be smaller than the size of the parent scrolling + // window (ScCsvTableBox ctor) because in RTL mode the alignment is against + // the right edge of the parent, and if larger than the scrolling window + // the left edge will be lost. If this widget is smaller than the scrolling + // window it is stretched to fit the parent and the problem doesn't arise. + Size aInitialSize(10, 10); + if (comphelper::LibreOfficeKit::isActive()) + aInitialSize = Size(-1, 150); + ScCsvControl::SetDrawingArea(pDrawingArea); + pDrawingArea->set_size_request(aInitialSize.Width(), aInitialSize.Height()); + SetOutputSizePixel(aInitialSize); + + EnableRTL( false ); // RTL + + InitFonts(); + ImplClearSplits(); +} + +ScCsvGrid::~ScCsvGrid() +{ + OSL_ENSURE(mpColorConfig, "the object hasn't been initialized properly"); + if (mpColorConfig) + mpColorConfig->RemoveListener(this); + mpBackgrDev.disposeAndClear(); + mpGridDev.disposeAndClear(); +} + +void +ScCsvGrid::Init() +{ + OSL_PRECOND(!mpColorConfig, "the object has already been initialized"); + mpColorConfig = &SC_MOD()->GetColorConfig(); + InitColors(); + mpColorConfig->AddListener(this); +} + +// common grid handling ------------------------------------------------------- + +void ScCsvGrid::UpdateLayoutData() +{ + DisableRepaint(); + OutputDevice& rRefDevice = GetDrawingArea()->get_ref_device(); + rRefDevice.SetFont(maMonoFont); + Execute(CSVCMD_SETCHARWIDTH, rRefDevice.GetTextWidth(OUString('X'))); + Execute(CSVCMD_SETLINEHEIGHT, rRefDevice.GetTextHeight() + 1); + rRefDevice.SetFont(maHeaderFont); + Execute(CSVCMD_SETHDRHEIGHT, rRefDevice.GetTextHeight() + 1); + UpdateOffsetX(); + EnableRepaint(); +} + +void ScCsvGrid::UpdateOffsetX() +{ + sal_Int32 nLastLine = GetLastVisLine() + 1; + sal_Int32 nDigits = 2; + for (;;) + { + nLastLine /= 10; + if (!nLastLine) + break; + ++nDigits; + } + nDigits = std::max( nDigits, sal_Int32( 3 ) ); + Execute(CSVCMD_SETHDRWIDTH, GetDrawingArea()->get_approximate_digit_width() * nDigits); +} + +void ScCsvGrid::ApplyLayout( const ScCsvLayoutData& rOldData ) +{ + ScCsvDiff nDiff = GetLayoutData().GetDiff( rOldData ); + if( nDiff == ScCsvDiff::Equal ) return; + + DisableRepaint(); + + if( nDiff & ScCsvDiff::RulerCursor ) + { + ImplInvertCursor( rOldData.mnPosCursor ); + ImplInvertCursor( GetRulerCursorPos() ); + } + + if( nDiff & ScCsvDiff::PosCount ) + { + if( GetPosCount() < rOldData.mnPosCount ) + { + SelectAll( false ); + maSplits.RemoveRange( GetPosCount(), rOldData.mnPosCount ); + } + else + maSplits.Remove( rOldData.mnPosCount ); + maSplits.Insert( GetPosCount() ); + maColStates.resize( maSplits.Count() - 1 ); + } + + if( nDiff & ScCsvDiff::LineOffset ) + { + Execute( CSVCMD_UPDATECELLTEXTS ); + UpdateOffsetX(); + } + + ScCsvDiff nHVDiff = nDiff & (ScCsvDiff::HorizontalMask | ScCsvDiff::VerticalMask); + if( nHVDiff == ScCsvDiff::PosOffset ) + ImplDrawHorzScrolled( rOldData.mnPosOffset ); + else if( nHVDiff != ScCsvDiff::Equal ) + InvalidateGfx(); + + EnableRepaint(); + + if( nDiff & (ScCsvDiff::PosOffset | ScCsvDiff::LineOffset) ) + AccSendVisibleEvent(); +} + +void ScCsvGrid::SetFirstImportedLine( sal_Int32 nLine ) +{ + ImplDrawFirstLineSep( false ); + mnFirstImpLine = nLine; + ImplDrawFirstLineSep( true ); + ImplDrawGridDev(); + Repaint(); +} + +sal_Int32 ScCsvGrid::GetNoScrollCol( sal_Int32 nPos ) const +{ + sal_Int32 nNewPos = nPos; + if( nNewPos != CSV_POS_INVALID ) + { + if( nNewPos < GetFirstVisPos() + CSV_SCROLL_DIST ) + { + sal_Int32 nScroll = (GetFirstVisPos() > 0) ? CSV_SCROLL_DIST : 0; + nNewPos = GetFirstVisPos() + nScroll; + } + else if( nNewPos > GetLastVisPos() - CSV_SCROLL_DIST - 1 ) + { + sal_Int32 nScroll = (GetFirstVisPos() < GetMaxPosOffset()) ? CSV_SCROLL_DIST : 0; + nNewPos = GetLastVisPos() - nScroll - 1; + } + } + return nNewPos; +} + +void ScCsvGrid::InitColors() +{ + OSL_PRECOND(mpColorConfig, "the object hasn't been initialized properly"); + if ( !mpColorConfig ) + return; + maBackColor = mpColorConfig->GetColorValue( ::svtools::DOCCOLOR ).nColor; + maGridColor = mpColorConfig->GetColorValue( ::svtools::CALCGRID ).nColor; + maGridPBColor = mpColorConfig->GetColorValue( ::svtools::CALCPAGEBREAK ).nColor; + maAppBackColor = mpColorConfig->GetColorValue( ::svtools::APPBACKGROUND ).nColor; + maTextColor = mpColorConfig->GetColorValue( ::svtools::FONTCOLOR, false ).nColor; + + // tdf#147386 If Automatic font color is used, then check background color and use Black/White as font color + if ( maTextColor == COL_AUTO ) + { + if ( maBackColor.IsDark() ) + maTextColor = COL_WHITE; + else + maTextColor = COL_BLACK; + } + + const StyleSettings& rSett = Application::GetSettings().GetStyleSettings(); + maHeaderBackColor = rSett.GetFaceColor(); + maHeaderGridColor = rSett.GetDarkShadowColor(); + maHeaderTextColor = rSett.GetButtonTextColor(); + maSelectColor = rSett.GetActiveColor(); + + InvalidateGfx(); +} + +void ScCsvGrid::InitFonts() +{ + maMonoFont = OutputDevice::GetDefaultFont( DefaultFontType::FIXED, LANGUAGE_ENGLISH_US, GetDefaultFontFlags::NONE ); + maMonoFont.SetFontSize( Size( maMonoFont.GetFontSize().Width(), maHeaderFont.GetFontSize().Height() ) ); + + /* *** Set edit engine defaults *** + maMonoFont for Latin script, smaller default font for Asian and Complex script. */ + + // get default fonts + SvxFontItem aLatinItem( EE_CHAR_FONTINFO ); + SvxFontItem aAsianItem( EE_CHAR_FONTINFO_CJK ); + SvxFontItem aComplexItem( EE_CHAR_FONTINFO_CTL ); + ::GetDefaultFonts( aLatinItem, aAsianItem, aComplexItem ); + + // create item set for defaults + SfxItemSet aDefSet( mpEditEngine->GetEmptyItemSet() ); + EditEngine::SetFontInfoInItemSet( aDefSet, maMonoFont ); + aDefSet.Put( aAsianItem ); + aDefSet.Put( aComplexItem ); + + // set Asian/Complex font size to height of character in Latin font + sal_uLong nFontHt = static_cast< sal_uLong >( maMonoFont.GetFontSize().Height() ); + aDefSet.Put( SvxFontHeightItem( nFontHt, 100, EE_CHAR_FONTHEIGHT_CJK ) ); + aDefSet.Put( SvxFontHeightItem( nFontHt, 100, EE_CHAR_FONTHEIGHT_CTL ) ); + + // copy other items from default font + const SfxPoolItem& rWeightItem = aDefSet.Get( EE_CHAR_WEIGHT ); + std::unique_ptr<SfxPoolItem> pNewItem(rWeightItem.Clone()); + pNewItem->SetWhich(EE_CHAR_WEIGHT_CJK); + aDefSet.Put( *pNewItem ); + pNewItem->SetWhich(EE_CHAR_WEIGHT_CTL); + aDefSet.Put( *pNewItem ); + const SfxPoolItem& rItalicItem = aDefSet.Get( EE_CHAR_ITALIC ); + pNewItem.reset(rItalicItem.Clone()); + pNewItem->SetWhich(EE_CHAR_ITALIC_CJK); + aDefSet.Put( *pNewItem ); + pNewItem->SetWhich(EE_CHAR_ITALIC_CTL); + aDefSet.Put( *pNewItem ); + const SfxPoolItem& rLangItem = aDefSet.Get( EE_CHAR_LANGUAGE ); + pNewItem.reset(rLangItem.Clone()); + pNewItem->SetWhich(EE_CHAR_LANGUAGE_CJK); + aDefSet.Put( *pNewItem ); + pNewItem->SetWhich(EE_CHAR_LANGUAGE_CTL); + aDefSet.Put( *pNewItem ); + + mpEditEngine->SetDefaults( aDefSet ); + InvalidateGfx(); +} + +void ScCsvGrid::InitSizeData() +{ + maWinSize = GetOutputSizePixel(); + mpBackgrDev->SetOutputSizePixel( maWinSize ); + mpGridDev->SetOutputSizePixel( maWinSize ); + InvalidateGfx(); +} + +// split handling ------------------------------------------------------------- + +void ScCsvGrid::InsertSplit( sal_Int32 nPos ) +{ + if( ImplInsertSplit( nPos ) ) + { + DisableRepaint(); + Execute( CSVCMD_EXPORTCOLUMNTYPE ); + Execute( CSVCMD_UPDATECELLTEXTS ); + sal_uInt32 nColIx = GetColumnFromPos( nPos ); + ImplDrawColumn( nColIx - 1 ); + ImplDrawColumn( nColIx ); + ValidateGfx(); // performance: do not redraw all columns + EnableRepaint(); + } +} + +void ScCsvGrid::RemoveSplit( sal_Int32 nPos ) +{ + if( ImplRemoveSplit( nPos ) ) + { + DisableRepaint(); + Execute( CSVCMD_EXPORTCOLUMNTYPE ); + Execute( CSVCMD_UPDATECELLTEXTS ); + ImplDrawColumn( GetColumnFromPos( nPos ) ); + ValidateGfx(); // performance: do not redraw all columns + EnableRepaint(); + } +} + +void ScCsvGrid::MoveSplit( sal_Int32 nPos, sal_Int32 nNewPos ) +{ + sal_uInt32 nColIx = GetColumnFromPos( nPos ); + if( nColIx == CSV_COLUMN_INVALID ) + return; + + DisableRepaint(); + if( (GetColumnPos( nColIx - 1 ) < nNewPos) && (nNewPos < GetColumnPos( nColIx + 1 )) ) + { + // move a split in the range between 2 others -> keep selection state of both columns + maSplits.Remove( nPos ); + maSplits.Insert( nNewPos ); + Execute( CSVCMD_UPDATECELLTEXTS ); + ImplDrawColumn( nColIx - 1 ); + ImplDrawColumn( nColIx ); + ValidateGfx(); // performance: do not redraw all columns + AccSendTableUpdateEvent( nColIx - 1, nColIx ); + } + else + { + ImplRemoveSplit( nPos ); + ImplInsertSplit( nNewPos ); + Execute( CSVCMD_EXPORTCOLUMNTYPE ); + Execute( CSVCMD_UPDATECELLTEXTS ); + } + EnableRepaint(); +} + +void ScCsvGrid::RemoveAllSplits() +{ + DisableRepaint(); + ImplClearSplits(); + Execute( CSVCMD_EXPORTCOLUMNTYPE ); + Execute( CSVCMD_UPDATECELLTEXTS ); + EnableRepaint(); +} + +void ScCsvGrid::SetSplits( const ScCsvSplits& rSplits ) +{ + DisableRepaint(); + ImplClearSplits(); + sal_uInt32 nCount = rSplits.Count(); + for( sal_uInt32 nIx = 0; nIx < nCount; ++nIx ) + maSplits.Insert( rSplits[ nIx ] ); + maColStates.clear(); + maColStates.resize( maSplits.Count() - 1 ); + Execute( CSVCMD_EXPORTCOLUMNTYPE ); + Execute( CSVCMD_UPDATECELLTEXTS ); + EnableRepaint(); +} + +bool ScCsvGrid::ImplInsertSplit( sal_Int32 nPos ) +{ + sal_uInt32 nColIx = GetColumnFromPos( nPos ); + bool bRet = (nColIx < GetColumnCount()) && maSplits.Insert( nPos ); + if( bRet ) + { + ScCsvColState aState( GetColumnType( nColIx ) ); + aState.Select( IsSelected( nColIx ) && IsSelected( nColIx + 1 ) ); + maColStates.insert( maColStates.begin() + nColIx + 1, aState ); + AccSendInsertColumnEvent( nColIx + 1, nColIx + 1 ); + AccSendTableUpdateEvent( nColIx, nColIx ); + } + return bRet; +} + +bool ScCsvGrid::ImplRemoveSplit( sal_Int32 nPos ) +{ + bool bRet = maSplits.Remove( nPos ); + if( bRet ) + { + sal_uInt32 nColIx = GetColumnFromPos( nPos ); + bool bSel = IsSelected( nColIx ) || IsSelected( nColIx + 1 ); + maColStates.erase( maColStates.begin() + nColIx + 1 ); + maColStates[ nColIx ].Select( bSel ); + AccSendRemoveColumnEvent( nColIx + 1, nColIx + 1 ); + AccSendTableUpdateEvent( nColIx, nColIx ); + } + return bRet; +} + +void ScCsvGrid::ImplClearSplits() +{ + sal_uInt32 nColumns = GetColumnCount(); + maSplits.Clear(); + maSplits.Insert( 0 ); + maSplits.Insert( GetPosCount() ); + maColStates.resize( 1 ); + InvalidateGfx(); + AccSendRemoveColumnEvent( 1, nColumns - 1 ); +} + +// columns/column types ------------------------------------------------------- + +sal_uInt32 ScCsvGrid::GetFirstVisColumn() const +{ + return GetColumnFromPos( GetFirstVisPos() ); +} + +sal_uInt32 ScCsvGrid::GetLastVisColumn() const +{ + return GetColumnFromPos( std::min( GetLastVisPos(), GetPosCount() ) - 1 ); +} + +bool ScCsvGrid::IsValidColumn( sal_uInt32 nColIndex ) const +{ + return nColIndex < GetColumnCount(); +} + +bool ScCsvGrid::IsVisibleColumn( sal_uInt32 nColIndex ) const +{ + return IsValidColumn( nColIndex ) && + (GetColumnPos( nColIndex ) < GetLastVisPos()) && + (GetFirstVisPos() < GetColumnPos( nColIndex + 1 )); +} + +sal_Int32 ScCsvGrid::GetColumnX( sal_uInt32 nColIndex ) const +{ + return GetX( GetColumnPos( nColIndex ) ); +} + +sal_uInt32 ScCsvGrid::GetColumnFromX( sal_Int32 nX ) const +{ + sal_Int32 nPos = (nX - GetFirstX()) / GetCharWidth() + GetFirstVisPos(); + return ((GetFirstVisPos() <= nPos) && (nPos <= GetLastVisPos())) ? + GetColumnFromPos( nPos ) : CSV_COLUMN_INVALID; +} + +sal_uInt32 ScCsvGrid::GetColumnFromPos( sal_Int32 nPos ) const +{ + return maSplits.UpperBound( nPos ); +} + +sal_Int32 ScCsvGrid::GetColumnWidth( sal_uInt32 nColIndex ) const +{ + return IsValidColumn( nColIndex ) ? (GetColumnPos( nColIndex + 1 ) - GetColumnPos( nColIndex )) : 0; +} + +void ScCsvGrid::SetColumnStates( ScCsvColStateVec&& rStates ) +{ + maColStates = std::move(rStates); + maColStates.resize( maSplits.Count() - 1 ); + Execute( CSVCMD_EXPORTCOLUMNTYPE ); + AccSendTableUpdateEvent( 0, GetColumnCount(), false ); + AccSendSelectionEvent(); +} + +sal_Int32 ScCsvGrid::GetColumnType( sal_uInt32 nColIndex ) const +{ + return IsValidColumn( nColIndex ) ? maColStates[ nColIndex ].mnType : CSV_TYPE_NOSELECTION; +} + +void ScCsvGrid::SetColumnType( sal_uInt32 nColIndex, sal_Int32 nColType ) +{ + if( IsValidColumn( nColIndex ) ) + { + maColStates[ nColIndex ].mnType = nColType; + AccSendTableUpdateEvent( nColIndex, nColIndex, false ); + } +} + +sal_Int32 ScCsvGrid::GetSelColumnType() const +{ + sal_uInt32 nColIx = GetFirstSelected(); + if( nColIx == CSV_COLUMN_INVALID ) + return CSV_TYPE_NOSELECTION; + + sal_Int32 nType = GetColumnType( nColIx ); + while( (nColIx != CSV_COLUMN_INVALID) && (nType != CSV_TYPE_MULTI) ) + { + if( nType != GetColumnType( nColIx ) ) + nType = CSV_TYPE_MULTI; + nColIx = GetNextSelected( nColIx ); + } + return nType; +} + +void ScCsvGrid::SetSelColumnType( sal_Int32 nType ) +{ + if( (nType != CSV_TYPE_MULTI) && (nType != CSV_TYPE_NOSELECTION) ) + { + for( sal_uInt32 nColIx = GetFirstSelected(); nColIx != CSV_COLUMN_INVALID; nColIx = GetNextSelected( nColIx ) ) + SetColumnType( nColIx, nType ); + Repaint( true ); + Execute( CSVCMD_EXPORTCOLUMNTYPE ); + } +} + +void ScCsvGrid::SetTypeNames( std::vector<OUString>&& rTypeNames ) +{ + OSL_ENSURE( !rTypeNames.empty(), "ScCsvGrid::SetTypeNames - vector is empty" ); + maTypeNames = std::move(rTypeNames); + Repaint( true ); + + mxPopup->clear(); + sal_uInt32 nCount = maTypeNames.size(); + for (sal_uInt32 nIx = 0; nIx < nCount; ++nIx) + mxPopup->append(OUString::number(nIx), maTypeNames[nIx]); + + ::std::for_each( maColStates.begin(), maColStates.end(), Func_SetType( CSV_TYPE_DEFAULT ) ); +} + +OUString ScCsvGrid::GetColumnTypeName( sal_uInt32 nColIndex ) const +{ + sal_uInt32 nTypeIx = static_cast< sal_uInt32 >( GetColumnType( nColIndex ) ); + return (nTypeIx < maTypeNames.size()) ? maTypeNames[ nTypeIx ] : OUString(); +} + +static sal_uInt8 lcl_GetExtColumnType( sal_Int32 nIntType ) +{ + static const sal_uInt8 pExtTypes[] = + { SC_COL_STANDARD, SC_COL_TEXT, SC_COL_DMY, SC_COL_MDY, SC_COL_YMD, SC_COL_ENGLISH, SC_COL_SKIP }; + static const sal_Int32 nExtTypeCount = SAL_N_ELEMENTS(pExtTypes); + return pExtTypes[ ((0 <= nIntType) && (nIntType < nExtTypeCount)) ? nIntType : 0 ]; +} + +void ScCsvGrid::FillColumnDataSep( ScAsciiOptions& rOptions ) const +{ + sal_uInt32 nCount = GetColumnCount(); + ScCsvExpDataVec aDataVec; + + for( sal_uInt32 nColIx = 0; nColIx < nCount; ++nColIx ) + { + if( GetColumnType( nColIx ) != CSV_TYPE_DEFAULT ) + // 1-based column index + aDataVec.emplace_back( + static_cast< sal_Int32 >( nColIx + 1 ), + lcl_GetExtColumnType( GetColumnType( nColIx ) ) ); + } + rOptions.SetColumnInfo( aDataVec ); +} + +void ScCsvGrid::FillColumnDataFix( ScAsciiOptions& rOptions ) const +{ + sal_uInt32 nCount = std::min( GetColumnCount(), static_cast<sal_uInt32>(MAXCOLCOUNT) ); + ScCsvExpDataVec aDataVec( nCount + 1 ); + + for( sal_uInt32 nColIx = 0; nColIx < nCount; ++nColIx ) + { + ScCsvExpData& rData = aDataVec[ nColIx ]; + rData.mnIndex = GetColumnPos( nColIx ); + rData.mnType = lcl_GetExtColumnType( GetColumnType( nColIx ) ); + } + aDataVec[ nCount ].mnIndex = SAL_MAX_INT32; + aDataVec[ nCount ].mnType = SC_COL_SKIP; + rOptions.SetColumnInfo( aDataVec ); +} + +void ScCsvGrid::ScrollVertRel( ScMoveMode eDir ) +{ + sal_Int32 nLine = GetFirstVisLine(); + switch( eDir ) + { + case MOVE_PREV: --nLine; break; + case MOVE_NEXT: ++nLine; break; + case MOVE_FIRST: nLine = 0; break; + case MOVE_LAST: nLine = GetMaxLineOffset(); break; + case MOVE_PREVPAGE: nLine -= GetVisLineCount() - 2; break; + case MOVE_NEXTPAGE: nLine += GetVisLineCount() - 2; break; + default: + { + // added to avoid warnings + } + } + Execute( CSVCMD_SETLINEOFFSET, nLine ); +} + +void ScCsvGrid::ExecutePopup( const Point& rPos ) +{ + OUString sItemId = mxPopup->popup_at_rect(GetDrawingArea(), tools::Rectangle(rPos, Size(1, 1))); + if (!sItemId.isEmpty()) // empty = cancelled + Execute(CSVCMD_SETCOLUMNTYPE, sItemId.toInt32()); +} + +// selection handling --------------------------------------------------------- + +bool ScCsvGrid::IsSelected( sal_uInt32 nColIndex ) const +{ + return IsValidColumn( nColIndex ) && maColStates[ nColIndex ].IsSelected(); +} + +sal_uInt32 ScCsvGrid::GetFirstSelected() const +{ + return IsSelected( 0 ) ? 0 : GetNextSelected( 0 ); +} + +sal_uInt32 ScCsvGrid::GetNextSelected( sal_uInt32 nFromIndex ) const +{ + sal_uInt32 nColCount = GetColumnCount(); + for( sal_uInt32 nColIx = nFromIndex + 1; nColIx < nColCount; ++nColIx ) + if( IsSelected( nColIx ) ) + return nColIx; + return CSV_COLUMN_INVALID; +} + +void ScCsvGrid::Select( sal_uInt32 nColIndex, bool bSelect ) +{ + if( IsValidColumn( nColIndex ) ) + { + maColStates[ nColIndex ].Select( bSelect ); + ImplDrawColumnSelection( nColIndex ); + Repaint(); + Execute( CSVCMD_EXPORTCOLUMNTYPE ); + if( bSelect ) + mnRecentSelCol = nColIndex; + AccSendSelectionEvent(); + } +} + +void ScCsvGrid::ToggleSelect( sal_uInt32 nColIndex ) +{ + Select( nColIndex, !IsSelected( nColIndex ) ); +} + +void ScCsvGrid::SelectRange( sal_uInt32 nColIndex1, sal_uInt32 nColIndex2, bool bSelect ) +{ + if( nColIndex1 == CSV_COLUMN_INVALID ) + Select( nColIndex2 ); + else if( nColIndex2 == CSV_COLUMN_INVALID ) + Select( nColIndex1 ); + else if( nColIndex1 > nColIndex2 ) + { + SelectRange( nColIndex2, nColIndex1, bSelect ); + if( bSelect ) + mnRecentSelCol = nColIndex1; + } + else if( IsValidColumn( nColIndex1 ) && IsValidColumn( nColIndex2 ) ) + { + for( sal_uInt32 nColIx = nColIndex1; nColIx <= nColIndex2; ++nColIx ) + { + maColStates[ nColIx ].Select( bSelect ); + ImplDrawColumnSelection( nColIx ); + } + Repaint(); + Execute( CSVCMD_EXPORTCOLUMNTYPE ); + if( bSelect ) + mnRecentSelCol = nColIndex1; + AccSendSelectionEvent(); + } +} + +void ScCsvGrid::SelectAll( bool bSelect ) +{ + SelectRange( 0, GetColumnCount() - 1, bSelect ); +} + +void ScCsvGrid::MoveCursor( sal_uInt32 nColIndex ) +{ + DisableRepaint(); + if( IsValidColumn( nColIndex ) ) + { + sal_Int32 nPosBeg = GetColumnPos( nColIndex ); + sal_Int32 nPosEnd = GetColumnPos( nColIndex + 1 ); + sal_Int32 nMinPos = std::max( nPosBeg - CSV_SCROLL_DIST, sal_Int32( 0 ) ); + sal_Int32 nMaxPos = std::min( nPosEnd - GetVisPosCount() + CSV_SCROLL_DIST + sal_Int32( 1 ), nMinPos ); + if( nPosBeg - CSV_SCROLL_DIST + 1 <= GetFirstVisPos() ) + Execute( CSVCMD_SETPOSOFFSET, nMinPos ); + else if( nPosEnd + CSV_SCROLL_DIST >= GetLastVisPos() ) + Execute( CSVCMD_SETPOSOFFSET, nMaxPos ); + } + Execute( CSVCMD_MOVEGRIDCURSOR, GetColumnPos( nColIndex ) ); + EnableRepaint(); +} + +void ScCsvGrid::MoveCursorRel( ScMoveMode eDir ) +{ + if( GetFocusColumn() == CSV_COLUMN_INVALID ) + return; + + switch( eDir ) + { + case MOVE_FIRST: + MoveCursor( 0 ); + break; + case MOVE_LAST: + MoveCursor( GetColumnCount() - 1 ); + break; + case MOVE_PREV: + if( GetFocusColumn() > 0 ) + MoveCursor( GetFocusColumn() - 1 ); + break; + case MOVE_NEXT: + if( GetFocusColumn() < GetColumnCount() - 1 ) + MoveCursor( GetFocusColumn() + 1 ); + break; + default: + { + // added to avoid warnings + } + } +} + +void ScCsvGrid::ImplClearSelection() +{ + ::std::for_each( maColStates.begin(), maColStates.end(), Func_Select( false ) ); + ImplDrawGridDev(); +} + +void ScCsvGrid::DoSelectAction( sal_uInt32 nColIndex, sal_uInt16 nModifier ) +{ + if( !(nModifier & KEY_MOD1) ) + ImplClearSelection(); + if( nModifier & KEY_SHIFT ) // SHIFT always expands + SelectRange( mnRecentSelCol, nColIndex ); + else if( !(nModifier & KEY_MOD1) ) // no SHIFT/CTRL always selects 1 column + Select( nColIndex ); + else if( mbTracking ) // CTRL in tracking does not toggle + Select( nColIndex, mbMTSelecting ); + else // CTRL only toggles + ToggleSelect( nColIndex ); + Execute( CSVCMD_MOVEGRIDCURSOR, GetColumnPos( nColIndex ) ); +} + +// cell contents -------------------------------------------------------------- + +void ScCsvGrid::ImplSetTextLineSep( + sal_Int32 nLine, const OUString& rTextLine, + const OUString& rSepChars, sal_Unicode cTextSep, bool bMergeSep, bool bRemoveSpace ) +{ + if( nLine < GetFirstVisLine() ) return; + + sal_uInt32 nLineIx = nLine - GetFirstVisLine(); + while( maTexts.size() <= nLineIx ) + maTexts.emplace_back( ); + std::vector<OUString>& rStrVec = maTexts[ nLineIx ]; + rStrVec.clear(); + + // scan for separators + OUString aCellText; + const sal_Unicode* pSepChars = rSepChars.getStr(); + const sal_Unicode* pChar = rTextLine.getStr(); + sal_uInt32 nColIx = 0; + + while( *pChar && (nColIx < sal::static_int_cast<sal_uInt32>(CSV_MAXCOLCOUNT)) ) + { + // scan for next cell text + bool bIsQuoted = false; + bool bOverflowCell = false; + pChar = ScImportExport::ScanNextFieldFromString( pChar, aCellText, + cTextSep, pSepChars, bMergeSep, bIsQuoted, bOverflowCell, bRemoveSpace ); + /* TODO: signal overflow somewhere in UI */ + + // update column width + sal_Int32 nWidth = std::max( CSV_MINCOLWIDTH, ScImportExport::CountVisualWidth( aCellText ) + 1 ); + if( IsValidColumn( nColIx ) ) + { + // expand existing column + sal_Int32 nDiff = nWidth - GetColumnWidth( nColIx ); + if( nDiff > 0 ) + { + Execute( CSVCMD_SETPOSCOUNT, GetPosCount() + nDiff ); + for( sal_uInt32 nSplitIx = GetColumnCount() - 1; nSplitIx > nColIx; --nSplitIx ) + { + sal_Int32 nPos = maSplits[ nSplitIx ]; + maSplits.Remove( nPos ); + maSplits.Insert( nPos + nDiff ); + } + } + } + else + { + // append new column + sal_Int32 nLastPos = GetPosCount(); + Execute( CSVCMD_SETPOSCOUNT, nLastPos + nWidth ); + ImplInsertSplit( nLastPos ); + } + + if( aCellText.getLength() <= CSV_MAXSTRLEN ) + rStrVec.push_back( aCellText ); + else + rStrVec.push_back( aCellText.copy( 0, CSV_MAXSTRLEN ) ); + ++nColIx; + } + InvalidateGfx(); +} + +void ScCsvGrid::ImplSetTextLineFix( sal_Int32 nLine, const OUString& rTextLine ) +{ + if( nLine < GetFirstVisLine() ) return; + + sal_Int32 nWidth = ScImportExport::CountVisualWidth( rTextLine ); + if( nWidth > GetPosCount() ) + Execute( CSVCMD_SETPOSCOUNT, nWidth ); + + sal_uInt32 nLineIx = nLine - GetFirstVisLine(); + while( maTexts.size() <= nLineIx ) + maTexts.emplace_back( ); + + std::vector<OUString>& rStrVec = maTexts[ nLineIx ]; + rStrVec.clear(); + sal_uInt32 nColCount = GetColumnCount(); + sal_Int32 nStrLen = rTextLine.getLength(); + sal_Int32 nStrIx = 0; + for( sal_uInt32 nColIx = 0; (nColIx < nColCount) && (nStrIx < nStrLen); ++nColIx ) + { + sal_Int32 nColWidth = GetColumnWidth( nColIx ); + sal_Int32 nLastIx = nStrIx; + ScImportExport::CountVisualWidth( rTextLine, nLastIx, nColWidth ); + sal_Int32 nLen = std::min( CSV_MAXSTRLEN, nLastIx - nStrIx ); + rStrVec.push_back( rTextLine.copy( nStrIx, nLen ) ); + nStrIx = nStrIx + nLen; + } + InvalidateGfx(); +} + +OUString ScCsvGrid::GetCellText( sal_uInt32 nColIndex, sal_Int32 nLine ) const +{ + if( nLine < GetFirstVisLine() ) return OUString(); + + sal_uInt32 nLineIx = nLine - GetFirstVisLine(); + if( nLineIx >= maTexts.size() ) return OUString(); + + const std::vector<OUString>& rStrVec = maTexts[ nLineIx ]; + if( nColIndex >= rStrVec.size() ) return OUString(); + + return rStrVec[ nColIndex ]; +} + +// event handling ------------------------------------------------------------- + +void ScCsvGrid::Resize() +{ + mpTableBox->InitControls(); + + ScCsvControl::Resize(); + InitSizeData(); + Execute( CSVCMD_UPDATECELLTEXTS ); +} + +void ScCsvGrid::GetFocus() +{ + ScCsvControl::GetFocus(); + Execute( CSVCMD_MOVEGRIDCURSOR, GetNoScrollCol( GetGridCursorPos() ) ); + Repaint(); +} + +void ScCsvGrid::LoseFocus() +{ + ScCsvControl::LoseFocus(); + Repaint(); +} + +bool ScCsvGrid::MouseButtonDown( const MouseEvent& rMEvt ) +{ + DisableRepaint(); + if( !HasFocus() ) + GrabFocus(); + + Point aPos( rMEvt.GetPosPixel() ); + sal_uInt32 nColIx = GetColumnFromX( aPos.X() ); + + if( rMEvt.IsLeft() ) + { + if( (GetFirstX() > aPos.X()) || (aPos.X() > GetLastX()) ) // in header column + { + if( aPos.Y() <= GetHdrHeight() ) + SelectAll(); + } + else if( IsValidColumn( nColIx ) ) + { + DoSelectAction( nColIx, rMEvt.GetModifier() ); + mnMTCurrCol = nColIx; + mbMTSelecting = IsSelected( nColIx ); + mbTracking = true; + } + } + EnableRepaint(); + return true; +} + +bool ScCsvGrid::MouseButtonUp( const MouseEvent& ) +{ + mbTracking = false; + return true; +} + +bool ScCsvGrid::MouseMove( const MouseEvent& rMEvt ) +{ + if (!mbTracking) + return true; + + DisableRepaint(); + + sal_Int32 nPos = (rMEvt.GetPosPixel().X() - GetFirstX()) / GetCharWidth() + GetFirstVisPos(); + // on mouse tracking: keep position valid + nPos = std::clamp( nPos, sal_Int32(0), GetPosCount() - 1 ); + Execute( CSVCMD_MAKEPOSVISIBLE, nPos ); + + sal_uInt32 nColIx = GetColumnFromPos( nPos ); + if( mnMTCurrCol != nColIx ) + { + DoSelectAction( nColIx, rMEvt.GetModifier() ); + mnMTCurrCol = nColIx; + } + EnableRepaint(); + + return true; +} + +bool ScCsvGrid::KeyInput( const KeyEvent& rKEvt ) +{ + const vcl::KeyCode& rKCode = rKEvt.GetKeyCode(); + sal_uInt16 nCode = rKCode.GetCode(); + bool bShift = rKCode.IsShift(); + bool bMod1 = rKCode.IsMod1(); + + if( !rKCode.IsMod2() ) + { + ScMoveMode eHDir = GetHorzDirection( nCode, !bMod1 ); + ScMoveMode eVDir = GetVertDirection( nCode, bMod1 ); + + if( eHDir != MOVE_NONE ) + { + DisableRepaint(); + MoveCursorRel( eHDir ); + if( !bMod1 ) + ImplClearSelection(); + if( bShift ) + SelectRange( mnRecentSelCol, GetFocusColumn() ); + else if( !bMod1 ) + Select( GetFocusColumn() ); + EnableRepaint(); + } + else if( eVDir != MOVE_NONE ) + ScrollVertRel( eVDir ); + else if( nCode == KEY_SPACE ) + { + if( !bMod1 ) + ImplClearSelection(); + if( bShift ) + SelectRange( mnRecentSelCol, GetFocusColumn() ); + else if( bMod1 ) + ToggleSelect( GetFocusColumn() ); + else + Select( GetFocusColumn() ); + } + else if( !bShift && bMod1 ) + { + if( nCode == KEY_A ) + SelectAll(); + else if( (KEY_1 <= nCode) && (nCode <= KEY_9) ) + { + sal_uInt32 nType = nCode - KEY_1; + if( nType < maTypeNames.size() ) + Execute( CSVCMD_SETCOLUMNTYPE, nType ); + } + } + } + + return rKCode.GetGroup() == KEYGROUP_CURSOR; +} + +bool ScCsvGrid::Command( const CommandEvent& rCEvt ) +{ + bool bConsumed = true; + switch( rCEvt.GetCommand() ) + { + case CommandEventId::ContextMenu: + { + if( rCEvt.IsMouseEvent() ) + { + Point aPos( rCEvt.GetMousePosPixel() ); + sal_uInt32 nColIx = GetColumnFromX( aPos.X() ); + if( IsValidColumn( nColIx ) && (GetFirstX() <= aPos.X()) && (aPos.X() <= GetLastX()) ) + { + if( !IsSelected( nColIx ) ) + DoSelectAction( nColIx, 0 ); // focus & select + ExecutePopup( aPos ); + } + } + else + { + sal_uInt32 nColIx = GetFocusColumn(); + if( !IsSelected( nColIx ) ) + Select( nColIx ); + sal_Int32 nX1 = std::max( GetColumnX( nColIx ), GetFirstX() ); + sal_Int32 nX2 = std::min( GetColumnX( nColIx + 1 ), GetWidth() ); + ExecutePopup( Point( (nX1 + nX2) / 2, GetHeight() / 2 ) ); + } + break; + } + case CommandEventId::Wheel: + { + tools::Rectangle aRect( Point(), maWinSize ); + if( aRect.Contains( rCEvt.GetMousePosPixel() ) ) + { + const CommandWheelData* pData = rCEvt.GetWheelData(); + if( pData && (pData->GetMode() == CommandWheelMode::SCROLL) && !pData->IsHorz() ) + Execute( CSVCMD_SETLINEOFFSET, GetFirstVisLine() - pData->GetNotchDelta() ); + } + break; + } + default: + bConsumed = false; + break; + } + return bConsumed; +} + +void ScCsvGrid::StyleUpdated() +{ + InitColors(); + InitFonts(); + UpdateLayoutData(); + Execute( CSVCMD_UPDATECELLTEXTS ); + + ScCsvControl::StyleUpdated(); +} + +void ScCsvGrid::ConfigurationChanged( utl::ConfigurationBroadcaster*, ConfigurationHints ) +{ + InitColors(); + Repaint(); +} + +// painting ------------------------------------------------------------------- + +void ScCsvGrid::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + ImplRedraw(rRenderContext); +} + +void ScCsvGrid::ImplRedraw(vcl::RenderContext& rRenderContext) +{ + if( IsVisible() ) + { + if( !IsValidGfx() ) + { + ValidateGfx(); + ImplDrawBackgrDev(); + ImplDrawGridDev(); + } + rRenderContext.DrawOutDev( Point(), maWinSize, Point(), maWinSize, *mpGridDev ); + } +} + +EditEngine* ScCsvGrid::GetEditEngine() +{ + return mpEditEngine.get(); +} + +void ScCsvGrid::ImplSetColumnClipRegion( OutputDevice& rOutDev, sal_uInt32 nColIndex ) +{ + rOutDev.SetClipRegion( vcl::Region( tools::Rectangle( + std::max( GetColumnX( nColIndex ), GetFirstX() ) + 1, 0, + std::min( GetColumnX( nColIndex + 1 ), GetLastX() ), GetHeight() - 1 ) ) ); +} + +void ScCsvGrid::ImplDrawColumnHeader( OutputDevice& rOutDev, sal_uInt32 nColIndex, Color aFillColor ) +{ + sal_Int32 nX1 = GetColumnX( nColIndex ) + 1; + sal_Int32 nX2 = GetColumnX( nColIndex + 1 ); + sal_Int32 nHdrHt = GetHdrHeight(); + + rOutDev.SetLineColor(); + rOutDev.SetFillColor( aFillColor ); + rOutDev.DrawRect( tools::Rectangle( nX1, 0, nX2, nHdrHt ) ); + + rOutDev.SetFont( maHeaderFont ); + rOutDev.SetTextColor( maHeaderTextColor ); + rOutDev.SetTextFillColor(); + rOutDev.DrawText( Point( nX1 + 1, 0 ), GetColumnTypeName( nColIndex ) ); + + rOutDev.SetLineColor( maHeaderGridColor ); + rOutDev.DrawLine( Point( nX1, nHdrHt ), Point( nX2, nHdrHt ) ); + rOutDev.DrawLine( Point( nX2, 0 ), Point( nX2, nHdrHt ) ); +} + +void ScCsvGrid::ImplDrawCellText( const Point& rPos, const OUString& rText ) +{ + OUString aPlainText = rText.replaceAll( "\t", " " ); + aPlainText = aPlainText.replaceAll( "\n", " " ); + mpEditEngine->SetPaperSize( maEdEngSize ); + mpEditEngine->SetTextCurrentDefaults(aPlainText); + mpEditEngine->Draw(*mpBackgrDev, rPos); + + sal_Int32 nCharIx = 0; + while( (nCharIx = rText.indexOf( '\t', nCharIx )) != -1 ) + { + sal_Int32 nX1 = rPos.X() + GetCharWidth() * nCharIx; + sal_Int32 nX2 = nX1 + GetCharWidth() - 2; + sal_Int32 nY = rPos.Y() + GetLineHeight() / 2; + Color aColor( maTextColor ); + mpBackgrDev->SetLineColor( aColor ); + mpBackgrDev->DrawLine( Point( nX1, nY ), Point( nX2, nY ) ); + mpBackgrDev->DrawLine( Point( nX2 - 2, nY - 2 ), Point( nX2, nY ) ); + mpBackgrDev->DrawLine( Point( nX2 - 2, nY + 2 ), Point( nX2, nY ) ); + ++nCharIx; + } + nCharIx = 0; + while( (nCharIx = rText.indexOf( '\n', nCharIx )) != -1 ) + { + sal_Int32 nX1 = rPos.X() + GetCharWidth() * nCharIx; + sal_Int32 nX2 = nX1 + GetCharWidth() - 2; + sal_Int32 nY = rPos.Y() + GetLineHeight() / 2; + Color aColor( maTextColor ); + mpBackgrDev->SetLineColor( aColor ); + mpBackgrDev->DrawLine( Point( nX1, nY ), Point( nX2, nY ) ); + mpBackgrDev->DrawLine( Point( nX1 + 2, nY - 2 ), Point( nX1, nY ) ); + mpBackgrDev->DrawLine( Point( nX1 + 2, nY + 2 ), Point( nX1, nY ) ); + mpBackgrDev->DrawLine( Point( nX2, nY - 2 ), Point( nX2, nY ) ); + ++nCharIx; + } +} + +void ScCsvGrid::ImplDrawFirstLineSep( bool bSet ) +{ + if( IsVisibleLine( mnFirstImpLine ) && (mnFirstImpLine != GetFirstVisLine() ) ) + { + sal_Int32 nY = GetY( mnFirstImpLine ); + sal_Int32 nX = std::min( GetColumnX( GetLastVisColumn() + 1 ), GetLastX() ); + mpBackgrDev->SetLineColor( bSet ? maGridPBColor : maGridColor ); + mpBackgrDev->DrawLine( Point( GetFirstX() + 1, nY ), Point( nX, nY ) ); + } +} + +void ScCsvGrid::ImplDrawColumnBackgr( sal_uInt32 nColIndex ) +{ + if( !IsVisibleColumn( nColIndex ) ) + return; + + ImplSetColumnClipRegion( *mpBackgrDev, nColIndex ); + + // grid + mpBackgrDev->SetLineColor(); + mpBackgrDev->SetFillColor( maBackColor ); + sal_Int32 nX1 = GetColumnX( nColIndex ) + 1; + sal_Int32 nX2 = GetColumnX( nColIndex + 1 ); + sal_Int32 nY2 = GetY( GetLastVisLine() + 1 ); + sal_Int32 nHdrHt = GetHdrHeight(); + tools::Rectangle aRect( nX1, nHdrHt, nX2, nY2 ); + mpBackgrDev->DrawRect( aRect ); + mpBackgrDev->SetLineColor( maGridColor ); + mpBackgrDev->DrawGrid( aRect, Size( 1, GetLineHeight() ), DrawGridFlags::HorzLines ); + mpBackgrDev->DrawLine( Point( nX2, nHdrHt ), Point( nX2, nY2 ) ); + ImplDrawFirstLineSep( true ); + + // cell texts + mpEditEngine->SetDefaultItem( SvxColorItem( maTextColor, EE_CHAR_COLOR ) ); + size_t nLineCount = ::std::min( static_cast< size_t >( GetLastVisLine() - GetFirstVisLine() + 1 ), maTexts.size() ); + // #i67432# cut string to avoid edit engine performance problems with very large strings + sal_Int32 nFirstVisPos = ::std::max( GetColumnPos( nColIndex ), GetFirstVisPos() ); + sal_Int32 nLastVisPos = ::std::min( GetColumnPos( nColIndex + 1 ), GetLastVisPos() ); + sal_Int32 nStrPos = nFirstVisPos - GetColumnPos( nColIndex ); + sal_Int32 nStrLen = nLastVisPos - nFirstVisPos + 1; + sal_Int32 nStrX = GetX( nFirstVisPos ); + for( size_t nLine = 0; nLine < nLineCount; ++nLine ) + { + std::vector<OUString>& rStrVec = maTexts[ nLine ]; + if( (nColIndex < rStrVec.size()) && (rStrVec[ nColIndex ].getLength() > nStrPos) ) + { + const OUString& rStr = rStrVec[ nColIndex ]; + OUString aText = rStr.copy( nStrPos, ::std::min( nStrLen, rStr.getLength() - nStrPos) ); + ImplDrawCellText( Point( nStrX, GetY( GetFirstVisLine() + nLine ) ), aText ); + } + } + + // header + ImplDrawColumnHeader( *mpBackgrDev, nColIndex, maHeaderBackColor ); + + mpBackgrDev->SetClipRegion(); +} + +void ScCsvGrid::ImplDrawRowHeaders() +{ + mpBackgrDev->SetLineColor(); + mpBackgrDev->SetFillColor( maAppBackColor ); + Point aPoint( GetHdrX(), 0 ); + tools::Rectangle aRect( aPoint, Size( GetHdrWidth() + 1, GetHeight() ) ); + mpBackgrDev->DrawRect( aRect ); + + mpBackgrDev->SetFillColor( maHeaderBackColor ); + aRect.SetBottom( GetY( GetLastVisLine() + 1 ) ); + mpBackgrDev->DrawRect( aRect ); + + // line numbers + mpBackgrDev->SetFont( maHeaderFont ); + mpBackgrDev->SetTextColor( maHeaderTextColor ); + mpBackgrDev->SetTextFillColor(); + sal_Int32 nLastLine = GetLastVisLine(); + for( sal_Int32 nLine = GetFirstVisLine(); nLine <= nLastLine; ++nLine ) + { + OUString aText( OUString::number( nLine + 1 ) ); + sal_Int32 nX = GetHdrX() + (GetHdrWidth() - mpBackgrDev->GetTextWidth( aText )) / 2; + mpBackgrDev->DrawText( Point( nX, GetY( nLine ) ), aText ); + } + + // grid + mpBackgrDev->SetLineColor( maHeaderGridColor ); + if( IsRTL() ) + { + mpBackgrDev->DrawLine( Point( 0, 0 ), Point( 0, GetHeight() - 1 ) ); + mpBackgrDev->DrawLine( aRect.TopLeft(), aRect.BottomLeft() ); + } + else + mpBackgrDev->DrawLine( aRect.TopRight(), aRect.BottomRight() ); + aRect.SetTop( GetHdrHeight() ); + mpBackgrDev->DrawGrid( aRect, Size( 1, GetLineHeight() ), DrawGridFlags::HorzLines ); +} + +void ScCsvGrid::ImplDrawBackgrDev() +{ + mpBackgrDev->SetLineColor(); + mpBackgrDev->SetFillColor( maAppBackColor ); + mpBackgrDev->DrawRect( tools::Rectangle( + Point( GetFirstX() + 1, 0 ), Size( GetWidth() - GetHdrWidth(), GetHeight() ) ) ); + + sal_uInt32 nLastCol = GetLastVisColumn(); + if (nLastCol == CSV_COLUMN_INVALID) + return; + for( sal_uInt32 nColIx = GetFirstVisColumn(); nColIx <= nLastCol; ++nColIx ) + ImplDrawColumnBackgr( nColIx ); + + ImplDrawRowHeaders(); +} + +void ScCsvGrid::ImplDrawColumnSelection( sal_uInt32 nColIndex ) +{ + ImplInvertCursor( GetRulerCursorPos() ); + ImplSetColumnClipRegion( *mpGridDev, nColIndex ); + mpGridDev->DrawOutDev( Point(), maWinSize, Point(), maWinSize, *mpBackgrDev ); + + if( IsSelected( nColIndex ) ) + { + sal_Int32 nX1 = GetColumnX( nColIndex ) + 1; + sal_Int32 nX2 = GetColumnX( nColIndex + 1 ); + + // header + tools::Rectangle aRect( nX1, 0, nX2, GetHdrHeight() ); + mpGridDev->SetLineColor(); + if( maHeaderBackColor.IsDark() ) + // redraw with light gray background in dark mode + ImplDrawColumnHeader( *mpGridDev, nColIndex, COL_LIGHTGRAY ); + else + { + // use transparent active color + mpGridDev->SetFillColor( maSelectColor ); + mpGridDev->DrawTransparent( tools::PolyPolygon( tools::Polygon( aRect ) ), CSV_HDR_TRANSPARENCY ); + } + + // column selection + aRect = tools::Rectangle( nX1, GetHdrHeight() + 1, nX2, GetY( GetLastVisLine() + 1 ) - 1 ); + ImplInvertRect( *mpGridDev, aRect ); + } + + mpGridDev->SetClipRegion(); + ImplInvertCursor( GetRulerCursorPos() ); +} + +void ScCsvGrid::ImplDrawGridDev() +{ + mpGridDev->DrawOutDev( Point(), maWinSize, Point(), maWinSize, *mpBackgrDev ); + sal_uInt32 nLastCol = GetLastVisColumn(); + if (nLastCol == CSV_COLUMN_INVALID) + return; + for( sal_uInt32 nColIx = GetFirstVisColumn(); nColIx <= nLastCol; ++nColIx ) + ImplDrawColumnSelection( nColIx ); +} + +void ScCsvGrid::ImplDrawColumn( sal_uInt32 nColIndex ) +{ + ImplDrawColumnBackgr( nColIndex ); + ImplDrawColumnSelection( nColIndex ); +} + +void ScCsvGrid::ImplDrawHorzScrolled( sal_Int32 nOldPos ) +{ + sal_Int32 nPos = GetFirstVisPos(); + if( !IsValidGfx() || (nPos == nOldPos) ) + return; + if( std::abs( nPos - nOldPos ) > GetVisPosCount() / 2 ) + { + ImplDrawBackgrDev(); + ImplDrawGridDev(); + return; + } + + Point aSrc, aDest; + sal_uInt32 nFirstColIx, nLastColIx; + if( nPos < nOldPos ) + { + aSrc = Point( GetFirstX() + 1, 0 ); + aDest = Point( GetFirstX() + GetCharWidth() * (nOldPos - nPos) + 1, 0 ); + nFirstColIx = GetColumnFromPos( nPos ); + nLastColIx = GetColumnFromPos( nOldPos ); + } + else + { + aSrc = Point( GetFirstX() + GetCharWidth() * (nPos - nOldPos) + 1, 0 ); + aDest = Point( GetFirstX() + 1, 0 ); + nFirstColIx = GetColumnFromPos( std::min( nOldPos + GetVisPosCount(), GetPosCount() ) - 1 ); + nLastColIx = GetColumnFromPos( std::min( nPos + GetVisPosCount(), GetPosCount() ) - 1 ); + } + + ImplInvertCursor( GetRulerCursorPos() + (nPos - nOldPos) ); + tools::Rectangle aRectangle( GetFirstX(), 0, GetLastX(), GetHeight() - 1 ); + vcl::Region aClipReg( aRectangle ); + mpBackgrDev->SetClipRegion( aClipReg ); + mpBackgrDev->CopyArea( aDest, aSrc, maWinSize ); + mpBackgrDev->SetClipRegion(); + mpGridDev->SetClipRegion( aClipReg ); + mpGridDev->CopyArea( aDest, aSrc, maWinSize ); + mpGridDev->SetClipRegion(); + ImplInvertCursor( GetRulerCursorPos() ); + + for( sal_uInt32 nColIx = nFirstColIx; nColIx <= nLastColIx; ++nColIx ) + ImplDrawColumn( nColIx ); + + sal_Int32 nLastX = GetX( GetPosCount() ) + 1; + if( nLastX <= GetLastX() ) + { + tools::Rectangle aRect( nLastX, 0, GetLastX(), GetHeight() - 1 ); + mpBackgrDev->SetLineColor(); + mpBackgrDev->SetFillColor( maAppBackColor ); + mpBackgrDev->DrawRect( aRect ); + mpGridDev->SetLineColor(); + mpGridDev->SetFillColor( maAppBackColor ); + mpGridDev->DrawRect( aRect ); + } +} + +void ScCsvGrid::ImplInvertCursor( sal_Int32 nPos ) +{ + if( IsVisibleSplitPos( nPos ) ) + { + sal_Int32 nX = GetX( nPos ) - 1; + tools::Rectangle aRect( Point( nX, 0 ), Size( 3, GetHdrHeight() ) ); + ImplInvertRect( *mpGridDev, aRect ); + aRect.SetTop( GetHdrHeight() + 1 ); + aRect.SetBottom( GetY( GetLastVisLine() + 1 ) ); + ImplInvertRect( *mpGridDev, aRect ); + } +} + +tools::Rectangle ScCsvGrid::GetFocusRect() +{ + auto nColIndex = GetFocusColumn(); + if( HasFocus() && IsVisibleColumn( nColIndex ) ) + { + sal_Int32 nX1 = std::max( GetColumnX( nColIndex ), GetFirstX() ) + 1; + sal_Int32 nX2 = std::min( GetColumnX( nColIndex + 1 ) - sal_Int32( 1 ), GetLastX() ); + sal_Int32 nY2 = std::min( GetY( GetLastVisLine() + 1 ), GetHeight() ) - 1; + return tools::Rectangle( nX1, 0, nX2, nY2 ); + } + return weld::CustomWidgetController::GetFocusRect(); +} + +// accessibility ============================================================== + +css::uno::Reference<css::accessibility::XAccessible> ScCsvGrid::CreateAccessible() +{ + rtl::Reference<ScAccessibleCsvGrid> xRef(new ScAccessibleCsvGrid(*this)); + mxAccessible = xRef; + return xRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/csvruler.cxx b/sc/source/ui/dbgui/csvruler.cxx new file mode 100644 index 0000000000..b900d3491c --- /dev/null +++ b/sc/source/ui/dbgui/csvruler.cxx @@ -0,0 +1,665 @@ +/* -*- 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 <csvruler.hxx> +#include <AccessibleCsvControl.hxx> + +#include <optutil.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <o3tl/string_view.hxx> + +using namespace com::sun::star::uno; + +constexpr OUString SEP_PATH = u"Office.Calc/Dialogs/CSVImport"_ustr; +constexpr OUString FIXED_WIDTH_LIST = u"FixedWidthList"_ustr; + +static void load_FixedWidthList(ScCsvSplits &rSplits) +{ + Sequence<Any>aValues; + const Any *pProperties; + Sequence<OUString> aNames { FIXED_WIDTH_LIST }; + ScLinkConfigItem aItem( SEP_PATH ); + + aValues = aItem.GetProperties( aNames ); + pProperties = aValues.getConstArray(); + + if( !pProperties[0].hasValue() ) + return; + + rSplits.Clear(); + + OUString sFixedWidthLists; + pProperties[0] >>= sFixedWidthLists; + + sal_Int32 nIdx {0}; + for(;;) + { + const sal_Int32 n = o3tl::toInt32(o3tl::getToken(sFixedWidthLists, 0, ';', nIdx)); + if (nIdx<0) + { + // String ends with a semi-colon so there + // is no useful 'int' after the last one. + // This also works in case of empty string + break; + } + rSplits.Insert(n); + } +} +static void save_FixedWidthList(const ScCsvSplits& rSplits) +{ + OUStringBuffer sSplits; + // Create a semi-colon separated string to save the splits + sal_uInt32 n = rSplits.Count(); + for (sal_uInt32 i = 0; i < n; ++i) + { + sSplits.append(OUString::number(rSplits[i]) + ";"); + } + + OUString sFixedWidthLists = sSplits.makeStringAndClear(); + Sequence<Any> aValues; + Any *pProperties; + Sequence<OUString> aNames { FIXED_WIDTH_LIST }; + ScLinkConfigItem aItem( SEP_PATH ); + + aValues = aItem.GetProperties( aNames ); + pProperties = aValues.getArray(); + pProperties[0] <<= sFixedWidthLists; + + aItem.PutProperties(aNames, aValues); +} + +ScCsvRuler::ScCsvRuler(const ScCsvLayoutData& rData, ScCsvTableBox* pTableBox) + : ScCsvControl(rData) + , mpTableBox(pTableBox) + , mnPosCursorLast(1) + , mnPosMTStart(0) + , mnPosMTCurr(0) + , mbPosMTMoved(false) + , mnSplitSize(0) + , mbTracking(false) +{ +} + +void ScCsvRuler::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + ScCsvControl::SetDrawingArea(pDrawingArea); + + UpdateSplitSize(); + + Size aSize(1, GetTextHeight() + mnSplitSize + 2); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); + SetOutputSizePixel(aSize); + + EnableRTL( false ); // RTL + InitColors(); + InitSizeData(); + + OutputDevice& rRefDevice = pDrawingArea->get_ref_device(); + maBackgrDev->SetFont( rRefDevice.GetFont() ); + maRulerDev->SetFont( rRefDevice.GetFont() ); + load_FixedWidthList( maSplits ); +} + +ScCsvRuler::~ScCsvRuler() +{ + save_FixedWidthList( maSplits ); +} + +// common ruler handling ------------------------------------------------------ + +void ScCsvRuler::ApplyLayout( const ScCsvLayoutData& rOldData ) +{ + ScCsvDiff nDiff = GetLayoutData().GetDiff( rOldData ) & (ScCsvDiff::HorizontalMask | ScCsvDiff::RulerCursor); + if( nDiff == ScCsvDiff::Equal ) return; + + DisableRepaint(); + if( nDiff & ScCsvDiff::HorizontalMask ) + { + InitSizeData(); + if( GetRulerCursorPos() >= GetPosCount() ) + MoveCursor( GetPosCount() - 1 ); + } + if( nDiff & ScCsvDiff::RulerCursor ) + { + ImplInvertCursor( rOldData.mnPosCursor ); + ImplInvertCursor( GetRulerCursorPos() ); + } + EnableRepaint(); + + if( nDiff & ScCsvDiff::PosOffset ) + AccSendVisibleEvent(); +} + +void ScCsvRuler::InitColors() +{ + const StyleSettings& rSett = Application::GetSettings().GetStyleSettings(); + maBackColor = rSett.GetFaceColor(); + maActiveColor = rSett.GetWindowColor(); + maTextColor = rSett.GetLabelTextColor(); + maSplitColor = maBackColor.IsDark() ? maTextColor : COL_LIGHTRED; + InvalidateGfx(); +} + +void ScCsvRuler::UpdateSplitSize() +{ + mnSplitSize = (GetCharWidth() * 3 / 5) | 1; // make an odd number +} + +void ScCsvRuler::InitSizeData() +{ + maWinSize = GetOutputSizePixel(); + + UpdateSplitSize(); + + sal_Int32 nActiveWidth = std::min( GetWidth() - GetHdrWidth(), GetPosCount() * GetCharWidth() ); + sal_Int32 nActiveHeight = GetTextHeight(); + + maActiveRect.SetPos( Point( GetFirstX(), (GetHeight() - nActiveHeight - 1) / 2 ) ); + maActiveRect.SetSize( Size( nActiveWidth, nActiveHeight ) ); + + maBackgrDev->SetOutputSizePixel( maWinSize ); + maRulerDev->SetOutputSizePixel( maWinSize ); + + InvalidateGfx(); +} + +void ScCsvRuler::MoveCursor( sal_Int32 nPos, bool bScroll ) +{ + DisableRepaint(); + if( bScroll ) + Execute( CSVCMD_MAKEPOSVISIBLE, nPos ); + Execute( CSVCMD_MOVERULERCURSOR, IsVisibleSplitPos( nPos ) ? nPos : CSV_POS_INVALID ); + EnableRepaint(); + AccSendCaretEvent(); +} + +void ScCsvRuler::MoveCursorRel( ScMoveMode eDir ) +{ + if( GetRulerCursorPos() == CSV_POS_INVALID ) + return; + + switch( eDir ) + { + case MOVE_FIRST: + MoveCursor( 1 ); + break; + case MOVE_LAST: + MoveCursor( GetPosCount() - 1 ); + break; + case MOVE_PREV: + if( GetRulerCursorPos() > 1 ) + MoveCursor( GetRulerCursorPos() - 1 ); + break; + case MOVE_NEXT: + if( GetRulerCursorPos() < GetPosCount() - 1 ) + MoveCursor( GetRulerCursorPos() + 1 ); + break; + default: + { + // added to avoid warnings + } + } +} + +void ScCsvRuler::MoveCursorToSplit( ScMoveMode eDir ) +{ + if( GetRulerCursorPos() == CSV_POS_INVALID ) + return; + + sal_uInt32 nIndex = CSV_VEC_NOTFOUND; + switch( eDir ) + { + case MOVE_FIRST: nIndex = maSplits.LowerBound( 0 ); break; + case MOVE_LAST: nIndex = maSplits.UpperBound( GetPosCount() ); break; + case MOVE_PREV: nIndex = maSplits.UpperBound( GetRulerCursorPos() - 1 ); break; + case MOVE_NEXT: nIndex = maSplits.LowerBound( GetRulerCursorPos() + 1 ); break; + default: + { + // added to avoid warnings + } + } + sal_Int32 nPos = maSplits[ nIndex ]; + if( nPos != CSV_POS_INVALID ) + MoveCursor( nPos ); +} + +void ScCsvRuler::ScrollVertRel( ScMoveMode eDir ) +{ + sal_Int32 nLine = GetFirstVisLine(); + switch( eDir ) + { + case MOVE_PREV: --nLine; break; + case MOVE_NEXT: ++nLine; break; + case MOVE_PREVPAGE: nLine -= GetVisLineCount() - 1; break; + case MOVE_NEXTPAGE: nLine += GetVisLineCount() - 1; break; + default: + { + // added to avoid warnings + } + } + Execute( CSVCMD_SETLINEOFFSET, nLine ); +} + +// split handling ------------------------------------------------------------- + +sal_Int32 ScCsvRuler::GetNoScrollPos( sal_Int32 nPos ) const +{ + sal_Int32 nNewPos = nPos; + if( nNewPos != CSV_POS_INVALID ) + { + if( nNewPos < GetFirstVisPos() + CSV_SCROLL_DIST ) + { + sal_Int32 nScroll = (GetFirstVisPos() > 0) ? CSV_SCROLL_DIST : 0; + nNewPos = std::max( nPos, GetFirstVisPos() + nScroll ); + } + else if( nNewPos > GetLastVisPos() - CSV_SCROLL_DIST - 1 ) + { + sal_Int32 nScroll = (GetFirstVisPos() < GetMaxPosOffset()) ? CSV_SCROLL_DIST : 0; + nNewPos = std::min( nNewPos, GetLastVisPos() - nScroll - sal_Int32( 1 ) ); + } + } + return nNewPos; +} + +void ScCsvRuler::InsertSplit( sal_Int32 nPos ) +{ + if( maSplits.Insert( nPos ) ) + { + ImplDrawSplit( nPos ); + Repaint(); + } +} + +void ScCsvRuler::RemoveSplit( sal_Int32 nPos ) +{ + if( maSplits.Remove( nPos ) ) + { + ImplEraseSplit( nPos ); + Repaint(); + } +} + +void ScCsvRuler::MoveSplit( sal_Int32 nPos, sal_Int32 nNewPos ) +{ + bool bRemove = maSplits.Remove( nPos ); + bool bInsert = maSplits.Insert( nNewPos ); + if( bRemove || bInsert ) + { + ImplEraseSplit( nPos ); + ImplDrawSplit( nNewPos ); + Repaint(); + } +} + +void ScCsvRuler::RemoveAllSplits() +{ + maSplits.Clear(); + Repaint( true ); +} + +sal_Int32 ScCsvRuler::FindEmptyPos( sal_Int32 nPos, ScMoveMode eDir ) const +{ + sal_Int32 nNewPos = nPos; + if( nNewPos != CSV_POS_INVALID ) + { + switch( eDir ) + { + case MOVE_FIRST: + nNewPos = std::min( nPos, FindEmptyPos( 0, MOVE_NEXT ) ); + break; + case MOVE_LAST: + nNewPos = std::max( nPos, FindEmptyPos( GetPosCount(), MOVE_PREV ) ); + break; + case MOVE_PREV: + while( HasSplit( --nNewPos ) ) ; + break; + case MOVE_NEXT: + while( HasSplit( ++nNewPos ) ) ; + break; + default: + { + // added to avoid warnings + } + } + } + return IsValidSplitPos( nNewPos ) ? nNewPos : CSV_POS_INVALID; +} + +void ScCsvRuler::MoveCurrSplit( sal_Int32 nNewPos ) +{ + DisableRepaint(); + Execute( CSVCMD_MOVESPLIT, GetRulerCursorPos(), nNewPos ); + MoveCursor( nNewPos ); + EnableRepaint(); +} + +void ScCsvRuler::MoveCurrSplitRel( ScMoveMode eDir ) +{ + if( HasSplit( GetRulerCursorPos() ) ) + { + sal_Int32 nNewPos = FindEmptyPos( GetRulerCursorPos(), eDir ); + if( nNewPos != CSV_POS_INVALID ) + MoveCurrSplit( nNewPos ); + } +} + +// event handling ------------------------------------------------------------- + +void ScCsvRuler::Resize() +{ + ScCsvControl::Resize(); + InitSizeData(); + Repaint(); +} + +void ScCsvRuler::GetFocus() +{ + ScCsvControl::GetFocus(); + DisableRepaint(); + if( GetRulerCursorPos() == CSV_POS_INVALID ) + MoveCursor( GetNoScrollPos( mnPosCursorLast ) ); + EnableRepaint(); +} + +void ScCsvRuler::LoseFocus() +{ + ScCsvControl::LoseFocus(); + mnPosCursorLast = GetRulerCursorPos(); + MoveCursor( CSV_POS_INVALID ); +} + +void ScCsvRuler::StyleUpdated() +{ + InitColors(); + Repaint(); + + ScCsvControl::StyleUpdated(); +} + +bool ScCsvRuler::MouseButtonDown( const MouseEvent& rMEvt ) +{ + DisableRepaint(); + if( !HasFocus() ) + GrabFocus(); + if( rMEvt.IsLeft() ) + { + sal_Int32 nPos = GetPosFromX( rMEvt.GetPosPixel().X() ); + if( IsVisibleSplitPos( nPos ) ) + StartMouseTracking( nPos ); + ImplSetMousePointer( nPos ); + } + EnableRepaint(); + return true; +} + +bool ScCsvRuler::MouseButtonUp( const MouseEvent& ) +{ + if (mbTracking) + { + EndMouseTracking(); + mbTracking = false; + } + return true; +} + +bool ScCsvRuler::MouseMove( const MouseEvent& rMEvt ) +{ + if( !rMEvt.IsModifierChanged() ) + { + sal_Int32 nPos = GetPosFromX( rMEvt.GetPosPixel().X() ); + if( mbTracking ) + { + // on mouse tracking: keep position valid + nPos = std::clamp( nPos, sal_Int32(1), GetPosCount() - 1 ); + MoveMouseTracking( nPos ); + } + else + { + tools::Rectangle aRect( Point(), maWinSize ); + if( !IsVisibleSplitPos( nPos ) || !aRect.Contains( rMEvt.GetPosPixel() ) ) + // if focused, keep old cursor position for key input + nPos = HasFocus() ? GetRulerCursorPos() : CSV_POS_INVALID; + MoveCursor( nPos, false ); + } + ImplSetMousePointer( nPos ); + } + return true; +} + +bool ScCsvRuler::KeyInput( const KeyEvent& rKEvt ) +{ + const vcl::KeyCode& rKCode = rKEvt.GetKeyCode(); + sal_uInt16 nCode = rKCode.GetCode(); + bool bNoMod = !rKCode.GetModifier(); + bool bShift = (rKCode.GetModifier() == KEY_SHIFT); + bool bJump = (rKCode.GetModifier() == KEY_MOD1); + bool bMove = (rKCode.GetModifier() == (KEY_MOD1 | KEY_SHIFT)); + + ScMoveMode eHDir = GetHorzDirection( nCode, true ); + ScMoveMode eVDir = GetVertDirection( nCode, false ); + + if( bNoMod ) + { + if( eHDir != MOVE_NONE ) + MoveCursorRel( eHDir ); + else if( eVDir != MOVE_NONE ) + ScrollVertRel( eVDir ); + else switch( nCode ) + { + case KEY_SPACE: Execute( CSVCMD_TOGGLESPLIT, GetRulerCursorPos() ); break; + case KEY_INSERT: Execute( CSVCMD_INSERTSPLIT, GetRulerCursorPos() ); break; + case KEY_DELETE: Execute( CSVCMD_REMOVESPLIT, GetRulerCursorPos() ); break; + } + } + else if( bJump && (eHDir != MOVE_NONE) ) + MoveCursorToSplit( eHDir ); + else if( bMove && (eHDir != MOVE_NONE) ) + MoveCurrSplitRel( eHDir ); + else if( bShift && (nCode == KEY_DELETE) ) + Execute( CSVCMD_REMOVEALLSPLITS ); + + return rKCode.GetGroup() == KEYGROUP_CURSOR; +} + +void ScCsvRuler::StartMouseTracking( sal_Int32 nPos ) +{ + mnPosMTStart = mnPosMTCurr = nPos; + mbPosMTMoved = false; + maOldSplits = maSplits; + Execute( CSVCMD_INSERTSPLIT, nPos ); + if( HasSplit( nPos ) ) + mbTracking = true; +} + +void ScCsvRuler::MoveMouseTracking( sal_Int32 nPos ) +{ + if( mnPosMTCurr != nPos ) + { + DisableRepaint(); + MoveCursor( nPos ); + if( (mnPosMTCurr != mnPosMTStart) && maOldSplits.HasSplit( mnPosMTCurr ) ) + Execute( CSVCMD_INSERTSPLIT, nPos ); + else + Execute( CSVCMD_MOVESPLIT, mnPosMTCurr, nPos ); + mnPosMTCurr = nPos; + mbPosMTMoved = true; + EnableRepaint(); + } +} + +void ScCsvRuler::EndMouseTracking() +{ + // remove on simple click on an existing split + if( (mnPosMTCurr == mnPosMTStart) && maOldSplits.HasSplit( mnPosMTCurr ) && !mbPosMTMoved ) + Execute( CSVCMD_REMOVESPLIT, mnPosMTCurr ); + mnPosMTStart = CSV_POS_INVALID; +} + +// painting ------------------------------------------------------------------- + +void ScCsvRuler::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& ) +{ + ImplRedraw(rRenderContext); +} + +void ScCsvRuler::ImplRedraw(vcl::RenderContext& rRenderContext) +{ + if( IsVisible() ) + { + if( !IsValidGfx() ) + { + ValidateGfx(); + ImplDrawBackgrDev(); + ImplDrawRulerDev(); + } + rRenderContext.DrawOutDev( Point(), maWinSize, Point(), maWinSize, *maRulerDev ); + } +} + +tools::Rectangle ScCsvRuler::GetFocusRect() +{ + /* Draws directly tracking rectangle to the column with the specified index. */ + if(HasFocus()) + return tools::Rectangle(0, 0, GetWidth() - 1, GetHeight() - 2); + return weld::CustomWidgetController::GetFocusRect(); +} + +void ScCsvRuler::ImplDrawArea( sal_Int32 nPosX, sal_Int32 nWidth ) +{ + maBackgrDev->SetLineColor(); + tools::Rectangle aRect( Point( nPosX, 0 ), Size( nWidth, GetHeight() ) ); + maBackgrDev->SetFillColor( maBackColor ); + maBackgrDev->DrawRect( aRect ); + + aRect = maActiveRect; + aRect.SetLeft( std::max( GetFirstX(), nPosX ) ); + aRect.SetRight( std::min( std::min( GetX( GetPosCount() ), GetLastX() ), nPosX + nWidth - sal_Int32( 1 ) ) ); + if( aRect.Left() <= aRect.Right() ) + { + maBackgrDev->SetFillColor( maActiveColor ); + maBackgrDev->DrawRect( aRect ); + } + + maBackgrDev->SetLineColor( maTextColor ); + sal_Int32 nY = GetHeight() - 1; + maBackgrDev->DrawLine( Point( nPosX, nY ), Point( nPosX + nWidth - 1, nY ) ); +} + +void ScCsvRuler::ImplDrawBackgrDev() +{ + ImplDrawArea( 0, GetWidth() ); + + // scale + maBackgrDev->SetLineColor( maTextColor ); + maBackgrDev->SetFillColor(); + sal_Int32 nPos; + + sal_Int32 nFirstPos = std::max( GetPosFromX( 0 ) - 1, sal_Int32(0) ); + sal_Int32 nLastPos = GetPosFromX( GetWidth() ); + sal_Int32 nY = (maActiveRect.Top() + maActiveRect.Bottom()) / 2; + for( nPos = nFirstPos; nPos <= nLastPos; ++nPos ) + { + sal_Int32 nX = GetX( nPos ); + if( nPos % 5 ) + maBackgrDev->DrawPixel( Point( nX, nY ) ); + else + maBackgrDev->DrawLine( Point( nX, nY - 1 ), Point( nX, nY + 1 ) ); + } + + // texts + maBackgrDev->SetTextColor( maTextColor ); + maBackgrDev->SetTextFillColor(); + for( nPos = ((nFirstPos + 9) / 10) * 10; nPos <= nLastPos; nPos += 10 ) + { + OUString aText( OUString::number( nPos ) ); + sal_Int32 nTextWidth = maBackgrDev->GetTextWidth( aText ); + sal_Int32 nTextX = GetX( nPos ) - nTextWidth / 2; + ImplDrawArea( nTextX - 1, nTextWidth + 2 ); + maBackgrDev->DrawText( Point( nTextX, maActiveRect.Top() ), aText ); + } +} + +void ScCsvRuler::ImplDrawSplit( sal_Int32 nPos ) +{ + if( IsVisibleSplitPos( nPos ) ) + { + Point aPos( GetX( nPos ) - mnSplitSize / 2, GetHeight() - mnSplitSize - 2 ); + Size aSize( mnSplitSize, mnSplitSize ); + maRulerDev->SetLineColor( maTextColor ); + maRulerDev->SetFillColor( maSplitColor ); + maRulerDev->DrawEllipse( tools::Rectangle( aPos, aSize ) ); + maRulerDev->DrawPixel( Point( GetX( nPos ), GetHeight() - 2 ) ); + } +} + +void ScCsvRuler::ImplEraseSplit( sal_Int32 nPos ) +{ + if( IsVisibleSplitPos( nPos ) ) + { + ImplInvertCursor( GetRulerCursorPos() ); + Point aPos( GetX( nPos ) - mnSplitSize / 2, 0 ); + Size aSize( mnSplitSize, GetHeight() ); + maRulerDev->DrawOutDev( aPos, aSize, aPos, aSize, *maBackgrDev ); + ImplInvertCursor( GetRulerCursorPos() ); + } +} + +void ScCsvRuler::ImplDrawRulerDev() +{ + maRulerDev->DrawOutDev( Point(), maWinSize, Point(), maWinSize, *maBackgrDev ); + ImplInvertCursor( GetRulerCursorPos() ); + + sal_uInt32 nFirst = maSplits.LowerBound( GetFirstVisPos() ); + sal_uInt32 nLast = maSplits.UpperBound( GetLastVisPos() ); + if( (nFirst != CSV_VEC_NOTFOUND) && (nLast != CSV_VEC_NOTFOUND) ) + for( sal_uInt32 nIndex = nFirst; nIndex <= nLast; ++nIndex ) + ImplDrawSplit( GetSplitPos( nIndex ) ); +} + +void ScCsvRuler::ImplInvertCursor( sal_Int32 nPos ) +{ + if( IsVisibleSplitPos( nPos ) ) + { + ImplInvertRect( *maRulerDev, tools::Rectangle( Point( GetX( nPos ) - 1, 0 ), Size( 3, GetHeight() - 1 ) ) ); + if( HasSplit( nPos ) ) + ImplDrawSplit( nPos ); + } +} + +void ScCsvRuler::ImplSetMousePointer( sal_Int32 nPos ) +{ + SetPointer( HasSplit( nPos ) ? PointerStyle::HSplit : PointerStyle::Arrow ); +} + +// accessibility ============================================================== + +css::uno::Reference<css::accessibility::XAccessible> ScCsvRuler::CreateAccessible() +{ + rtl::Reference<ScAccessibleCsvRuler> xRef(new ScAccessibleCsvRuler(*this)); + mxAccessible = xRef; + return xRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/csvsplits.cxx b/sc/source/ui/dbgui/csvsplits.cxx new file mode 100644 index 0000000000..575bd53d0a --- /dev/null +++ b/sc/source/ui/dbgui/csvsplits.cxx @@ -0,0 +1,102 @@ +/* -*- 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 <csvsplits.hxx> + +#include <algorithm> + +#include <sal/log.hxx> + +bool ScCsvSplits::Insert( sal_Int32 nPos ) +{ + if (nPos < 0) + return false; + + const auto aIter = ::std::lower_bound( maVec.begin(), maVec.end(), nPos ); + + if (aIter != maVec.end() && *aIter == nPos) + return false; + + SAL_WARN_IF(maVec.size()>=static_cast<std::size_t>(SAL_MAX_UINT32-1), + "sc.ui", "ScCsvSplits::Insert: too many elements in vector"); + + maVec.insert( aIter, nPos ); + return true; +} + +bool ScCsvSplits::Remove( sal_Int32 nPos ) +{ + sal_uInt32 nIndex = GetIndex( nPos ); + if (nIndex == CSV_VEC_NOTFOUND) + return false; + + maVec.erase( maVec.begin() + nIndex ); + return true; +} + +void ScCsvSplits::RemoveRange( sal_Int32 nPosStart, sal_Int32 nPosEnd ) +{ + sal_uInt32 nStartIx = LowerBound( nPosStart ); + sal_uInt32 nEndIx = UpperBound( nPosEnd ); + if( (nStartIx != CSV_VEC_NOTFOUND) && (nEndIx != CSV_VEC_NOTFOUND) && (nStartIx <= nEndIx) ) + maVec.erase( maVec.begin() + nStartIx, maVec.begin() + nEndIx + 1 ); +} + +void ScCsvSplits::Clear() +{ + maVec.clear(); +} + +bool ScCsvSplits::HasSplit( sal_Int32 nPos ) const +{ + return GetIndex( nPos ) != CSV_VEC_NOTFOUND; +} + +sal_uInt32 ScCsvSplits::GetIndex( sal_Int32 nPos ) const +{ + auto aIter = ::std::lower_bound( maVec.cbegin(), maVec.cend(), nPos ); + return GetIterIndex( ((aIter != maVec.end()) && (*aIter == nPos)) ? aIter : maVec.end() ); +} + +sal_uInt32 ScCsvSplits::LowerBound( sal_Int32 nPos ) const +{ + return GetIterIndex( ::std::lower_bound( maVec.begin(), maVec.end(), nPos ) ); +} + +sal_uInt32 ScCsvSplits::UpperBound( sal_Int32 nPos ) const +{ + sal_uInt32 nIndex = LowerBound( nPos ); + if( nIndex == CSV_VEC_NOTFOUND ) + return Count() ? (Count() - 1) : CSV_VEC_NOTFOUND; + if( GetPos( nIndex ) == nPos ) + return nIndex; + return nIndex ? (nIndex - 1) : CSV_VEC_NOTFOUND; +} + +sal_Int32 ScCsvSplits::GetPos( sal_uInt32 nIndex ) const +{ + return (nIndex < Count()) ? maVec[ nIndex ] : CSV_POS_INVALID; +} + +sal_uInt32 ScCsvSplits::GetIterIndex( const_iterator const & aIter ) const +{ + return (aIter == maVec.end()) ? CSV_VEC_NOTFOUND : (aIter - maVec.begin()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/csvtablebox.cxx b/sc/source/ui/dbgui/csvtablebox.cxx new file mode 100644 index 0000000000..10dba1b810 --- /dev/null +++ b/sc/source/ui/dbgui/csvtablebox.cxx @@ -0,0 +1,369 @@ +/* -*- 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 <csvtablebox.hxx> +#include <vcl/settings.hxx> + +ScCsvTableBox::ScCsvTableBox(weld::Builder& rBuilder) + : mxRuler(new ScCsvRuler(maData, this)) + , mxGrid(new ScCsvGrid(maData, rBuilder.weld_menu("popup"), this)) + , mxScroll(rBuilder.weld_scrolled_window("scrolledwindow", true)) + , mxRulerWeld(new weld::CustomWeld(rBuilder, "csvruler", *mxRuler)) + , mxGridWeld(new weld::CustomWeld(rBuilder, "csvgrid", *mxGrid)) + , maEndScrollIdle("ScCsvTableBox maEndScrollIdle") +{ + Size aSize(mxScroll->get_approximate_digit_width() * 67, + mxScroll->get_text_height() * 10); + // this needs to be larger than the ScCsvGrid initial size to get it + // to stretch to fit, see ScCsvGrid::SetDrawingArea + mxScroll->set_size_request(aSize.Width(), aSize.Height()); + + mbFixedMode = false; + mnFixedWidth = 1; + + Link<ScCsvControl&,void> aLink = LINK( this, ScCsvTableBox, CsvCmdHdl ); + mxRuler->SetCmdHdl( aLink ); + mxGrid->SetCmdHdl( aLink ); + + mxScroll->connect_hadjustment_changed(LINK(this, ScCsvTableBox, HScrollHdl)); + mxScroll->connect_vadjustment_changed(LINK(this, ScCsvTableBox, VScrollHdl)); + + maEndScrollIdle.SetPriority(TaskPriority::LOWEST); + maEndScrollIdle.SetInvokeHandler(LINK(this,ScCsvTableBox,ScrollEndHdl)); + + InitControls(); +} + +ScCsvTableBox::~ScCsvTableBox() +{ +} + +// common table box handling -------------------------------------------------- + +void ScCsvTableBox::SetSeparatorsMode() +{ + if( !mbFixedMode ) + return; + + // rescue data for fixed width mode + mnFixedWidth = mxGrid->GetPosCount(); + maFixColStates = mxGrid->GetColumnStates(); + // switch to separators mode + mbFixedMode = false; + // reset and reinitialize controls + mxGrid->DisableRepaint(); + mxGrid->Execute( CSVCMD_SETLINEOFFSET, 0 ); + mxGrid->Execute( CSVCMD_SETPOSCOUNT, 1 ); + mxGrid->Execute( CSVCMD_NEWCELLTEXTS ); + mxGrid->SetColumnStates( std::vector(maSepColStates) ); + InitControls(); + mxGrid->EnableRepaint(); +} + +void ScCsvTableBox::SetFixedWidthMode() +{ + if( mbFixedMode ) + return; + + // rescue data for separators mode + maSepColStates = mxGrid->GetColumnStates(); + // switch to fixed width mode + mbFixedMode = true; + // reset and reinitialize controls + mxGrid->DisableRepaint(); + mxGrid->Execute( CSVCMD_SETLINEOFFSET, 0 ); + mxGrid->Execute( CSVCMD_SETPOSCOUNT, mnFixedWidth ); + mxGrid->SetSplits( mxRuler->GetSplits() ); + mxGrid->SetColumnStates( std::vector(maFixColStates) ); + InitControls(); + mxGrid->EnableRepaint(); +} + +void ScCsvTableBox::Init() +{ + mxGrid->Init(); +} + +void ScCsvTableBox::InitControls() +{ + mxGrid->UpdateLayoutData(); + + mxGrid->Show(); + if (mbFixedMode) + mxRuler->Show(); + else + mxRuler->Hide(); + + Size aWinSize = mxGrid->GetOutputSizePixel(); + maData.mnWinWidth = aWinSize.Width(); + maData.mnWinHeight = aWinSize.Height(); + + // scrollbars always visible + InitHScrollBar(); + + // scrollbars always visible + InitVScrollBar(); + + // let the controls self-adjust to visible area + mxGrid->Execute( CSVCMD_SETPOSOFFSET, mxGrid->GetFirstVisPos() ); + mxGrid->Execute( CSVCMD_SETLINEOFFSET, mxGrid->GetFirstVisLine() ); +} + +void ScCsvTableBox::InitHScrollBar() +{ + int nLower = 0; + int nValue = mxGrid->GetFirstVisPos(); + int nUpper = mxGrid->GetPosCount() + 2; + int nPageSize = mxGrid->GetVisPosCount(); + + // Undo scrollbar RTL + if (AllSettings::GetLayoutRTL()) + nValue = nUpper - (nValue - nLower + nPageSize); + + mxScroll->hadjustment_configure(nValue, nLower, nUpper, + 1, mxGrid->GetVisPosCount() * 3 / 4, + nPageSize); +} + +void ScCsvTableBox::InitVScrollBar() +{ + mxScroll->vadjustment_configure(mxGrid->GetFirstVisLine(), 0, mxGrid->GetLineCount() + 1, + 1, mxGrid->GetVisLineCount() - 2, + mxGrid->GetVisLineCount()); +} + +void ScCsvTableBox::MakePosVisible( sal_Int32 nPos ) +{ + if( (0 <= nPos) && (nPos < mxGrid->GetPosCount()) ) + { + if( nPos - CSV_SCROLL_DIST + 1 <= mxGrid->GetFirstVisPos() ) + mxGrid->Execute( CSVCMD_SETPOSOFFSET, nPos - CSV_SCROLL_DIST ); + else if( nPos + CSV_SCROLL_DIST >= mxGrid->GetLastVisPos() ) + mxGrid->Execute( CSVCMD_SETPOSOFFSET, nPos - mxGrid->GetVisPosCount() + CSV_SCROLL_DIST ); + } +} + +// cell contents -------------------------------------------------------------- + +void ScCsvTableBox::SetUniStrings( + const OUString* pTextLines, const OUString& rSepChars, + sal_Unicode cTextSep, bool bMergeSep, bool bRemoveSpace ) +{ + // assuming that pTextLines is a string array with size CSV_PREVIEW_LINES + // -> will be dynamic sometime + mxGrid->DisableRepaint(); + sal_Int32 nEndLine = mxGrid->GetFirstVisLine() + CSV_PREVIEW_LINES; + const OUString* pString = pTextLines; + for( sal_Int32 nLine = mxGrid->GetFirstVisLine(); nLine < nEndLine; ++nLine, ++pString ) + { + if( mbFixedMode ) + mxGrid->ImplSetTextLineFix( nLine, *pString ); + else + mxGrid->ImplSetTextLineSep( nLine, *pString, rSepChars, cTextSep, bMergeSep, bRemoveSpace ); + } + mxGrid->EnableRepaint(); +} + +// column settings ------------------------------------------------------------ + +void ScCsvTableBox::InitTypes(const weld::ComboBox& rListBox) +{ + const sal_Int32 nTypeCount = rListBox.get_count(); + std::vector<OUString> aTypeNames( nTypeCount ); + for( sal_Int32 nIndex = 0; nIndex < nTypeCount; ++nIndex ) + aTypeNames[ nIndex ] = rListBox.get_text( nIndex ); + mxGrid->SetTypeNames( std::move(aTypeNames) ); +} + +void ScCsvTableBox::FillColumnData( ScAsciiOptions& rOptions ) const +{ + if( mbFixedMode ) + mxGrid->FillColumnDataFix( rOptions ); + else + mxGrid->FillColumnDataSep( rOptions ); +} + +// event handling ------------------------------------------------------------- + +IMPL_LINK( ScCsvTableBox, CsvCmdHdl, ScCsvControl&, rCtrl, void ) +{ + const ScCsvCmd& rCmd = rCtrl.GetCmd(); + ScCsvCmdType eType = rCmd.GetType(); + sal_Int32 nParam1 = rCmd.GetParam1(); + sal_Int32 nParam2 = rCmd.GetParam2(); + + bool bFound = true; + switch( eType ) + { + case CSVCMD_REPAINT: + if( !mxGrid->IsNoRepaint() ) + { + mxGrid->Invalidate(); + mxRuler->Invalidate(); + InitHScrollBar(); + InitVScrollBar(); + } + break; + case CSVCMD_MAKEPOSVISIBLE: + MakePosVisible( nParam1 ); + break; + + case CSVCMD_NEWCELLTEXTS: + if( mbFixedMode ) + mxGrid->Execute( CSVCMD_UPDATECELLTEXTS ); + else + { + mxGrid->DisableRepaint(); + ScCsvColStateVec aStates( mxGrid->GetColumnStates() ); + sal_Int32 nPos = mxGrid->GetFirstVisPos(); + mxGrid->Execute( CSVCMD_SETPOSCOUNT, 1 ); + mxGrid->Execute( CSVCMD_UPDATECELLTEXTS ); + mxGrid->Execute( CSVCMD_SETPOSOFFSET, nPos ); + mxGrid->SetColumnStates( std::move(aStates) ); + mxGrid->EnableRepaint(); + } + break; + case CSVCMD_UPDATECELLTEXTS: + maUpdateTextHdl.Call( *this ); + break; + case CSVCMD_SETCOLUMNTYPE: + mxGrid->SetSelColumnType( nParam1 ); + break; + case CSVCMD_EXPORTCOLUMNTYPE: + maColTypeHdl.Call( *this ); + break; + case CSVCMD_SETFIRSTIMPORTLINE: + mxGrid->SetFirstImportedLine( nParam1 ); + break; + + case CSVCMD_INSERTSPLIT: + OSL_ENSURE( mbFixedMode, "ScCsvTableBox::CsvCmdHdl::InsertSplit - invalid call" ); + if( mxRuler->GetSplitCount() + 1 < sal::static_int_cast<sal_uInt32>(CSV_MAXCOLCOUNT) ) + { + mxRuler->InsertSplit( nParam1 ); + mxGrid->InsertSplit( nParam1 ); + } + break; + case CSVCMD_REMOVESPLIT: + OSL_ENSURE( mbFixedMode, "ScCsvTableBox::CsvCmdHdl::RemoveSplit - invalid call" ); + mxRuler->RemoveSplit( nParam1 ); + mxGrid->RemoveSplit( nParam1 ); + break; + case CSVCMD_TOGGLESPLIT: + mxGrid->Execute( mxRuler->HasSplit( nParam1 ) ? CSVCMD_REMOVESPLIT : CSVCMD_INSERTSPLIT, nParam1 ); + break; + case CSVCMD_MOVESPLIT: + OSL_ENSURE( mbFixedMode, "ScCsvTableBox::CsvCmdHdl::MoveSplit - invalid call" ); + mxRuler->MoveSplit( nParam1, nParam2 ); + mxGrid->MoveSplit( nParam1, nParam2 ); + break; + case CSVCMD_REMOVEALLSPLITS: + OSL_ENSURE( mbFixedMode, "ScCsvTableBox::CsvCmdHdl::RemoveAllSplits - invalid call" ); + mxRuler->RemoveAllSplits(); + mxGrid->RemoveAllSplits(); + break; + default: + bFound = false; + } + if( bFound ) + return; + + const ScCsvLayoutData aOldData( maData ); + switch( eType ) + { + case CSVCMD_SETPOSCOUNT: + maData.mnPosCount = std::max( nParam1, sal_Int32( 1 ) ); + ImplSetPosOffset( mxGrid->GetFirstVisPos() ); + break; + case CSVCMD_SETPOSOFFSET: + ImplSetPosOffset( nParam1 ); + break; + case CSVCMD_SETHDRWIDTH: + maData.mnHdrWidth = std::max( nParam1, sal_Int32( 0 ) ); + ImplSetPosOffset( mxGrid->GetFirstVisPos() ); + break; + case CSVCMD_SETCHARWIDTH: + maData.mnCharWidth = std::max( nParam1, sal_Int32( 1 ) ); + ImplSetPosOffset( mxGrid->GetFirstVisPos() ); + break; + case CSVCMD_SETLINECOUNT: + maData.mnLineCount = std::max( nParam1, sal_Int32( 1 ) ); + ImplSetLineOffset( mxGrid->GetFirstVisLine() ); + break; + case CSVCMD_SETLINEOFFSET: + ImplSetLineOffset( nParam1 ); + break; + case CSVCMD_SETHDRHEIGHT: + maData.mnHdrHeight = std::max( nParam1, sal_Int32( 0 ) ); + ImplSetLineOffset( mxGrid->GetFirstVisLine() ); + break; + case CSVCMD_SETLINEHEIGHT: + maData.mnLineHeight = std::max( nParam1, sal_Int32( 1 ) ); + ImplSetLineOffset( mxGrid->GetFirstVisLine() ); + break; + case CSVCMD_MOVERULERCURSOR: + maData.mnPosCursor = mxGrid->IsVisibleSplitPos( nParam1 ) ? nParam1 : CSV_POS_INVALID; + break; + case CSVCMD_MOVEGRIDCURSOR: + maData.mnColCursor = ((0 <= nParam1) && (nParam1 < mxGrid->GetPosCount())) ? nParam1 : CSV_POS_INVALID; + break; + default: + { + // added to avoid warnings + } + } + + if( maData != aOldData ) + { + mxGrid->DisableRepaint(); + mxRuler->ApplyLayout( aOldData ); + mxGrid->ApplyLayout( aOldData ); + mxGrid->EnableRepaint(); + } +} + +IMPL_LINK(ScCsvTableBox, HScrollHdl, weld::ScrolledWindow&, rScroll, void) +{ + int nLower = 0; + int nValue = rScroll.hadjustment_get_value(); + int nUpper = mxGrid->GetPosCount() + 2; + int nPageSize = mxGrid->GetVisPosCount(); + + // Undo scrollbar RTL + if (AllSettings::GetLayoutRTL()) + nValue = nUpper - (nValue - nLower + nPageSize); + + mxGrid->Execute(CSVCMD_SETPOSOFFSET, nValue); + maEndScrollIdle.Start(); +} + +IMPL_LINK(ScCsvTableBox, VScrollHdl, weld::ScrolledWindow&, rScroll, void) +{ + mxGrid->Execute(CSVCMD_SETLINEOFFSET, rScroll.vadjustment_get_value()); +} + +IMPL_LINK_NOARG(ScCsvTableBox, ScrollEndHdl, Timer*, void) +{ + if( mxGrid->GetRulerCursorPos() != CSV_POS_INVALID ) + mxGrid->Execute( CSVCMD_MOVERULERCURSOR, mxRuler->GetNoScrollPos( mxGrid->GetRulerCursorPos() ) ); + if( mxGrid->GetGridCursorPos() != CSV_POS_INVALID ) + mxGrid->Execute( CSVCMD_MOVEGRIDCURSOR, mxGrid->GetNoScrollCol( mxGrid->GetGridCursorPos() ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/dapidata.cxx b/sc/source/ui/dbgui/dapidata.cxx new file mode 100644 index 0000000000..0bd0861650 --- /dev/null +++ b/sc/source/ui/dbgui/dapidata.cxx @@ -0,0 +1,169 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <comphelper/processfactory.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <com/sun/star/sheet/DataImportMode.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <com/sun/star/sdb/XQueriesSupplier.hpp> +#include <com/sun/star/sdb/XCompletedConnection.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> + +#include <dapidata.hxx> +#include <dpsdbtab.hxx> + +using namespace com::sun::star; + +// entries in the "type" ListBox +#define DP_TYPELIST_TABLE 0 +#define DP_TYPELIST_QUERY 1 +#define DP_TYPELIST_SQLNAT 3 + +ScDataPilotDatabaseDlg::ScDataPilotDatabaseDlg(weld::Window* pParent) + : GenericDialogController(pParent, "modules/scalc/ui/selectdatasource.ui", "SelectDataSourceDialog") + , m_xLbDatabase(m_xBuilder->weld_combo_box("database")) + , m_xCbObject(m_xBuilder->weld_combo_box("datasource")) + , m_xLbType(m_xBuilder->weld_combo_box("type")) +{ + weld::WaitObject aWait(pParent); // initializing the database service the first time takes a while + + try + { + // get database names + + uno::Reference<sdb::XDatabaseContext> xContext = sdb::DatabaseContext::create( + comphelper::getProcessComponentContext() ); + const uno::Sequence<OUString> aNames = xContext->getElementNames(); + for( const OUString& aName : aNames ) + { + m_xLbDatabase->append_text(aName); + } + } + catch(uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sc", "exception in database"); + } + + m_xLbDatabase->set_active(0); + m_xLbType->set_active(0); + + FillObjects(); + + m_xLbDatabase->connect_changed( LINK( this, ScDataPilotDatabaseDlg, SelectHdl ) ); + m_xLbType->connect_changed( LINK( this, ScDataPilotDatabaseDlg, SelectHdl ) ); +} + +ScDataPilotDatabaseDlg::~ScDataPilotDatabaseDlg() +{ +} + +void ScDataPilotDatabaseDlg::GetValues( ScImportSourceDesc& rDesc ) +{ + const sal_Int32 nSelect = m_xLbType->get_active(); + + rDesc.aDBName = m_xLbDatabase->get_active_text(); + rDesc.aObject = m_xCbObject->get_active_text(); + + if (rDesc.aDBName.isEmpty() || rDesc.aObject.isEmpty()) + rDesc.nType = sheet::DataImportMode_NONE; + else if ( nSelect == DP_TYPELIST_TABLE ) + rDesc.nType = sheet::DataImportMode_TABLE; + else if ( nSelect == DP_TYPELIST_QUERY ) + rDesc.nType = sheet::DataImportMode_QUERY; + else + rDesc.nType = sheet::DataImportMode_SQL; + + rDesc.bNative = ( nSelect == DP_TYPELIST_SQLNAT ); +} + +IMPL_LINK_NOARG(ScDataPilotDatabaseDlg, SelectHdl, weld::ComboBox&, void) +{ + FillObjects(); +} + +void ScDataPilotDatabaseDlg::FillObjects() +{ + m_xCbObject->clear(); + + OUString aDatabaseName = m_xLbDatabase->get_active_text(); + if (aDatabaseName.isEmpty()) + return; + + const int nSelect = m_xLbType->get_active(); + if ( nSelect > DP_TYPELIST_QUERY ) + return; // only tables and queries + + try + { + // open connection (for tables or queries) + + uno::Reference<sdb::XDatabaseContext> xContext = sdb::DatabaseContext::create( + comphelper::getProcessComponentContext() ); + + uno::Any aSourceAny = xContext->getByName( aDatabaseName ); + uno::Reference<sdb::XCompletedConnection> xSource(aSourceAny, uno::UNO_QUERY); + if ( !xSource.is() ) return; + + uno::Reference<task::XInteractionHandler> xHandler( + task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr), + uno::UNO_QUERY_THROW); + + uno::Reference<sdbc::XConnection> xConnection = xSource->connectWithCompletion( xHandler ); + + uno::Reference<container::XNameAccess> xItems; + if ( nSelect == DP_TYPELIST_TABLE ) + { + // get all tables + + uno::Reference<sdbcx::XTablesSupplier> xTablesSupp( xConnection, uno::UNO_QUERY ); + if ( !xTablesSupp.is() ) return; + + xItems = xTablesSupp->getTables(); + } + else + { + // get all queries + + uno::Reference<sdb::XQueriesSupplier> xQueriesSupp( xConnection, uno::UNO_QUERY ); + if ( !xQueriesSupp.is() ) return; + + xItems = xQueriesSupp->getQueries(); + } + + if ( !xItems.is() ) return; + + // fill list + const uno::Sequence<OUString> aNames = xItems->getElementNames(); + for( const OUString& aName : aNames ) + { + m_xCbObject->append_text(aName); + } + } + catch(uno::Exception&) + { + // this may happen if an invalid database is selected -> no DBG_ERROR + TOOLS_WARN_EXCEPTION( "sc", "exception in database"); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/dapitype.cxx b/sc/source/ui/dbgui/dapitype.cxx new file mode 100644 index 0000000000..9843a2bb97 --- /dev/null +++ b/sc/source/ui/dbgui/dapitype.cxx @@ -0,0 +1,153 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <dapitype.hxx> +#include <comphelper/lok.hxx> + +using namespace com::sun::star; + +ScDataPilotSourceTypeDlg::ScDataPilotSourceTypeDlg(weld::Window* pParent, bool bEnableExternal) + : GenericDialogController(pParent, "modules/scalc/ui/selectsource.ui", "SelectSourceDialog") + , m_xBtnSelection(m_xBuilder->weld_radio_button("selection")) + , m_xBtnNamedRange(m_xBuilder->weld_radio_button("namedrange")) + , m_xBtnDatabase(m_xBuilder->weld_radio_button("database")) + , m_xBtnExternal(m_xBuilder->weld_radio_button("external")) + , m_xLbNamedRange(m_xBuilder->weld_combo_box("rangelb")) + , m_xBtnOk(m_xBuilder->weld_button("ok")) // for LOK jsdialog + , m_xBtnCancel(m_xBuilder->weld_button("cancel")) // for LOK jsdialog +{ + m_xBtnSelection->connect_toggled( LINK(this, ScDataPilotSourceTypeDlg, RadioClickHdl) ); + m_xBtnNamedRange->connect_toggled( LINK(this, ScDataPilotSourceTypeDlg, RadioClickHdl) ); + m_xBtnDatabase->connect_toggled( LINK(this, ScDataPilotSourceTypeDlg, RadioClickHdl) ); + m_xBtnExternal->connect_toggled( LINK(this, ScDataPilotSourceTypeDlg, RadioClickHdl) ); + + m_xBtnOk->connect_clicked( LINK(this, ScDataPilotSourceTypeDlg, ResponseHdl ) ); + m_xBtnCancel->connect_clicked( LINK(this, ScDataPilotSourceTypeDlg, ResponseHdl ) ); + + if (!bEnableExternal) + m_xBtnExternal->set_sensitive(false); + + m_xBtnSelection->set_active(true); + + // Disabled unless at least one named range exists. + m_xLbNamedRange->set_sensitive(false); + m_xBtnNamedRange->set_sensitive(false); + + // Intentionally hide this button to see if anyone complains. + m_xBtnExternal->hide(); + + if (comphelper::LibreOfficeKit::isActive()) + m_xBtnDatabase->hide(); +} + +IMPL_LINK(ScDataPilotSourceTypeDlg, ResponseHdl, weld::Button&, rButton, void) +{ + if (&rButton == m_xBtnOk.get()) + m_xDialog->response(RET_OK); + else + m_xDialog->response(RET_CANCEL); +} + +ScDataPilotSourceTypeDlg::~ScDataPilotSourceTypeDlg() +{ +} + +bool ScDataPilotSourceTypeDlg::IsDatabase() const +{ + return m_xBtnDatabase->get_active(); +} + +bool ScDataPilotSourceTypeDlg::IsExternal() const +{ + return m_xBtnExternal->get_active(); +} + +bool ScDataPilotSourceTypeDlg::IsNamedRange() const +{ + return m_xBtnNamedRange->get_active(); +} + +OUString ScDataPilotSourceTypeDlg::GetSelectedNamedRange() const +{ + return m_xLbNamedRange->get_active_text(); +} + +void ScDataPilotSourceTypeDlg::AppendNamedRange(const OUString& rName) +{ + m_xLbNamedRange->append_text(rName); + if (m_xLbNamedRange->get_count() == 1) + { + // Select position 0 only for the first time. + m_xLbNamedRange->set_active(0); + m_xBtnNamedRange->set_sensitive(true); + } +} + +IMPL_LINK_NOARG(ScDataPilotSourceTypeDlg, RadioClickHdl, weld::Toggleable&, void) +{ + m_xLbNamedRange->set_sensitive(m_xBtnNamedRange->get_active()); +} + +ScDataPilotServiceDlg::ScDataPilotServiceDlg(weld::Window* pParent, const std::vector<OUString>& rServices) + : GenericDialogController(pParent, "modules/scalc/ui/dapiservicedialog.ui", "DapiserviceDialog") + , m_xLbService(m_xBuilder->weld_combo_box("service")) + , m_xEdSource(m_xBuilder->weld_entry("source")) + , m_xEdName(m_xBuilder->weld_entry("name")) + , m_xEdUser(m_xBuilder->weld_entry("user")) + , m_xEdPasswd(m_xBuilder->weld_entry("password")) +{ + for (const OUString& aName : rServices) + { + m_xLbService->append_text(aName); + } + m_xLbService->set_active(0); +} + +ScDataPilotServiceDlg::~ScDataPilotServiceDlg() +{ +} + +OUString ScDataPilotServiceDlg::GetServiceName() const +{ + return m_xLbService->get_active_text(); +} + +OUString ScDataPilotServiceDlg::GetParSource() const +{ + return m_xEdSource->get_text(); +} + +OUString ScDataPilotServiceDlg::GetParName() const +{ + return m_xEdName->get_text(); +} + +OUString ScDataPilotServiceDlg::GetParUser() const +{ + return m_xEdUser->get_text(); +} + +OUString ScDataPilotServiceDlg::GetParPass() const +{ + return m_xEdPasswd->get_text(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/dbnamdlg.cxx b/sc/source/ui/dbgui/dbnamdlg.cxx new file mode 100644 index 0000000000..68543eb6ce --- /dev/null +++ b/sc/source/ui/dbgui/dbnamdlg.cxx @@ -0,0 +1,634 @@ +/* -*- 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 <memory> +#include <sal/config.h> + +#include <cassert> + +#include <comphelper/string.hxx> +#include <unotools/charclass.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <o3tl/string_view.hxx> + +#include <reffact.hxx> +#include <document.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <rangenam.hxx> +#include <globalnames.hxx> +#include <dbnamdlg.hxx> +#include <dbdocfun.hxx> + +namespace +{ + void ERRORBOX(weld::Window* pParent, const OUString& rString) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, + VclMessageType::Warning, VclButtonsType::Ok, + rString)); + xBox->run(); + } + + +class DBSaveData +{ +public: + DBSaveData( formula::RefEdit& rEd, weld::CheckButton& rHdr, weld::CheckButton& rTot, weld::CheckButton& rSize, weld::CheckButton& rFmt, + weld::CheckButton& rStrip, ScRange& rArea ) + : rEdAssign(rEd) + , rBtnHeader(rHdr) + , rBtnTotals(rTot) + , rBtnSize(rSize) + , rBtnFormat(rFmt) + , rBtnStrip(rStrip) + , rCurArea(rArea) + , bHeader(false) + , bTotals(false) + , bSize(false) + , bFormat(false) + , bStrip(false) + , bDirty(false) + { + } + void Save(); + void Restore(); + +private: + formula::RefEdit& rEdAssign; + weld::CheckButton& rBtnHeader; + weld::CheckButton& rBtnTotals; + weld::CheckButton& rBtnSize; + weld::CheckButton& rBtnFormat; + weld::CheckButton& rBtnStrip; + ScRange& rCurArea; + OUString aStr; + ScRange aArea; + bool bHeader:1; + bool bTotals:1; + bool bSize:1; + bool bFormat:1; + bool bStrip:1; + bool bDirty:1; +}; + +} + +void DBSaveData::Save() +{ + aArea = rCurArea; + aStr = rEdAssign.GetText(); + bHeader = rBtnHeader.get_active(); + bTotals = rBtnTotals.get_active(); + bSize = rBtnSize.get_active(); + bFormat = rBtnFormat.get_active(); + bStrip = rBtnStrip.get_active(); + bDirty = true; +} + +void DBSaveData::Restore() +{ + if ( bDirty ) + { + rCurArea = aArea; + rEdAssign.SetText( aStr ); + rBtnHeader.set_active ( bHeader ); + rBtnTotals.set_active ( bTotals ); + rBtnSize.set_active ( bSize ); + rBtnFormat.set_active ( bFormat ); + rBtnStrip.set_active ( bStrip ); + bDirty = false; + } +} + +static std::unique_ptr<DBSaveData> xSaveObj; + +ScDbNameDlg::ScDbNameDlg(SfxBindings* pB, SfxChildWindow* pCW, weld::Window* pParent, + ScViewData& rViewData) + : ScAnyRefDlgController(pB, pCW, pParent, + "modules/scalc/ui/definedatabaserangedialog.ui", "DefineDatabaseRangeDialog") + , m_rViewData(rViewData) + , rDoc(rViewData.GetDocument()) + , bRefInputMode(false) + , aAddrDetails(rDoc.GetAddressConvention(), 0, 0) + , aLocalDbCol(*(rDoc.GetDBCollection())) + , m_xEdName(m_xBuilder->weld_entry_tree_view("entrygrid", "entry", "entry-list")) + , m_xAssignFrame(m_xBuilder->weld_frame("RangeFrame")) + , m_xEdAssign(new formula::RefEdit(m_xBuilder->weld_entry("assign"))) + , m_xRbAssign(new formula::RefButton(m_xBuilder->weld_button("assignrb"))) + , m_xOptions(m_xBuilder->weld_widget("Options")) + , m_xBtnHeader(m_xBuilder->weld_check_button("ContainsColumnLabels")) + , m_xBtnTotals(m_xBuilder->weld_check_button("ContainsTotalsRow")) + , m_xBtnDoSize(m_xBuilder->weld_check_button("InsertOrDeleteCells")) + , m_xBtnKeepFmt(m_xBuilder->weld_check_button("KeepFormatting")) + , m_xBtnStripData(m_xBuilder->weld_check_button("DontSaveImportedData")) + , m_xFTSource(m_xBuilder->weld_label("Source")) + , m_xFTOperations(m_xBuilder->weld_label("Operations")) + , m_xBtnOk(m_xBuilder->weld_button("ok")) + , m_xBtnCancel(m_xBuilder->weld_button("cancel")) + , m_xBtnAdd(m_xBuilder->weld_button("add")) + , m_xBtnRemove(m_xBuilder->weld_button("delete")) + , m_xModifyPB(m_xBuilder->weld_button("modify")) + , m_xInvalidFT(m_xBuilder->weld_label("invalid")) + , m_xFrameLabel(m_xAssignFrame->weld_label_widget()) +{ + m_xEdName->set_height_request_by_rows(4); + m_xEdAssign->SetReferences(this, m_xFrameLabel.get()); + m_xRbAssign->SetReferences(this, m_xEdAssign.get()); + aStrAdd = m_xBtnAdd->get_label(); + aStrModify = m_xModifyPB->get_label(); + aStrInvalid = m_xInvalidFT->get_label(); + + // so that the strings in the resource can stay with fixed texts: + aStrSource = m_xFTSource->get_label(); + aStrOperations = m_xFTOperations->get_label(); + + xSaveObj.reset(new DBSaveData( *m_xEdAssign, *m_xBtnHeader, *m_xBtnTotals, + *m_xBtnDoSize, *m_xBtnKeepFmt, *m_xBtnStripData, theCurArea )); + Init(); +} + +ScDbNameDlg::~ScDbNameDlg() +{ + xSaveObj.reset(); +} + +void ScDbNameDlg::Init() +{ + m_xBtnHeader->set_active(true); // Default: with column headers + m_xBtnTotals->set_active( false ); // Default: without totals row + m_xBtnDoSize->set_active(true); + m_xBtnKeepFmt->set_active(true); + + m_xBtnOk->connect_clicked ( LINK( this, ScDbNameDlg, OkBtnHdl ) ); + m_xBtnCancel->connect_clicked ( LINK( this, ScDbNameDlg, CancelBtnHdl ) ); + m_xBtnAdd->connect_clicked ( LINK( this, ScDbNameDlg, AddBtnHdl ) ); + m_xBtnRemove->connect_clicked ( LINK( this, ScDbNameDlg, RemoveBtnHdl ) ); + m_xEdName->connect_changed( LINK( this, ScDbNameDlg, NameModifyHdl ) ); + m_xEdAssign->SetModifyHdl ( LINK( this, ScDbNameDlg, AssModifyHdl ) ); + UpdateNames(); + + OUString theAreaStr; + + SCCOL nStartCol = 0; + SCROW nStartRow = 0; + SCTAB nStartTab = 0; + SCCOL nEndCol = 0; + SCROW nEndRow = 0; + SCTAB nEndTab = 0; + + ScDBCollection* pDBColl = rDoc.GetDBCollection(); + + m_rViewData.GetSimpleArea( nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab ); + + theCurArea = ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab); + + theAreaStr = theCurArea.Format(rDoc, ScRefFlags::RANGE_ABS_3D, aAddrDetails); + + if ( pDBColl ) + { + // determine if the defined DB area has been marked: + ScDBData* pDBData = pDBColl->GetDBAtCursor( nStartCol, nStartRow, nStartTab, ScDBDataPortion::TOP_LEFT ); + if ( pDBData ) + { + ScAddress& rStart = theCurArea.aStart; + ScAddress& rEnd = theCurArea.aEnd; + SCCOL nCol1; + SCCOL nCol2; + SCROW nRow1; + SCROW nRow2; + SCTAB nTab; + + pDBData->GetArea( nTab, nCol1, nRow1, nCol2, nRow2 ); + + if ( (rStart.Tab() == nTab) + && (rStart.Col() == nCol1) && (rStart.Row() == nRow1) + && (rEnd.Col() == nCol2) && (rEnd.Row() == nRow2 ) ) + { + OUString aDBName = pDBData->GetName(); + if ( aDBName != STR_DB_LOCAL_NONAME ) + m_xEdName->set_entry_text(aDBName); + + m_xBtnHeader->set_active( pDBData->HasHeader() ); + m_xBtnTotals->set_active( pDBData->HasTotals() ); + m_xBtnDoSize->set_active( pDBData->IsDoSize() ); + m_xBtnKeepFmt->set_active( pDBData->IsKeepFmt() ); + m_xBtnStripData->set_active( pDBData->IsStripData() ); + SetInfoStrings( pDBData ); + } + } + } + + m_xEdAssign->SetText( theAreaStr ); + m_xEdName->grab_focus(); + bSaved = true; + xSaveObj->Save(); + NameModifyHdl( *m_xEdName ); + bInvalid = false; +} + +void ScDbNameDlg::SetInfoStrings( const ScDBData* pDBData ) +{ + OUStringBuffer aBuf(aStrSource); + if (pDBData) + { + aBuf.append(" " + pDBData->GetSourceString()); + } + m_xFTSource->set_label(aBuf.makeStringAndClear()); + + aBuf.append(aStrOperations); + if (pDBData) + { + aBuf.append(" " + pDBData->GetOperations()); + } + m_xFTOperations->set_label(aBuf.makeStringAndClear()); +} + +// Transfer of a table area selected with the mouse, which is then displayed +// as a new selection in the reference window. + +void ScDbNameDlg::SetReference( const ScRange& rRef, ScDocument& rDocP ) +{ + if (!m_xEdAssign->GetWidget()->get_sensitive()) + return; + + if ( rRef.aStart != rRef.aEnd ) + RefInputStart(m_xEdAssign.get()); + + theCurArea = rRef; + + OUString aRefStr(theCurArea.Format(rDocP, ScRefFlags::RANGE_ABS_3D, aAddrDetails)); + m_xEdAssign->SetRefString( aRefStr ); + m_xOptions->set_sensitive(true); + m_xBtnAdd->set_sensitive(true); + bSaved = true; + xSaveObj->Save(); +} + +void ScDbNameDlg::Close() +{ + DoClose( ScDbNameDlgWrapper::GetChildWindowId() ); +} + +void ScDbNameDlg::SetActive() +{ + m_xEdAssign->GrabFocus(); + + // No NameModifyHdl, because otherwise areas can not be changed + // (the old content would be displayed again after the reference selection is pulled) + // (the selected DB name has not changed either) + + RefInputDone(); +} + +void ScDbNameDlg::UpdateNames() +{ + typedef ScDBCollection::NamedDBs DBsType; + + const DBsType& rDBs = aLocalDbCol.getNamedDBs(); + + m_xEdName->freeze(); + + m_xEdName->clear(); + m_xEdAssign->SetText( OUString() ); + + if (!rDBs.empty()) + { + for (const auto& rxDB : rDBs) + m_xEdName->append_text(rxDB->GetName()); + } + else + { + m_xBtnAdd->set_label( aStrAdd ); + m_xBtnAdd->set_sensitive(false); + m_xBtnRemove->set_sensitive(false); + } + + m_xEdName->thaw(); +} + +void ScDbNameDlg::UpdateDBData( const OUString& rStrName ) +{ + + const ScDBData* pData = aLocalDbCol.getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rStrName)); + + if ( pData ) + { + SCCOL nColStart = 0; + SCROW nRowStart = 0; + SCCOL nColEnd = 0; + SCROW nRowEnd = 0; + SCTAB nTab = 0; + + pData->GetArea( nTab, nColStart, nRowStart, nColEnd, nRowEnd ); + theCurArea = ScRange( ScAddress( nColStart, nRowStart, nTab ), + ScAddress( nColEnd, nRowEnd, nTab ) ); + OUString theArea(theCurArea.Format(rDoc, ScRefFlags::RANGE_ABS_3D, aAddrDetails)); + m_xEdAssign->SetText( theArea ); + m_xBtnAdd->set_label( aStrModify ); + m_xBtnHeader->set_active( pData->HasHeader() ); + m_xBtnTotals->set_active( pData->HasTotals() ); + m_xBtnDoSize->set_active( pData->IsDoSize() ); + m_xBtnKeepFmt->set_active( pData->IsKeepFmt() ); + m_xBtnStripData->set_active( pData->IsStripData() ); + SetInfoStrings( pData ); + } + + m_xBtnAdd->set_label( aStrModify ); + m_xBtnAdd->set_sensitive(true); + m_xBtnRemove->set_sensitive(true); + m_xOptions->set_sensitive(true); +} + +bool ScDbNameDlg::IsRefInputMode() const +{ + return bRefInputMode; +} + +// Handler: + +IMPL_LINK_NOARG(ScDbNameDlg, OkBtnHdl, weld::Button&, void) +{ + bInvalid = false; + AddBtnHdl(*m_xBtnAdd); + + // Pass the changes and the remove list to the view: both are + // transferred as a reference only, so that no dead memory can + // be created at this point: + if (!bInvalid) + { + ScDBDocFunc aFunc(*m_rViewData.GetDocShell()); + aFunc.ModifyAllDBData(aLocalDbCol, aRemoveList); + response(RET_OK); + } +} + +IMPL_LINK_NOARG(ScDbNameDlg, CancelBtnHdl, weld::Button&, void) +{ + response(RET_CANCEL); +} + +IMPL_LINK_NOARG(ScDbNameDlg, AddBtnHdl, weld::Button&, void) +{ + OUString aNewName = comphelper::string::strip(m_xEdName->get_active_text(), ' '); + OUString aNewArea = m_xEdAssign->GetText(); + + if ( aNewName.isEmpty() || aNewArea.isEmpty() ) + return; + + if (ScRangeData::IsNameValid(aNewName, rDoc) == ScRangeData::IsNameValidType::NAME_VALID + && aNewName != STR_DB_LOCAL_NONAME) + { + // because editing can be done now, parsing is needed first + ScRange aTmpRange; + OUString aText = m_xEdAssign->GetText(); + if ( aTmpRange.ParseAny( aText, rDoc, aAddrDetails ) & ScRefFlags::VALID ) + { + theCurArea = aTmpRange; + ScAddress aStart = theCurArea.aStart; + ScAddress aEnd = theCurArea.aEnd; + + ScDBData* pOldEntry = aLocalDbCol.getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(aNewName)); + if (pOldEntry) + { + // modify area + + pOldEntry->MoveTo( aStart.Tab(), aStart.Col(), aStart.Row(), + aEnd.Col(), aEnd.Row() ); + pOldEntry->SetByRow( true ); + pOldEntry->SetHeader( m_xBtnHeader->get_active() ); + pOldEntry->SetTotals( m_xBtnTotals->get_active() ); + pOldEntry->SetDoSize( m_xBtnDoSize->get_active() ); + pOldEntry->SetKeepFmt( m_xBtnKeepFmt->get_active() ); + pOldEntry->SetStripData( m_xBtnStripData->get_active() ); + } + else + { + // insert new area + + std::unique_ptr<ScDBData> pNewEntry(new ScDBData( aNewName, aStart.Tab(), + aStart.Col(), aStart.Row(), + aEnd.Col(), aEnd.Row(), + true, m_xBtnHeader->get_active(), + m_xBtnTotals->get_active() )); + pNewEntry->SetDoSize( m_xBtnDoSize->get_active() ); + pNewEntry->SetKeepFmt( m_xBtnKeepFmt->get_active() ); + pNewEntry->SetStripData( m_xBtnStripData->get_active() ); + + bool ins = aLocalDbCol.getNamedDBs().insert(std::move(pNewEntry)); + assert(ins); (void)ins; + } + + UpdateNames(); + + m_xEdName->set_entry_text( OUString() ); + m_xEdName->grab_focus(); + m_xBtnAdd->set_label( aStrAdd ); + m_xBtnAdd->set_sensitive(false); + m_xBtnRemove->set_sensitive(false); + m_xEdAssign->SetText( OUString() ); + m_xBtnHeader->set_active(true); // Default: with column headers + m_xBtnTotals->set_active( false ); // Default: without totals row + m_xBtnDoSize->set_active( false ); + m_xBtnKeepFmt->set_active( false ); + m_xBtnStripData->set_active( false ); + SetInfoStrings( nullptr ); // empty + theCurArea = ScRange(); + bSaved = true; + xSaveObj->Save(); + NameModifyHdl( *m_xEdName ); + } + else + { + ERRORBOX(m_xDialog.get(), aStrInvalid); + m_xEdAssign->SelectAll(); + m_xEdAssign->GrabFocus(); + bInvalid = true; + } + } + else + { + ERRORBOX(m_xDialog.get(), ScResId(STR_INVALIDNAME)); + m_xEdName->select_entry_region(0, -1); + m_xEdName->grab_focus(); + bInvalid = true; + } +} + +namespace { + +class FindByName +{ + const OUString& mrName; +public: + explicit FindByName(const OUString& rName) : mrName(rName) {} + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + return p->GetName() == mrName; + } +}; + +} + +IMPL_LINK_NOARG(ScDbNameDlg, RemoveBtnHdl, weld::Button&, void) +{ + OUString aStrEntry = m_xEdName->get_active_text(); + ScDBCollection::NamedDBs& rDBs = aLocalDbCol.getNamedDBs(); + ScDBCollection::NamedDBs::iterator itr = + ::std::find_if(rDBs.begin(), rDBs.end(), FindByName(aStrEntry)); + + if (itr == rDBs.end()) + return; + + OUString aStrDelMsg = ScResId( STR_QUERY_DELENTRY ); + OUString sMsg{ o3tl::getToken(aStrDelMsg, 0, '#') + aStrEntry + o3tl::getToken(aStrDelMsg, 1, '#') }; + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Question, VclButtonsType::YesNo, + sMsg)); + xQueryBox->set_default_response(RET_YES); + if (RET_YES != xQueryBox->run()) + return; + + SCTAB nTab; + SCCOL nColStart, nColEnd; + SCROW nRowStart, nRowEnd; + (*itr)->GetArea( nTab, nColStart, nRowStart, nColEnd, nRowEnd ); + aRemoveList.emplace_back( ScAddress( nColStart, nRowStart, nTab ), + ScAddress( nColEnd, nRowEnd, nTab ) ); + + rDBs.erase(itr); + + UpdateNames(); + + m_xEdName->set_entry_text( OUString() ); + m_xEdName->grab_focus(); + m_xBtnAdd->set_label( aStrAdd ); + m_xBtnAdd->set_sensitive(false); + m_xBtnRemove->set_sensitive(false); + m_xEdAssign->SetText( OUString() ); + theCurArea = ScRange(); + m_xBtnHeader->set_active(true); // Default: with column headers + m_xBtnTotals->set_active( false ); // Default: without totals row + m_xBtnDoSize->set_active( false ); + m_xBtnKeepFmt->set_active( false ); + m_xBtnStripData->set_active( false ); + SetInfoStrings( nullptr ); // empty + bSaved=false; + xSaveObj->Restore(); + NameModifyHdl( *m_xEdName ); +} + +IMPL_LINK_NOARG(ScDbNameDlg, NameModifyHdl, weld::ComboBox&, void) +{ + OUString theName = m_xEdName->get_active_text(); + bool bNameFound = m_xEdName->find_text(theName) != -1; + + if ( theName.isEmpty() ) + { + if (m_xBtnAdd->get_label() != aStrAdd) + m_xBtnAdd->set_label( aStrAdd ); + m_xBtnAdd->set_sensitive(false); + m_xBtnRemove->set_sensitive(false); + m_xAssignFrame->set_sensitive(false); + m_xOptions->set_sensitive(false); + //bSaved=sal_False; + //xSaveObj->Restore(); + //@BugID 54702 enable/disable in the base class only + //SFX_APPWINDOW->Disable(sal_False); //! general method in ScAnyRefDlg + bRefInputMode = false; + } + else + { + if ( bNameFound ) + { + if (m_xBtnAdd->get_label() != aStrModify) + m_xBtnAdd->set_label( aStrModify ); + + if(!bSaved) + { + bSaved = true; + xSaveObj->Save(); + } + UpdateDBData( theName ); + } + else + { + if (m_xBtnAdd->get_label() != aStrAdd) + m_xBtnAdd->set_label( aStrAdd ); + + bSaved=false; + xSaveObj->Restore(); + + if ( !m_xEdAssign->GetText().isEmpty() ) + { + m_xBtnAdd->set_sensitive(true); + m_xOptions->set_sensitive(true); + } + else + { + m_xBtnAdd->set_sensitive(false); + m_xOptions->set_sensitive(false); + } + m_xBtnRemove->set_sensitive(false); + } + + m_xAssignFrame->set_sensitive(true); + + //@BugID 54702 enable/disable in the base class only + //SFX_APPWINDOW->set_sensitive(true); + bRefInputMode = true; + } +} + +IMPL_LINK_NOARG(ScDbNameDlg, AssModifyHdl, formula::RefEdit&, void) +{ + // parse here for Save(), etc. + + ScRange aTmpRange; + OUString aText = m_xEdAssign->GetText(); + if ( aTmpRange.ParseAny( aText, rDoc, aAddrDetails ) & ScRefFlags::VALID ) + theCurArea = aTmpRange; + + if (!aText.isEmpty() && !m_xEdName->get_active_text().isEmpty()) + { + m_xBtnAdd->set_sensitive(true); + m_xBtnHeader->set_sensitive(true); + m_xBtnTotals->set_sensitive(true); + m_xBtnDoSize->set_sensitive(true); + m_xBtnKeepFmt->set_sensitive(true); + m_xBtnStripData->set_sensitive(true); + m_xFTSource->set_sensitive(true); + m_xFTOperations->set_sensitive(true); + } + else + { + m_xBtnAdd->set_sensitive(false); + m_xBtnHeader->set_sensitive(false); + m_xBtnTotals->set_sensitive(false); + m_xBtnDoSize->set_sensitive(false); + m_xBtnKeepFmt->set_sensitive(false); + m_xBtnStripData->set_sensitive(false); + m_xFTSource->set_sensitive(false); + m_xFTOperations->set_sensitive(false); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/dpgroupdlg.cxx b/sc/source/ui/dbgui/dpgroupdlg.cxx new file mode 100644 index 0000000000..550695cc31 --- /dev/null +++ b/sc/source/ui/dbgui/dpgroupdlg.cxx @@ -0,0 +1,353 @@ +/* -*- 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 . + */ + +#ifdef SC_DLLIMPLEMENTATION +#undef SC_DLLIMPLEMENTATION +#endif + +#include <dpgroupdlg.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <editfield.hxx> + +#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp> +#include <svtools/ctrlbox.hxx> + +namespace { + +/** Date part flags in order of the list box entries. */ +const sal_Int32 spnDateParts[] = +{ + css::sheet::DataPilotFieldGroupBy::SECONDS, + css::sheet::DataPilotFieldGroupBy::MINUTES, + css::sheet::DataPilotFieldGroupBy::HOURS, + css::sheet::DataPilotFieldGroupBy::DAYS, + css::sheet::DataPilotFieldGroupBy::MONTHS, + css::sheet::DataPilotFieldGroupBy::QUARTERS, + css::sheet::DataPilotFieldGroupBy::YEARS +}; + +const TranslateId aDatePartResIds[] = +{ + STR_DPFIELD_GROUP_BY_SECONDS, + STR_DPFIELD_GROUP_BY_MINUTES, + STR_DPFIELD_GROUP_BY_HOURS, + STR_DPFIELD_GROUP_BY_DAYS, + STR_DPFIELD_GROUP_BY_MONTHS, + STR_DPFIELD_GROUP_BY_QUARTERS, + STR_DPFIELD_GROUP_BY_YEARS +}; + +} // namespace + +ScDPGroupEditHelper::ScDPGroupEditHelper(weld::RadioButton& rRbAuto, weld::RadioButton& rRbMan, weld::Widget& rEdValue) + : mrRbAuto(rRbAuto) + , mrRbMan(rRbMan) + , mrEdValue(rEdValue) +{ + mrRbAuto.connect_toggled( LINK( this, ScDPGroupEditHelper, ToggleHdl ) ); + mrRbMan.connect_toggled( LINK( this, ScDPGroupEditHelper, ToggleHdl ) ); +} + +bool ScDPGroupEditHelper::IsAuto() const +{ + return mrRbAuto.get_active(); +} + +double ScDPGroupEditHelper::GetValue() const +{ + double fValue; + if( !ImplGetValue( fValue ) ) + fValue = 0.0; + return fValue; +} + +void ScDPGroupEditHelper::SetValue( bool bAuto, double fValue ) +{ + if( bAuto ) + { + mrRbAuto.set_active(true); + ToggleHdl(mrRbAuto); + } + else + { + mrRbMan.set_active(true); + ToggleHdl(mrRbMan); + } + ImplSetValue( fValue ); +} + +IMPL_LINK(ScDPGroupEditHelper, ToggleHdl, weld::Toggleable&, rButton, void) +{ + if (!rButton.get_active()) + return; + + if (mrRbAuto.get_active()) + { + // disable edit field on clicking "automatic" radio button + mrEdValue.set_sensitive(false); + } + else if (mrRbMan.get_active()) + { + // enable and set focus to edit field on clicking "manual" radio button + mrEdValue.set_sensitive(true); + mrEdValue.grab_focus(); + } +} + +ScDPNumGroupEditHelper::ScDPNumGroupEditHelper(weld::RadioButton& rRbAuto, + weld::RadioButton& rRbMan, ScDoubleField& rEdValue) + : ScDPGroupEditHelper(rRbAuto, rRbMan, rEdValue.get_widget()) + , mrEdValue(rEdValue) +{ +} + +bool ScDPNumGroupEditHelper::ImplGetValue( double& rfValue ) const +{ + return mrEdValue.GetValue(rfValue); +} + +void ScDPNumGroupEditHelper::ImplSetValue( double fValue ) +{ + mrEdValue.SetValue(fValue); +} + +ScDPDateGroupEditHelper::ScDPDateGroupEditHelper(weld::RadioButton& rRbAuto, weld::RadioButton& rRbMan, + SvtCalendarBox& rEdValue, const Date& rNullDate) + : ScDPGroupEditHelper(rRbAuto, rRbMan, rEdValue.get_button()) + , mrEdValue(rEdValue) + , maNullDate(rNullDate) +{ +} + +bool ScDPDateGroupEditHelper::ImplGetValue( double& rfValue ) const +{ + rfValue = mrEdValue.get_date() - maNullDate; + return true; +} + +void ScDPDateGroupEditHelper::ImplSetValue( double fValue ) +{ + Date aDate( maNullDate ); + aDate.AddDays( fValue ); + mrEdValue.set_date( aDate ); +} + +ScDPNumGroupDlg::ScDPNumGroupDlg(weld::Window* pParent, const ScDPNumGroupInfo& rInfo) + : GenericDialogController(pParent, "modules/scalc/ui/groupbynumber.ui", "PivotTableGroupByNumber") + , mxRbAutoStart(m_xBuilder->weld_radio_button("auto_start")) + , mxRbManStart(m_xBuilder->weld_radio_button("manual_start")) + , mxEdStart(new ScDoubleField(m_xBuilder->weld_entry("edit_start"))) + , mxRbAutoEnd(m_xBuilder->weld_radio_button("auto_end")) + , mxRbManEnd(m_xBuilder->weld_radio_button("manual_end")) + , mxEdEnd(new ScDoubleField(m_xBuilder->weld_entry("edit_end"))) + , mxEdBy(new ScDoubleField(m_xBuilder->weld_entry("edit_by"))) + , maStartHelper(*mxRbAutoStart, *mxRbManStart, *mxEdStart) + , maEndHelper(*mxRbAutoEnd, *mxRbManEnd, *mxEdEnd) +{ + maStartHelper.SetValue( rInfo.mbAutoStart, rInfo.mfStart ); + maEndHelper.SetValue( rInfo.mbAutoEnd, rInfo.mfEnd ); + mxEdBy->SetValue( (rInfo.mfStep <= 0.0) ? 1.0 : rInfo.mfStep ); + + /* Set the initial focus, currently it is somewhere after calling all the radio + button click handlers. Now the first enabled editable control is focused. */ + if (mxEdStart->get_sensitive()) + mxEdStart->grab_focus(); + else if (mxEdEnd->get_sensitive()) + mxEdEnd->grab_focus(); + else + mxEdBy->grab_focus(); +} + +ScDPNumGroupDlg::~ScDPNumGroupDlg() +{ +} + +ScDPNumGroupInfo ScDPNumGroupDlg::GetGroupInfo() const +{ + ScDPNumGroupInfo aInfo; + aInfo.mbEnable = true; + aInfo.mbDateValues = false; + aInfo.mbAutoStart = maStartHelper.IsAuto(); + aInfo.mbAutoEnd = maEndHelper.IsAuto(); + + // get values and silently auto-correct them, if they are not valid + // TODO: error messages in OK event? + aInfo.mfStart = maStartHelper.GetValue(); + aInfo.mfEnd = maEndHelper.GetValue(); + if( !mxEdBy->GetValue( aInfo.mfStep ) || (aInfo.mfStep <= 0.0) ) + aInfo.mfStep = 1.0; + if( aInfo.mfEnd <= aInfo.mfStart ) + aInfo.mfEnd = aInfo.mfStart + aInfo.mfStep; + + return aInfo; +} + +ScDPDateGroupDlg::ScDPDateGroupDlg(weld::Window* pParent, + const ScDPNumGroupInfo& rInfo, sal_Int32 nDatePart, const Date& rNullDate) + : GenericDialogController(pParent, "modules/scalc/ui/groupbydate.ui", "PivotTableGroupByDate") + , mxRbAutoStart(m_xBuilder->weld_radio_button("auto_start")) + , mxRbManStart(m_xBuilder->weld_radio_button("manual_start")) + , mxEdStart(new SvtCalendarBox(m_xBuilder->weld_menu_button("start_date"))) + , mxRbAutoEnd(m_xBuilder->weld_radio_button("auto_end")) + , mxRbManEnd(m_xBuilder->weld_radio_button("manual_end")) + , mxEdEnd(new SvtCalendarBox(m_xBuilder->weld_menu_button("end_date"))) + , mxRbNumDays(m_xBuilder->weld_radio_button("days")) + , mxRbUnits(m_xBuilder->weld_radio_button("intervals")) + , mxEdNumDays(m_xBuilder->weld_spin_button("days_value")) + , mxLbUnits(m_xBuilder->weld_tree_view("interval_list")) + , mxBtnOk(m_xBuilder->weld_button("ok")) + , maStartHelper(*mxRbAutoStart, *mxRbManStart, *mxEdStart, rNullDate) + , maEndHelper(*mxRbAutoEnd, *mxRbManEnd, *mxEdEnd, rNullDate) +{ + maStartHelper.SetValue( rInfo.mbAutoStart, rInfo.mfStart ); + maEndHelper.SetValue( rInfo.mbAutoEnd, rInfo.mfEnd ); + + mxLbUnits->enable_toggle_buttons(weld::ColumnToggleType::Check); + + if( nDatePart == 0 ) + nDatePart = css::sheet::DataPilotFieldGroupBy::MONTHS; + for (size_t nIdx = 0; nIdx < SAL_N_ELEMENTS(aDatePartResIds); ++nIdx) + { + mxLbUnits->append(); + mxLbUnits->set_toggle(nIdx, (nDatePart & spnDateParts[ nIdx ]) ? TRISTATE_TRUE : TRISTATE_FALSE); + mxLbUnits->set_text(nIdx, ScResId(aDatePartResIds[nIdx]), 0); + } + + if( rInfo.mbDateValues ) + { + mxRbNumDays->set_active(true); + ToggleHdl(*mxRbNumDays ); + + double fNumDays = rInfo.mfStep; + if( fNumDays < 1.0 ) + fNumDays = 1.0; + else if( fNumDays > 32767.0 ) + fNumDays = 32767.0; + mxEdNumDays->set_value(fNumDays); + } + else + { + mxRbUnits->set_active(true); + ToggleHdl(*mxRbUnits); + } + + /* Set the initial focus, currently it is somewhere after calling all the radio + button click handlers. Now the first enabled editable control is focused. */ + if( mxEdStart->get_sensitive() ) + mxEdStart->grab_focus(); + else if( mxEdEnd->get_sensitive() ) + mxEdEnd->grab_focus(); + else if( mxEdNumDays->get_sensitive() ) + mxEdNumDays->grab_focus(); + else if( mxLbUnits->get_sensitive() ) + mxLbUnits->grab_focus(); + + mxRbNumDays->connect_toggled( LINK( this, ScDPDateGroupDlg, ToggleHdl ) ); + mxRbUnits->connect_toggled( LINK( this, ScDPDateGroupDlg, ToggleHdl ) ); + mxLbUnits->connect_toggled( LINK( this, ScDPDateGroupDlg, CheckHdl ) ); +} + +ScDPDateGroupDlg::~ScDPDateGroupDlg() +{ +} + +ScDPNumGroupInfo ScDPDateGroupDlg::GetGroupInfo() const +{ + ScDPNumGroupInfo aInfo; + aInfo.mbEnable = true; + aInfo.mbDateValues = mxRbNumDays->get_active(); + aInfo.mbAutoStart = maStartHelper.IsAuto(); + aInfo.mbAutoEnd = maEndHelper.IsAuto(); + + // get values and silently auto-correct them, if they are not valid + // TODO: error messages in OK event? + aInfo.mfStart = maStartHelper.GetValue(); + aInfo.mfEnd = maEndHelper.GetValue(); + sal_Int64 nNumDays = mxEdNumDays->get_value(); + aInfo.mfStep = static_cast<double>( aInfo.mbDateValues ? nNumDays : 0L ); + if( aInfo.mfEnd <= aInfo.mfStart ) + aInfo.mfEnd = aInfo.mfStart + nNumDays; + + return aInfo; +} + +sal_Int32 ScDPDateGroupDlg::GetDatePart() const +{ + // return DAYS for special "number of days" mode + if( mxRbNumDays->get_active() ) + return css::sheet::DataPilotFieldGroupBy::DAYS; + + // return listbox contents for "units" mode + sal_Int32 nDatePart = 0; + for (int nIdx = 0, nCount = mxLbUnits->n_children(); nIdx < nCount; ++nIdx ) + if (mxLbUnits->get_toggle(nIdx) == TRISTATE_TRUE) + nDatePart |= spnDateParts[ nIdx ]; + return nDatePart; +} + +IMPL_LINK(ScDPDateGroupDlg, ToggleHdl, weld::Toggleable&, rButton, void) +{ + if (!rButton.get_active()) + return; + if (mxRbNumDays->get_active()) + { + mxLbUnits->set_sensitive(false); + // enable and set focus to edit field on clicking "num of days" radio button + mxEdNumDays->set_sensitive(true); + mxEdNumDays->grab_focus(); + mxBtnOk->set_sensitive(true); + } + else if (mxRbUnits->get_active()) + { + mxEdNumDays->set_sensitive(false); + // enable and set focus to listbox on clicking "units" radio button + mxLbUnits->set_sensitive(true); + mxLbUnits->grab_focus(); + // disable OK button if no date part selected + Check(); + } +} + +namespace +{ + bool HasCheckedEntryCount(const weld::TreeView& rView) + { + for (int i = 0; i < rView.n_children(); ++i) + { + if (rView.get_toggle(i) == TRISTATE_TRUE) + return true; + } + return false; + } +} + +IMPL_LINK_NOARG(ScDPDateGroupDlg, CheckHdl, const weld::TreeView::iter_col&, void) +{ + Check(); +} + +void ScDPDateGroupDlg::Check() +{ + // enable/disable OK button on modifying check list box + mxBtnOk->set_sensitive(HasCheckedEntryCount(*mxLbUnits)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/filtdlg.cxx b/sc/source/ui/dbgui/filtdlg.cxx new file mode 100644 index 0000000000..ca9a59ec56 --- /dev/null +++ b/sc/source/ui/dbgui/filtdlg.cxx @@ -0,0 +1,1571 @@ +/* -*- 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 <sfx2/dispatch.hxx> +#include <sal/log.hxx> + +#include <uiitems.hxx> +#include <reffact.hxx> +#include <viewdata.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <scresid.hxx> +#include <queryentry.hxx> + +#include <foptmgr.hxx> + +#include <globstr.hrc> +#include <strings.hrc> + +#include <filtdlg.hxx> +#include <svx/colorwindow.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weld.hxx> +#include <svl/numformat.hxx> +#include <svl/sharedstringpool.hxx> + +#include <limits> + +#define QUERY_ENTRY_COUNT 4 +#define INVALID_HEADER_POS std::numeric_limits<size_t>::max() + +ScFilterDlg::EntryList::EntryList() : + mnHeaderPos(INVALID_HEADER_POS) {} + +ScFilterDlg::ScFilterDlg(SfxBindings* pB, SfxChildWindow* pCW, weld::Window* pParent, + const SfxItemSet& rArgSet) + : ScAnyRefDlgController(pB, pCW, pParent, + "modules/scalc/ui/standardfilterdialog.ui", "StandardFilterDialog") + , aStrUndefined(ScResId(SCSTR_UNDEFINED)) + , aStrNone(ScResId(SCSTR_NONE)) + , aStrEmpty(ScResId(SCSTR_FILTER_EMPTY)) + , aStrNotEmpty(ScResId(SCSTR_FILTER_NOTEMPTY)) + , aStrColumn(ScResId(SCSTR_COLUMN_LETTER)) + , aStrFontColor(ScResId(SCSTR_FILTER_FONT_COLOR_COND)) + , aStrBackgroundColor(ScResId(SCSTR_FILTER_BACKGROUND_COLOR_COND)) + , nWhichQuery(rArgSet.GetPool()->GetWhich(SID_QUERY)) + , theQueryData(static_cast<const ScQueryItem&>(rArgSet.Get(nWhichQuery)).GetQueryData()) + , pViewData(nullptr) + , pDoc(nullptr) + , nSrcTab(0) + , bRefInputMode(false) + , m_xLbConnect1(m_xBuilder->weld_combo_box("connect1")) + , m_xLbField1(m_xBuilder->weld_combo_box("field1")) + , m_xLbCond1(m_xBuilder->weld_combo_box("cond1")) + , m_xEdVal1(m_xBuilder->weld_combo_box("val1")) + , m_xLbColor1(m_xBuilder->weld_combo_box("color1")) + , m_xBtnRemove1(m_xBuilder->weld_button("remove1")) + , m_xLbConnect2(m_xBuilder->weld_combo_box("connect2")) + , m_xLbField2(m_xBuilder->weld_combo_box("field2")) + , m_xLbCond2(m_xBuilder->weld_combo_box("cond2")) + , m_xEdVal2(m_xBuilder->weld_combo_box("val2")) + , m_xLbColor2(m_xBuilder->weld_combo_box("color2")) + , m_xBtnRemove2(m_xBuilder->weld_button("remove2")) + , m_xLbConnect3(m_xBuilder->weld_combo_box("connect3")) + , m_xLbField3(m_xBuilder->weld_combo_box("field3")) + , m_xLbCond3(m_xBuilder->weld_combo_box("cond3")) + , m_xEdVal3(m_xBuilder->weld_combo_box("val3")) + , m_xLbColor3(m_xBuilder->weld_combo_box("color3")) + , m_xBtnRemove3(m_xBuilder->weld_button("remove3")) + , m_xLbConnect4(m_xBuilder->weld_combo_box("connect4")) + , m_xLbField4(m_xBuilder->weld_combo_box("field4")) + , m_xLbCond4(m_xBuilder->weld_combo_box("cond4")) + , m_xEdVal4(m_xBuilder->weld_combo_box("val4")) + , m_xLbColor4(m_xBuilder->weld_combo_box("color4")) + , m_xBtnRemove4(m_xBuilder->weld_button("remove4")) + , m_xContents(m_xBuilder->weld_widget("grid")) + , m_xScrollBar(m_xBuilder->weld_scrolled_window("scrollbar", true)) + , m_xExpander(m_xBuilder->weld_expander("more")) + , m_xBtnClear(m_xBuilder->weld_button("clear")) + , m_xBtnOk(m_xBuilder->weld_button("ok")) + , m_xBtnCancel(m_xBuilder->weld_button("cancel")) + , m_xBtnCase(m_xBuilder->weld_check_button("case")) + , m_xBtnRegExp(m_xBuilder->weld_check_button("regexp")) + , m_xBtnHeader(m_xBuilder->weld_check_button("header")) + , m_xBtnUnique(m_xBuilder->weld_check_button("unique")) + , m_xBtnCopyResult(m_xBuilder->weld_check_button("copyresult")) + , m_xLbCopyArea(m_xBuilder->weld_combo_box("lbcopyarea")) + , m_xEdCopyArea(new formula::RefEdit(m_xBuilder->weld_entry("edcopyarea"))) + , m_xRbCopyArea(new formula::RefButton(m_xBuilder->weld_button("rbcopyarea"))) + , m_xBtnDestPers(m_xBuilder->weld_check_button("destpers")) + , m_xFtDbAreaLabel(m_xBuilder->weld_label("dbarealabel")) + , m_xFtDbArea(m_xBuilder->weld_label("dbarea")) +{ + m_xExpander->connect_expanded(LINK(this, ScFilterDlg, MoreExpandedHdl)); + m_xEdCopyArea->SetReferences(this, m_xFtDbAreaLabel.get()); + m_xRbCopyArea->SetReferences(this, m_xEdCopyArea.get()); + + assert(m_xLbCond1->find_text(aStrFontColor) != -1); + assert(m_xLbCond1->find_text(aStrBackgroundColor) != -1); + + Init( rArgSet ); + + // Hack: RefInput control + pTimer.reset( new Timer("ScFilterTimer") ); + pTimer->SetTimeout( 50 ); // Wait 50ms + pTimer->SetInvokeHandler( LINK( this, ScFilterDlg, TimeOutHdl ) ); +} + +ScFilterDlg::~ScFilterDlg() +{ + pOptionsMgr.reset(); + pOutItem.reset(); + + // Hack: RefInput control + pTimer->Stop(); + pTimer.reset(); +} + +namespace { +VirtualDevice* lcl_getColorImage(const Color &rColor) +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + Size aImageSize(rStyleSettings.GetListBoxPreviewDefaultPixelSize()); + + VclPtrInstance<VirtualDevice> xDevice; + xDevice->SetOutputSize(aImageSize); + const tools::Rectangle aRect(Point(0, 0), aImageSize); + if (rColor == COL_NONE_COLOR) + { + const Color aW(COL_WHITE); + const Color aG(0xef, 0xef, 0xef); + xDevice->DrawCheckered(aRect.TopLeft(), aRect.GetSize(), 8, aW, aG); + xDevice->SetFillColor(); + } + else + { + xDevice->SetFillColor(rColor); + } + + xDevice->DrawRect(aRect); + + return xDevice.get(); +} +} + +void ScFilterDlg::Init( const SfxItemSet& rArgSet ) +{ + const ScQueryItem& rQueryItem = static_cast<const ScQueryItem&>( + rArgSet.Get( nWhichQuery )); + + m_xBtnClear->connect_clicked ( LINK( this, ScFilterDlg, BtnClearHdl ) ); + m_xBtnOk->connect_clicked ( LINK( this, ScFilterDlg, EndDlgHdl ) ); + m_xBtnCancel->connect_clicked ( LINK( this, ScFilterDlg, EndDlgHdl ) ); + m_xBtnHeader->connect_toggled ( LINK( this, ScFilterDlg, CheckBoxHdl ) ); + m_xBtnCase->connect_toggled ( LINK( this, ScFilterDlg, CheckBoxHdl ) ); + + m_xLbField1->connect_changed ( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbField2->connect_changed ( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbField3->connect_changed ( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbField4->connect_changed ( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbConnect1->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbConnect2->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbConnect3->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbConnect4->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + + m_xLbField1->append_text("0000000000"); + m_xLbField1->set_active(0); + auto nPrefWidth = m_xLbField1->get_preferred_size().Width(); + m_xLbField1->clear(); + + m_xLbField1->set_size_request(nPrefWidth, -1); + m_xLbField2->set_size_request(nPrefWidth, -1); + m_xLbField3->set_size_request(nPrefWidth, -1); + m_xLbField4->set_size_request(nPrefWidth, -1); + + m_xLbCond1->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbCond2->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbCond3->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbCond4->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + + m_xLbColor1->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbColor2->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbColor3->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + m_xLbColor4->connect_changed( LINK( this, ScFilterDlg, LbSelectHdl ) ); + + m_xBtnRemove1->connect_clicked( LINK( this, ScFilterDlg, BtnRemoveHdl ) ); + m_xBtnRemove2->connect_clicked( LINK( this, ScFilterDlg, BtnRemoveHdl ) ); + m_xBtnRemove3->connect_clicked( LINK( this, ScFilterDlg, BtnRemoveHdl ) ); + m_xBtnRemove4->connect_clicked( LINK( this, ScFilterDlg, BtnRemoveHdl ) ); + + pViewData = rQueryItem.GetViewData(); + pDoc = pViewData ? &pViewData->GetDocument() : nullptr; + nSrcTab = pViewData ? pViewData->GetTabNo() : static_cast<SCTAB>(0); + + // for easier access: + maFieldLbArr.reserve(QUERY_ENTRY_COUNT); + maFieldLbArr.push_back(m_xLbField1.get()); + maFieldLbArr.push_back(m_xLbField2.get()); + maFieldLbArr.push_back(m_xLbField3.get()); + maFieldLbArr.push_back(m_xLbField4.get()); + maValueEdArr.reserve(QUERY_ENTRY_COUNT); + maValueEdArr.push_back(m_xEdVal1.get()); + maValueEdArr.push_back(m_xEdVal2.get()); + maValueEdArr.push_back(m_xEdVal3.get()); + maValueEdArr.push_back(m_xEdVal4.get()); + maCondLbArr.reserve(QUERY_ENTRY_COUNT); + maCondLbArr.push_back(m_xLbCond1.get()); + maCondLbArr.push_back(m_xLbCond2.get()); + maCondLbArr.push_back(m_xLbCond3.get()); + maCondLbArr.push_back(m_xLbCond4.get()); + maConnLbArr.reserve(QUERY_ENTRY_COUNT); + maConnLbArr.push_back(m_xLbConnect1.get()); + maConnLbArr.push_back(m_xLbConnect2.get()); + maConnLbArr.push_back(m_xLbConnect3.get()); + maConnLbArr.push_back(m_xLbConnect4.get()); + maColorLbArr.reserve(QUERY_ENTRY_COUNT); + maColorLbArr.push_back(m_xLbColor1.get()); + maColorLbArr.push_back(m_xLbColor2.get()); + maColorLbArr.push_back(m_xLbColor3.get()); + maColorLbArr.push_back(m_xLbColor4.get()); + maRemoveBtnArr.reserve(QUERY_ENTRY_COUNT); + maRemoveBtnArr.push_back(m_xBtnRemove1.get()); + maRemoveBtnArr.push_back(m_xBtnRemove2.get()); + maRemoveBtnArr.push_back(m_xBtnRemove3.get()); + maRemoveBtnArr.push_back(m_xBtnRemove4.get()); + + // Option initialization: + pOptionsMgr.reset( new ScFilterOptionsMgr( + pViewData, + theQueryData, + m_xBtnCase.get(), + m_xBtnRegExp.get(), + m_xBtnHeader.get(), + m_xBtnUnique.get(), + m_xBtnCopyResult.get(), + m_xBtnDestPers.get(), + m_xLbCopyArea.get(), + m_xEdCopyArea.get(), + m_xRbCopyArea.get(), + m_xFtDbAreaLabel.get(), + m_xFtDbArea.get(), + aStrUndefined ) ); + // Read in field lists and select entries + + FillFieldLists(); + + for (size_t i = 0; i < QUERY_ENTRY_COUNT; ++i) + { + OUString aValStr; + size_t nCondPos = 0; + size_t nFieldSelPos = 0; + + maColorLbArr[i]->set_visible(false); + + ScQueryEntry& rEntry = theQueryData.GetEntry(i); + if ( rEntry.bDoQuery ) + { + nCondPos = static_cast<size_t>(rEntry.eOp); + nFieldSelPos = GetFieldSelPos( static_cast<SCCOL>(rEntry.nField) ); + if (rEntry.IsQueryByEmpty()) + { + aValStr = aStrEmpty; + maCondLbArr[i]->set_sensitive(false); + } + else if (rEntry.IsQueryByNonEmpty()) + { + aValStr = aStrNotEmpty; + maCondLbArr[i]->set_sensitive(false); + } + else if (rEntry.IsQueryByTextColor() || rEntry.IsQueryByBackgroundColor()) + { + nCondPos = maCondLbArr[i]->find_text( + rEntry.IsQueryByTextColor() ? aStrFontColor : aStrBackgroundColor); + maValueEdArr[i]->set_visible(false); + maColorLbArr[i]->set_visible(true); + maColorLbArr[i]->set_sensitive(true); + } + else + { + const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + OUString aQueryStr = rItem.maString.getString(); + SetValString(aQueryStr, rItem, aValStr); + } + } + else if ( i == 0 ) + { + nFieldSelPos = pViewData ? GetFieldSelPos(pViewData->GetCurX()) : 0; + rEntry.nField = nFieldSelPos ? (theQueryData.nCol1 + + static_cast<SCCOL>(nFieldSelPos) - 1) : static_cast<SCCOL>(0); + rEntry.bDoQuery=true; + if (maRefreshExceptQuery.size() < i + 1) + maRefreshExceptQuery.resize(i + 1, false); + maRefreshExceptQuery[i] = true; + + } + maFieldLbArr[i]->set_active( nFieldSelPos ); + maCondLbArr [i]->set_active( nCondPos ); + maValueEdArr[i]->set_entry_text( aValStr ); + maValueEdArr[i]->set_entry_completion(false); + maValueEdArr[i]->connect_changed( LINK( this, ScFilterDlg, ValModifyHdl ) ); + UpdateValueList(i+1); + UpdateColorList(i+1); + } + + m_xScrollBar->connect_vadjustment_changed( LINK( this, ScFilterDlg, ScrollHdl ) ); + m_xScrollBar->vadjustment_configure(0, 0, 8, 1, 3, 4); + Size aSize(m_xContents->get_preferred_size()); + m_xContents->set_size_request(aSize.Width(), aSize.Height()); + + m_xLbConnect1->hide(); + // Disable/Enable Logic: + + (m_xLbField1->get_active() != 0) + && (m_xLbField2->get_active() != 0) + ? m_xLbConnect2->set_active( static_cast<sal_uInt16>(theQueryData.GetEntry(1).eConnect) ) + : m_xLbConnect2->set_active(-1); + + (m_xLbField2->get_active() != 0) + && (m_xLbField3->get_active() != 0) + ? m_xLbConnect3->set_active( static_cast<sal_uInt16>(theQueryData.GetEntry(2).eConnect) ) + : m_xLbConnect3->set_active(-1); + + (m_xLbField3->get_active() != 0) + && (m_xLbField4->get_active() != 0) + ? m_xLbConnect4->set_active( static_cast<sal_uInt16>(theQueryData.GetEntry(3).eConnect) ) + : m_xLbConnect4->set_active(-1); + if ( m_xLbField1->get_active() == 0 ) + { + m_xLbConnect2->set_sensitive(false); + m_xLbField2->set_sensitive(false); + m_xLbCond2->set_sensitive(false); + m_xEdVal2->set_sensitive(false); + m_xLbColor2->set_sensitive(false); + m_xBtnRemove2->set_sensitive(false); + } + else if ( m_xLbConnect2->get_active() == -1 ) + { + m_xLbField2->set_sensitive(false); + m_xLbCond2->set_sensitive(false); + m_xEdVal2->set_sensitive(false); + m_xLbColor2->set_sensitive(false); + m_xBtnRemove2->set_sensitive(false); + } + + if ( m_xLbField2->get_active() == 0 ) + { + m_xLbConnect3->set_sensitive(false); + m_xLbField3->set_sensitive(false); + m_xLbCond3->set_sensitive(false); + m_xEdVal3->set_sensitive(false); + m_xLbColor3->set_sensitive(false); + m_xBtnRemove3->set_sensitive(false); + } + else if ( m_xLbConnect3->get_active() == -1 ) + { + m_xLbField3->set_sensitive(false); + m_xLbCond3->set_sensitive(false); + m_xEdVal3->set_sensitive(false); + m_xLbColor3->set_sensitive(false); + m_xBtnRemove3->set_sensitive(false); + } + if ( m_xLbField3->get_active() == 0 ) + { + m_xLbConnect4->set_sensitive(false); + m_xLbField4->set_sensitive(false); + m_xLbCond4->set_sensitive(false); + m_xEdVal4->set_sensitive(false); + m_xLbColor4->set_sensitive(false); + m_xBtnRemove4->set_sensitive(false); + } + else if ( m_xLbConnect4->get_active() == -1 ) + { + m_xLbField4->set_sensitive(false); + m_xLbCond4->set_sensitive(false); + m_xEdVal4->set_sensitive(false); + m_xLbColor4->set_sensitive(false); + m_xBtnRemove4->set_sensitive(false); + } + + m_xEdVal1->set_entry_width_chars(10); + m_xEdVal2->set_entry_width_chars(10); + m_xEdVal3->set_entry_width_chars(10); + m_xEdVal4->set_entry_width_chars(10); + + if (pDoc != nullptr && pDoc->GetChangeTrack() != nullptr) + m_xBtnCopyResult->set_sensitive(false); +} + +void ScFilterDlg::Close() +{ + if (pViewData) + pViewData->GetDocShell()->CancelAutoDBRange(); + + DoClose( ScFilterDlgWrapper::GetChildWindowId() ); +} + +// Mouse-selected cell area becomes the new selection and is shown in the +// reference text box + +void ScFilterDlg::SetReference( const ScRange& rRef, ScDocument& rDocP ) +{ + if ( bRefInputMode ) // Only possible if in reference edit mode + { + if ( rRef.aStart != rRef.aEnd ) + RefInputStart( m_xEdCopyArea.get() ); + OUString aRefStr(rRef.aStart.Format(ScRefFlags::ADDR_ABS_3D, &rDocP, rDocP.GetAddressConvention())); + m_xEdCopyArea->SetRefString( aRefStr ); + } +} + +void ScFilterDlg::SetActive() +{ + if ( bRefInputMode ) + { + m_xEdCopyArea->GrabFocus(); + m_xEdCopyArea->GetModifyHdl().Call( *m_xEdCopyArea ); + } + else + m_xDialog->grab_focus(); + + RefInputDone(); +} + +void ScFilterDlg::FillFieldLists() +{ + m_xLbField1->freeze(); + m_xLbField2->freeze(); + m_xLbField3->freeze(); + m_xLbField4->freeze(); + + m_xLbField1->clear(); + m_xLbField2->clear(); + m_xLbField3->clear(); + m_xLbField4->clear(); + m_xLbField1->append_text( aStrNone ); + m_xLbField2->append_text( aStrNone ); + m_xLbField3->append_text( aStrNone ); + m_xLbField4->append_text( aStrNone ); + + if ( pDoc ) + { + OUString aFieldName; + SCTAB nTab = nSrcTab; + SCCOL nFirstCol = theQueryData.nCol1; + SCROW nFirstRow = theQueryData.nRow1; + SCCOL nMaxCol = theQueryData.nCol2; + SCCOL col = 0; + + for ( col=nFirstCol; col<=nMaxCol; col++ ) + { + aFieldName = pDoc->GetString(col, nFirstRow, nTab); + if (!m_xBtnHeader->get_active() || aFieldName.isEmpty()) + { + aFieldName = ScGlobal::ReplaceOrAppend( aStrColumn, u"%1", ScColToAlpha( col )); + } + m_xLbField1->append_text( aFieldName ); + m_xLbField2->append_text( aFieldName ); + m_xLbField3->append_text( aFieldName ); + m_xLbField4->append_text( aFieldName ); + } + } + + m_xLbField4->thaw(); + m_xLbField3->thaw(); + m_xLbField2->thaw(); + m_xLbField1->thaw(); +} + +void ScFilterDlg::UpdateValueList( size_t nList ) +{ + bool bCaseSens = m_xBtnCase->get_active(); + + if (pDoc && nList > 0 && nList <= QUERY_ENTRY_COUNT) + { + weld::ComboBox* pValList = maValueEdArr[nList-1]; + const sal_Int32 nFieldSelPos = maFieldLbArr[nList-1]->get_active(); + OUString aCurValue = pValList->get_active_text(); + + std::unique_ptr<weld::WaitObject> xWaiter; + std::vector<weld::ComboBoxEntry> aEntries; + aEntries.emplace_back(aStrNotEmpty); + aEntries.emplace_back(aStrEmpty); + + if (nFieldSelPos) + { + xWaiter.reset(new weld::WaitObject(m_xDialog.get())); // even if only the list box has content + SCCOL nColumn = theQueryData.nCol1 + static_cast<SCCOL>(nFieldSelPos) - 1; + EntryList* pList = nullptr; + if (!m_EntryLists.count(nColumn)) + { + size_t nOffset = GetSliderPos(); + SCTAB nTab = nSrcTab; + SCROW nFirstRow = theQueryData.nRow1; + SCROW nLastRow = theQueryData.nRow2; + if (maHasDates.size() < nOffset+nList) + maHasDates.resize(nOffset+nList, false); + maHasDates[nOffset+nList-1] = false; + + // first without the first line + std::pair<EntryListsMap::iterator, bool> r = + m_EntryLists.insert(std::make_pair(nColumn, std::make_unique<EntryList>())); + if (!r.second) + // insertion failed. + return; + + pList = r.first->second.get(); + pDoc->GetFilterEntriesArea( + nColumn, nFirstRow+1, nLastRow, + nTab, bCaseSens, pList->maFilterEntries); + maHasDates[nOffset+nList-1] = pList->maFilterEntries.mbHasDates; + + // Entry for the first line + //! Entry (pHdrEntry) doesn't generate collection? + + pList->mnHeaderPos = INVALID_HEADER_POS; + ScFilterEntries aHdrColl; + pDoc->GetFilterEntriesArea( + nColumn, nFirstRow, nFirstRow, nTab, true, aHdrColl ); + if (!aHdrColl.empty()) + { + // See if the header value is already in the list. + std::vector<ScTypedStrData>::iterator itBeg = pList->maFilterEntries.begin(), itEnd = pList->maFilterEntries.end(); + if (std::none_of(itBeg, itEnd, FindTypedStrData(aHdrColl.front(), bCaseSens))) + { + // Not in the list. Insert it. + pList->maFilterEntries.push_back(aHdrColl.front()); + if (bCaseSens) + std::sort(pList->maFilterEntries.begin(), pList->maFilterEntries.end(), ScTypedStrData::LessCaseSensitive()); + else + std::sort(pList->maFilterEntries.begin(), pList->maFilterEntries.end(), ScTypedStrData::LessCaseInsensitive()); + + // Record its position. + itBeg = pList->maFilterEntries.begin(); + itEnd = pList->maFilterEntries.end(); + auto it = std::find_if(itBeg, itEnd, FindTypedStrData(aHdrColl.front(), bCaseSens)); + pList->mnHeaderPos = std::distance(itBeg, it); + } + } + } + else + pList = m_EntryLists[nColumn].get(); + + assert(pList); + + for (const auto& rEntry : pList->maFilterEntries) + aEntries.emplace_back(rEntry.GetString()); + } + pValList->insert_vector(aEntries, false); + pValList->set_entry_text(aCurValue); + } + + UpdateHdrInValueList( nList ); +} + +void ScFilterDlg::UpdateHdrInValueList( size_t nList ) +{ + //! GetText / SetText ?? + + if (!pDoc) + return; + + if (nList == 0 || nList > QUERY_ENTRY_COUNT) + return; + + size_t nFieldSelPos = maFieldLbArr[nList-1]->get_active(); + if (!nFieldSelPos) + return; + + SCCOL nColumn = theQueryData.nCol1 + static_cast<SCCOL>(nFieldSelPos) - 1; + if (!m_EntryLists.count(nColumn)) + { + OSL_FAIL("column not yet initialized"); + return; + } + + size_t const nPos = m_EntryLists[nColumn]->mnHeaderPos; + if (nPos == INVALID_HEADER_POS) + return; + + weld::ComboBox* pValList = maValueEdArr[nList-1]; + int nListPos = nPos + 2; // for "empty" and "non-empty" + + const ScTypedStrData& rHdrEntry = m_EntryLists[nColumn]->maFilterEntries.maStrData[nPos]; + + const OUString& aHdrStr = rHdrEntry.GetString(); + bool bWasThere = nListPos < pValList->get_count() && aHdrStr == pValList->get_text(nListPos); + bool bInclude = !m_xBtnHeader->get_active(); + + if (bInclude) // Include entry + { + if (!bWasThere) + pValList->insert_text(nListPos, aHdrStr); + } + else // Omit entry + { + if (bWasThere) + pValList->remove(nListPos); + } +} + +void ScFilterDlg::ClearValueList( size_t nList ) +{ + if (nList > 0 && nList <= QUERY_ENTRY_COUNT) + { + weld::ComboBox* pValList = maValueEdArr[nList-1]; + pValList->clear(); + pValList->append_text( aStrNotEmpty ); + pValList->append_text( aStrEmpty ); + pValList->set_entry_text( OUString() ); + } +} + +void ScFilterDlg::UpdateColorList(size_t nList) +{ + if (!pDoc || nList <= 0 || nList > QUERY_ENTRY_COUNT) + return; + + size_t nPos = nList - 1; + ScQueryEntry& rEntry = theQueryData.GetEntry(nPos); + const sal_Int32 nFieldSelPos = maFieldLbArr[nPos]->get_active(); + if (!nFieldSelPos) + return; + + SCCOL nColumn = theQueryData.nCol1 + static_cast<SCCOL>(nFieldSelPos) - 1; + EntryList* pList = m_EntryLists[nColumn].get(); + if (!pList) + return; + + std::set<Color> aColors; + OUString sSelectedCondition = maCondLbArr[nPos]->get_active_text(); + if (sSelectedCondition == aStrFontColor) + aColors = pList->maFilterEntries.getTextColors(); + else if (sSelectedCondition == aStrBackgroundColor) + aColors = pList->maFilterEntries.getBackgroundColors(); + else + return; + + maColorLbArr[nPos]->clear(); + for (const auto& rColor : aColors) + { + OUString sId = rColor.AsRGBHexString(); + if (rColor == COL_AUTO) + { + OUString sText = sSelectedCondition == aStrFontColor + ? ScResId(SCSTR_FILTER_AUTOMATIC_COLOR) + : ScResId(SCSTR_FILTER_NO_FILL); + maColorLbArr[nPos]->append(sId, sText); + } + else + { + VirtualDevice* pDev = lcl_getColorImage(rColor); + maColorLbArr[nPos]->append(sId, OUString(), *pDev); + } + + auto aItem = rEntry.GetQueryItem(); + if (aItem.maColor == rColor + && ((sSelectedCondition == aStrFontColor && aItem.meType == ScQueryEntry::ByTextColor) + || (sSelectedCondition == aStrBackgroundColor + && aItem.meType == ScQueryEntry::ByBackgroundColor))) + { + maColorLbArr[nPos]->set_active_id(sId); + } + } +} + +size_t ScFilterDlg::GetFieldSelPos( SCCOL nField ) +{ + if ( nField >= theQueryData.nCol1 && nField <= theQueryData.nCol2 ) + return static_cast<size_t>(nField - theQueryData.nCol1 + 1); + else + return 0; +} + +ScQueryItem* ScFilterDlg::GetOutputItem() +{ + ScAddress theCopyPos; + ScQueryParam theParam( theQueryData ); + bool bCopyPosOk = false; + + if ( m_xBtnCopyResult->get_active() ) + { + ScRefFlags nResult = theCopyPos.Parse( + m_xEdCopyArea->GetText(), *pDoc, pDoc->GetAddressConvention()); + bCopyPosOk = (nResult & ScRefFlags::VALID) == ScRefFlags::VALID; + } + + if ( m_xBtnCopyResult->get_active() && bCopyPosOk ) + { + theParam.bInplace = false; + theParam.nDestTab = theCopyPos.Tab(); + theParam.nDestCol = theCopyPos.Col(); + theParam.nDestRow = theCopyPos.Row(); + } + else + { + theParam.bInplace = true; + theParam.nDestTab = 0; + theParam.nDestCol = 0; + theParam.nDestRow = 0; + } + + theParam.bHasHeader = m_xBtnHeader->get_active(); + theParam.bByRow = true; + theParam.bDuplicate = !m_xBtnUnique->get_active(); + theParam.bCaseSens = m_xBtnCase->get_active(); + theParam.eSearchType = m_xBtnRegExp->get_active() ? utl::SearchParam::SearchType::Regexp : utl::SearchParam::SearchType::Normal; + theParam.bDestPers = m_xBtnDestPers->get_active(); + + // only set the three - reset everything else + + pOutItem.reset( new ScQueryItem( nWhichQuery, &theParam ) ); + + return pOutItem.get(); +} + +bool ScFilterDlg::IsRefInputMode() const +{ + return bRefInputMode; +} + +// Handler: + +IMPL_LINK( ScFilterDlg, BtnClearHdl, weld::Button&, rBtn, void ) +{ + if ( &rBtn != m_xBtnClear.get() ) + return; + + // scroll to the top + m_xScrollBar->vadjustment_set_value(0); + size_t nOffset = 0; + RefreshEditRow( nOffset); + + // clear all conditions + m_xLbConnect1->set_active(-1); + m_xLbConnect2->set_active(-1); + m_xLbConnect3->set_active(-1); + m_xLbConnect4->set_active(-1); + m_xLbField1->set_active(0); + m_xLbField2->set_active(0); + m_xLbField3->set_active(0); + m_xLbField4->set_active(0); + m_xLbCond1->set_active(0); + m_xLbCond2->set_active(0); + m_xLbCond3->set_active(0); + m_xLbCond4->set_active(0); + ClearValueList( 1 ); + ClearValueList( 2 ); + ClearValueList( 3 ); + ClearValueList( 4 ); + + // disable fields for second row onward + m_xLbConnect2->set_sensitive(false); + m_xLbConnect3->set_sensitive(false); + m_xLbConnect4->set_sensitive(false); + m_xLbField2->set_sensitive(false); + m_xLbField3->set_sensitive(false); + m_xLbField4->set_sensitive(false); + m_xLbCond2->set_sensitive(false); + m_xLbCond3->set_sensitive(false); + m_xLbCond4->set_sensitive(false); + m_xEdVal2->set_sensitive(false); + m_xEdVal3->set_sensitive(false); + m_xEdVal4->set_sensitive(false); + m_xLbColor2->set_sensitive(false); + m_xLbColor3->set_sensitive(false); + m_xLbColor4->set_sensitive(false); + m_xBtnRemove2->set_sensitive(false); + m_xBtnRemove3->set_sensitive(false); + m_xBtnRemove4->set_sensitive(false); + + // clear query data objects + SCSIZE nCount = theQueryData.GetEntryCount(); + if (maRefreshExceptQuery.size() < nCount + 1) + maRefreshExceptQuery.resize(nCount + 1, false); + for (SCSIZE i = 0; i < nCount; ++i) + { + theQueryData.GetEntry(i).bDoQuery = false; + maRefreshExceptQuery[i] = false; + theQueryData.GetEntry(i).nField = static_cast<SCCOL>(0); + } + maRefreshExceptQuery[0] = true; +} + +IMPL_LINK( ScFilterDlg, EndDlgHdl, weld::Button&, rBtn, void ) +{ + if ( &rBtn == m_xBtnOk.get() ) + { + bool bAreaInputOk = true; + + if ( m_xBtnCopyResult->get_active() ) + { + if ( !pOptionsMgr->VerifyPosStr( m_xEdCopyArea->GetText() ) ) + { + if (!m_xExpander->get_expanded()) + m_xExpander->set_expanded(true); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, + ScResId(STR_INVALID_TABREF))); + xBox->run(); + m_xEdCopyArea->GrabFocus(); + bAreaInputOk = false; + } + } + + if ( bAreaInputOk ) + { + SetDispatcherLock( false ); + SwitchToDocument(); + GetBindings().GetDispatcher()->ExecuteList(FID_FILTER_OK, + SfxCallMode::SLOT | SfxCallMode::RECORD, + { GetOutputItem() }); + response(RET_OK); + } + } + else if ( &rBtn == m_xBtnCancel.get() ) + { + response(RET_CANCEL); + } +} + +IMPL_LINK_NOARG(ScFilterDlg, MoreExpandedHdl, weld::Expander&, void) +{ + if ( m_xExpander->get_expanded() ) + pTimer->Start(); + else + { + pTimer->Stop(); + bRefInputMode = false; + //@BugID 54702 Enable/disable only in Basic class + //SFX_APPWINDOW->Disable(FALSE); //! general method in ScAnyRefDlg + } +} + +IMPL_LINK( ScFilterDlg, TimeOutHdl, Timer*, _pTimer, void ) +{ + // Check if RefInputMode is still true every 50ms + if (_pTimer == pTimer.get() && m_xDialog->has_toplevel_focus()) + bRefInputMode = (m_xEdCopyArea->GetWidget()->has_focus() || m_xRbCopyArea->GetWidget()->has_focus()); + + if ( m_xExpander->get_expanded() ) + pTimer->Start(); +} + +IMPL_LINK(ScFilterDlg, LbSelectHdl, weld::ComboBox&, rLb, void) +{ + /* + * Handle enable/disable logic depending on which ListBox was selected + */ + sal_uInt16 nOffset = GetSliderPos(); + + if ( &rLb == m_xLbConnect1.get() ) + { + m_xLbField1->set_sensitive(true); + m_xLbCond1->set_sensitive(true); + m_xEdVal1->set_sensitive(true); + m_xBtnRemove1->set_sensitive(true); + + const sal_Int32 nConnect1 = m_xLbConnect1->get_active(); + size_t nQE = nOffset; + theQueryData.GetEntry(nQE).eConnect =static_cast<ScQueryConnect>(nConnect1); + if (maRefreshExceptQuery.size() < nQE + 1) + maRefreshExceptQuery.resize(nQE + 1, false); + maRefreshExceptQuery[nQE] = true; + } + else if ( &rLb == m_xLbConnect2.get() ) + { + m_xLbField2->set_sensitive(true); + m_xLbCond2->set_sensitive(true); + m_xEdVal2->set_sensitive(true); + m_xBtnRemove2->set_sensitive(true); + + const sal_Int32 nConnect2 = m_xLbConnect2->get_active(); + size_t nQE = 1+nOffset; + theQueryData.GetEntry(nQE).eConnect =static_cast<ScQueryConnect>(nConnect2); + if (maRefreshExceptQuery.size() < nQE + 1) + maRefreshExceptQuery.resize(nQE + 1, false); + maRefreshExceptQuery[nQE]=true; + } + else if ( &rLb == m_xLbConnect3.get() ) + { + m_xLbField3->set_sensitive(true); + m_xLbCond3->set_sensitive(true); + m_xEdVal3->set_sensitive(true); + m_xBtnRemove3->set_sensitive(true); + + const sal_Int32 nConnect3 = m_xLbConnect3->get_active(); + size_t nQE = 2 + nOffset; + theQueryData.GetEntry(nQE).eConnect = static_cast<ScQueryConnect>(nConnect3); + if (maRefreshExceptQuery.size() < nQE + 1) + maRefreshExceptQuery.resize(nQE + 1, false); + maRefreshExceptQuery[nQE] = true; + + } + else if ( &rLb == m_xLbConnect4.get() ) + { + m_xLbField4->set_sensitive(true); + m_xLbCond4->set_sensitive(true); + m_xEdVal4->set_sensitive(true); + m_xLbColor4->set_sensitive(true); + m_xBtnRemove4->set_sensitive(true); + + const sal_Int32 nConnect4 = m_xLbConnect4->get_active(); + size_t nQE = 3 + nOffset; + theQueryData.GetEntry(nQE).eConnect = static_cast<ScQueryConnect>(nConnect4); + if (maRefreshExceptQuery.size() < nQE + 1) + maRefreshExceptQuery.resize(nQE + 1, false); + maRefreshExceptQuery[nQE] = true; + } + else if ( &rLb == m_xLbField1.get() ) + { + if ( m_xLbField1->get_active() == 0 ) + { + m_xLbConnect2->set_active(-1); + m_xLbConnect3->set_active(-1); + m_xLbConnect4->set_active(-1); + m_xLbField2->set_active( 0 ); + m_xLbField3->set_active( 0 ); + m_xLbField4->set_active( 0 ); + m_xLbCond2->set_active( 0 ); + m_xLbCond3->set_active( 0 ); + m_xLbCond4->set_active( 0 ); + ClearValueList( 1 ); + ClearValueList( 2 ); + ClearValueList( 3 ); + ClearValueList( 4 ); + + m_xLbConnect2->set_sensitive(false); + m_xLbConnect3->set_sensitive(false); + m_xLbConnect4->set_sensitive(false); + m_xLbField2->set_sensitive(false); + m_xLbField3->set_sensitive(false); + m_xLbField4->set_sensitive(false); + m_xLbCond2->set_sensitive(false); + m_xLbCond3->set_sensitive(false); + m_xLbCond4->set_sensitive(false); + m_xEdVal2->set_sensitive(false); + m_xEdVal3->set_sensitive(false); + m_xEdVal4->set_sensitive(false); + m_xLbColor2->set_sensitive(false); + m_xLbColor3->set_sensitive(false); + m_xLbColor4->set_sensitive(false); + m_xBtnRemove2->set_sensitive(false); + m_xBtnRemove3->set_sensitive(false); + m_xBtnRemove4->set_sensitive(false); + SCSIZE nCount = theQueryData.GetEntryCount(); + if (maRefreshExceptQuery.size() < nCount + 1) + maRefreshExceptQuery.resize(nCount + 1, false); + for (SCSIZE i = nOffset; i < nCount; ++i) + { + theQueryData.GetEntry(i).bDoQuery = false; + maRefreshExceptQuery[i] = false; + theQueryData.GetEntry(i).nField = static_cast<SCCOL>(0); + } + maRefreshExceptQuery[nOffset] = true; + } + else + { + UpdateValueList( 1 ); + UpdateColorList( 1 ); + if ( !m_xLbConnect2->get_sensitive() ) + { + m_xLbConnect2->set_sensitive(true); + } + theQueryData.GetEntry(nOffset).bDoQuery = true; + const sal_Int32 nField = rLb.get_active(); + theQueryData.GetEntry(nOffset).nField = theQueryData.nCol1 + static_cast<SCCOL>(nField) - 1 ; + } + } + else if ( &rLb == m_xLbField2.get() ) + { + if ( m_xLbField2->get_active() == 0 ) + { + m_xLbConnect3->set_active(-1); + m_xLbConnect4->set_active(-1); + m_xLbField3->set_active( 0 ); + m_xLbField4->set_active( 0 ); + m_xLbCond3->set_active( 0 ); + m_xLbCond4->set_active( 0 ); + ClearValueList( 2 ); + ClearValueList( 3 ); + ClearValueList( 4 ); + + m_xLbConnect3->set_sensitive(false); + m_xLbConnect4->set_sensitive(false); + m_xLbField3->set_sensitive(false); + m_xLbField4->set_sensitive(false); + m_xLbCond3->set_sensitive(false); + m_xLbCond4->set_sensitive(false); + m_xEdVal3->set_sensitive(false); + m_xEdVal4->set_sensitive(false); + m_xLbColor3->set_sensitive(false); + m_xLbColor4->set_sensitive(false); + m_xBtnRemove3->set_sensitive(false); + m_xBtnRemove4->set_sensitive(false); + + sal_uInt16 nTemp=nOffset+1; + SCSIZE nCount = theQueryData.GetEntryCount(); + if (maRefreshExceptQuery.size() < nCount) + maRefreshExceptQuery.resize(nCount, false); + for (SCSIZE i= nTemp; i< nCount; i++) + { + theQueryData.GetEntry(i).bDoQuery = false; + maRefreshExceptQuery[i] = false; + theQueryData.GetEntry(i).nField = static_cast<SCCOL>(0); + } + maRefreshExceptQuery[nTemp] = true; + } + else + { + UpdateValueList( 2 ); + UpdateColorList( 2 ); + if ( !m_xLbConnect3->get_sensitive() ) + { + m_xLbConnect3->set_sensitive(true); + } + const sal_Int32 nField = rLb.get_active(); + sal_uInt16 nQ=1+nOffset; + theQueryData.GetEntry(nQ).bDoQuery = true; + theQueryData.GetEntry(nQ).nField = theQueryData.nCol1 + static_cast<SCCOL>(nField) - 1 ; + } + } + else if ( &rLb == m_xLbField3.get() ) + { + if ( m_xLbField3->get_active() == 0 ) + { + m_xLbConnect4->set_active(-1); + m_xLbField4->set_active( 0 ); + m_xLbCond4->set_active( 0 ); + ClearValueList( 3 ); + ClearValueList( 4 ); + + m_xLbConnect4->set_sensitive(false); + m_xLbField4->set_sensitive(false); + m_xLbCond4->set_sensitive(false); + m_xEdVal4->set_sensitive(false); + m_xLbColor4->set_sensitive(false); + m_xBtnRemove4->set_sensitive(false); + + sal_uInt16 nTemp=nOffset+2; + SCSIZE nCount = theQueryData.GetEntryCount(); + if (maRefreshExceptQuery.size() < nCount) + maRefreshExceptQuery.resize(nCount, false); + for (SCSIZE i = nTemp; i < nCount; ++i) + { + theQueryData.GetEntry(i).bDoQuery = false; + maRefreshExceptQuery[i] = false; + theQueryData.GetEntry(i).nField = static_cast<SCCOL>(0); + } + maRefreshExceptQuery[nTemp] = true; + } + else + { + UpdateValueList( 3 ); + UpdateColorList( 3 ); + if ( !m_xLbConnect4->get_sensitive() ) + { + m_xLbConnect4->set_sensitive(true); + } + + const sal_Int32 nField = rLb.get_active(); + sal_uInt16 nQ=2+nOffset; + theQueryData.GetEntry(nQ).bDoQuery = true; + theQueryData.GetEntry(nQ).nField = theQueryData.nCol1 + static_cast<SCCOL>(nField) - 1 ; + + } + } + else if ( &rLb == m_xLbField4.get() ) + { + if ( m_xLbField4->get_active() == 0 ) + { + ClearValueList( 4 ); + sal_uInt16 nTemp=nOffset+3; + SCSIZE nCount = theQueryData.GetEntryCount(); + if (maRefreshExceptQuery.size() < nCount) + maRefreshExceptQuery.resize(nCount, false); + for (SCSIZE i = nTemp; i < nCount; ++i) + { + theQueryData.GetEntry(i).bDoQuery = false; + maRefreshExceptQuery[i] = false; + theQueryData.GetEntry(i).nField = static_cast<SCCOL>(0); + } + maRefreshExceptQuery[nTemp] = true; + } + else + { + UpdateValueList( 4 ); + UpdateColorList( 4 ); + const sal_Int32 nField = rLb.get_active(); + sal_uInt16 nQ=3+nOffset; + theQueryData.GetEntry(nQ).bDoQuery = true; + theQueryData.GetEntry(nQ).nField = theQueryData.nCol1 + static_cast<SCCOL>(nField) - 1 ; + } + + } + else if (&rLb == m_xLbCond1.get() || &rLb == m_xLbCond2.get() || &rLb == m_xLbCond3.get() + || &rLb == m_xLbCond4.get()) + { + ScQueryOp op; + sal_uInt16 nQ = 0; + bool bEnableColorLb = false; + if (rLb.get_active_text() == aStrFontColor || rLb.get_active_text() == aStrBackgroundColor) + { + bEnableColorLb = true; + op = SC_EQUAL; + } + else + { + op = static_cast<ScQueryOp>(rLb.get_active()); + } + + if (&rLb == m_xLbCond1.get()) + { + nQ = nOffset; + m_xLbColor1->set_visible(bEnableColorLb); + m_xLbColor1->set_sensitive(bEnableColorLb); + m_xEdVal1->set_visible(!bEnableColorLb); + UpdateColorList(1); + } + else if (&rLb == m_xLbCond2.get()) + { + nQ = 1 + nOffset; + m_xLbColor2->set_visible(bEnableColorLb); + m_xLbColor2->set_sensitive(bEnableColorLb); + m_xEdVal2->set_visible(!bEnableColorLb); + UpdateColorList(2); + } + else if (&rLb == m_xLbCond3.get()) + { + nQ = 2 + nOffset; + m_xLbColor3->set_visible(bEnableColorLb); + m_xLbColor3->set_sensitive(bEnableColorLb); + m_xEdVal3->set_visible(!bEnableColorLb); + UpdateColorList(3); + } + else if (&rLb == m_xLbCond4.get()) + { + nQ = 3 + nOffset; + m_xLbColor4->set_visible(bEnableColorLb); + m_xLbColor4->set_sensitive(bEnableColorLb); + m_xEdVal4->set_visible(!bEnableColorLb); + UpdateColorList(4); + } + + auto aEntry = theQueryData.GetEntry(nQ); + aEntry.eOp = op; + } + else if (&rLb == m_xLbColor1.get() || &rLb == m_xLbColor2.get() || &rLb == m_xLbColor3.get() + || &rLb == m_xLbColor4.get()) + { + sal_uInt16 nQ = 0; + if (&rLb == m_xLbColor1.get()) + { + nQ = nOffset; + } + else if (&rLb == m_xLbColor2.get()) + { + nQ = 1 + nOffset; + } + else if (&rLb == m_xLbColor3.get()) + { + nQ = 2 + nOffset; + } + else if (&rLb == m_xLbColor4.get()) + { + nQ = 3 + nOffset; + } + + ScQueryEntry& aEntry = theQueryData.GetEntry(nQ); + Color aColor = Color::STRtoRGB(maColorLbArr[nQ]->get_active_id()); + if (maCondLbArr[nQ]->get_active_text() == aStrFontColor) + { + aEntry.SetQueryByTextColor(aColor); + } + else if (maCondLbArr[nQ]->get_active_text() == aStrBackgroundColor) + { + aEntry.SetQueryByBackgroundColor(aColor); + } + } +} + +IMPL_LINK( ScFilterDlg, CheckBoxHdl, weld::Toggleable&, rBox, void ) +{ + // Column headers: + // Field list: Columnxx <-> column header string + // Value list: Column header value not applicable. + // Upper/lower case: + // Value list: completely new + + if ( &rBox == m_xBtnHeader.get() ) // Field list and value list + { + const sal_Int32 nCurSel1 = m_xLbField1->get_active(); + const sal_Int32 nCurSel2 = m_xLbField2->get_active(); + const sal_Int32 nCurSel3 = m_xLbField3->get_active(); + const sal_Int32 nCurSel4 = m_xLbField4->get_active(); + FillFieldLists(); + m_xLbField1->set_active( nCurSel1 ); + m_xLbField2->set_active( nCurSel2 ); + m_xLbField3->set_active( nCurSel3 ); + m_xLbField4->set_active( nCurSel4 ); + + UpdateHdrInValueList( 1 ); + UpdateHdrInValueList( 2 ); + UpdateHdrInValueList( 3 ); + UpdateHdrInValueList( 4 ); + } + + if ( &rBox != m_xBtnCase.get() ) // Complete value list + return; + + m_EntryLists.clear(); + UpdateValueList( 1 ); // current text is recorded + UpdateValueList( 2 ); + UpdateValueList( 3 ); + UpdateValueList( 4 ); + + UpdateColorList( 1 ); + UpdateColorList( 2 ); + UpdateColorList( 3 ); + UpdateColorList( 4 ); +} + +IMPL_LINK( ScFilterDlg, ValModifyHdl, weld::ComboBox&, rEd, void ) +{ + size_t nOffset = GetSliderPos(); + size_t i = 0; + size_t nQE = i + nOffset; + OUString aStrVal = rEd.get_active_text(); + weld::ComboBox* pLbCond = m_xLbCond1.get(); + weld::ComboBox* pLbField = m_xLbField1.get(); + if ( &rEd == m_xEdVal2.get() ) + { + pLbCond = m_xLbCond2.get(); + pLbField = m_xLbField2.get(); + i=1; + nQE=i+nOffset; + } + if ( &rEd == m_xEdVal3.get() ) + { + pLbCond = m_xLbCond3.get(); + pLbField = m_xLbField3.get(); + i=2; + nQE=i+nOffset; + } + if ( &rEd == m_xEdVal4.get() ) + { + pLbCond = m_xLbCond4.get(); + pLbField = m_xLbField4.get(); + i=3; + nQE=i+nOffset; + } + + if ( aStrEmpty == aStrVal || aStrNotEmpty == aStrVal ) + { + pLbCond->set_active_text(OUString('=')); + pLbCond->set_sensitive(false); + } + else + pLbCond->set_sensitive(true); + + if (maHasDates.size() < nQE + 1) + maHasDates.resize(nQE + 1, false); + if (maRefreshExceptQuery.size() < nQE + 1) + maRefreshExceptQuery.resize(nQE + 1, false); + + ScQueryEntry& rEntry = theQueryData.GetEntry( nQE ); + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + bool bDoThis = (pLbField->get_active() != 0); + rEntry.bDoQuery = bDoThis; + + if ( !(rEntry.bDoQuery || maRefreshExceptQuery[nQE]) ) + return; + + bool bByEmptyOrNotByEmpty = false; + if ( aStrEmpty == aStrVal ) + { + bByEmptyOrNotByEmpty = true; + rEntry.SetQueryByEmpty(); + } + else if ( aStrNotEmpty == aStrVal ) + { + bByEmptyOrNotByEmpty = true; + rEntry.SetQueryByNonEmpty(); + } + else + { + rItem.maString = pDoc->GetSharedStringPool().intern(aStrVal); + rItem.mfVal = 0.0; + rItem.meType = ScQueryEntry::ByString; + } + + const sal_Int32 nField = pLbField->get_active(); + rEntry.nField = nField ? (theQueryData.nCol1 + + static_cast<SCCOL>(nField) - 1) : static_cast<SCCOL>(0); + + ScQueryOp eOp = static_cast<ScQueryOp>(pLbCond->get_active()); + rEntry.eOp = eOp; + if (maHasDates[nQE] && !bByEmptyOrNotByEmpty) + rItem.meType = ScQueryEntry::ByDate; +} + +IMPL_LINK( ScFilterDlg, BtnRemoveHdl, weld::Button&, rBtn, void ) +{ + // Calculate the row to delete + sal_uInt16 nOffset = GetSliderPos(); + int nButtonIndex = 0; + if ( &rBtn == m_xBtnRemove2.get() ) + nButtonIndex = 1; + if ( &rBtn == m_xBtnRemove3.get() ) + nButtonIndex = 2; + if ( &rBtn == m_xBtnRemove4.get() ) + nButtonIndex = 3; + SCSIZE nRowToDelete = nOffset + nButtonIndex; + + // Check that the index is sensible + SCSIZE nCount = theQueryData.GetEntryCount(); + if (nRowToDelete >= nCount) + { + SAL_WARN( "sc", "ScFilterDlg::BtnRemoveHdl: could not delete row - invalid index."); + return; + } + + // Resize maRefreshExceptQuery + if (maRefreshExceptQuery.size() < nCount + 1) + maRefreshExceptQuery.resize(nCount + 1, false); + + // Move all the subsequent rows back one position; + // also find the last row, which we will delete + SCSIZE nRowToClear = nCount-1; + for (SCSIZE i = nRowToDelete; i < nCount-1; ++i) + { + if (theQueryData.GetEntry(i+1).bDoQuery) + { + theQueryData.GetEntry(i) = theQueryData.GetEntry(i+1); + } + else + { + nRowToClear = i; + break; + } + } + + // If the next row is being edited, but not confirmed, move it back + // one position + if (nRowToClear < nCount-1 && maRefreshExceptQuery[nRowToClear+1]) + { + theQueryData.GetEntry(nRowToClear) = theQueryData.GetEntry(nRowToClear+1); + maRefreshExceptQuery[nRowToClear] = true; + maRefreshExceptQuery[nRowToClear+1] = false; + } + else + { + // Remove the very last one, since everything has moved back + theQueryData.GetEntry(nRowToClear).bDoQuery = false; + theQueryData.GetEntry(nRowToClear).nField = static_cast<SCCOL>(0); + maRefreshExceptQuery[nRowToClear] = false; + } + + // Always enable the very first row + if (!theQueryData.GetEntry(0).bDoQuery) + { + maRefreshExceptQuery[0] = true; + } + + // Refresh the UI + RefreshEditRow( nOffset ); + + // Special handling if the very first row was cleared + if (!theQueryData.GetEntry(0).bDoQuery) + { + m_xLbConnect1->set_active(-1); + m_xLbField1->set_active(0); + m_xLbField1->set_sensitive(true); + m_xLbCond1->set_active(0); + m_xLbCond1->set_sensitive(true); + ClearValueList(1); + } +} + +IMPL_LINK_NOARG(ScFilterDlg, ScrollHdl, weld::ScrolledWindow&, void) +{ + SliderMoved(); +} + +void ScFilterDlg::SliderMoved() +{ + size_t nOffset = GetSliderPos(); + RefreshEditRow( nOffset); +} + +size_t ScFilterDlg::GetSliderPos() const +{ + return static_cast<size_t>(m_xScrollBar->vadjustment_get_value()); +} + +void ScFilterDlg::RefreshEditRow( size_t nOffset ) +{ + if (nOffset==0) + maConnLbArr[0]->hide(); + else + maConnLbArr[0]->show(); + + for (size_t i = 0; i < QUERY_ENTRY_COUNT; ++i) + { + OUString aValStr; + size_t nCondPos = 0; + size_t nFieldSelPos = 0; + size_t nQE = i + nOffset; + + maColorLbArr[i]->set_visible(false); + + if (maRefreshExceptQuery.size() < nQE + 1) + maRefreshExceptQuery.resize(nQE + 1, false); + + ScQueryEntry& rEntry = theQueryData.GetEntry( nQE); + if ( rEntry.bDoQuery || maRefreshExceptQuery[nQE] ) + { + nCondPos = static_cast<size_t>(rEntry.eOp); + if(rEntry.bDoQuery) + nFieldSelPos = GetFieldSelPos( static_cast<SCCOL>(rEntry.nField) ); + + const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + OUString aQueryStr = rItem.maString.getString(); + if (rEntry.IsQueryByEmpty()) + { + aValStr = aStrEmpty; + maCondLbArr[i]->set_sensitive(false); + } + else if (rEntry.IsQueryByNonEmpty()) + { + aValStr = aStrNotEmpty; + maCondLbArr[i]->set_sensitive(false); + } + else if (rEntry.IsQueryByTextColor() || rEntry.IsQueryByBackgroundColor()) + { + nCondPos = maCondLbArr[i]->find_text( + rEntry.IsQueryByTextColor() ? aStrFontColor : aStrBackgroundColor); + + maValueEdArr[i]->set_visible(false); + maColorLbArr[i]->set_visible(true); + maColorLbArr[i]->set_sensitive(true); + } + else + { + SetValString(aQueryStr, rItem, aValStr); + maCondLbArr[i]->set_sensitive(true); + } + maFieldLbArr[i]->set_sensitive(true); + maValueEdArr[i]->set_sensitive(true); + maRemoveBtnArr[i]->set_sensitive(true); + + if (nOffset==0) + { + if (i<3) + { + if(rEntry.bDoQuery) + maConnLbArr[i+1]->set_sensitive(true); + else + maConnLbArr[i+1]->set_sensitive(false); + size_t nQENext = nQE + 1; + if (maRefreshExceptQuery.size() < nQENext + 1) + maRefreshExceptQuery.resize(nQENext + 1, false); + if (theQueryData.GetEntry(nQENext).bDoQuery || maRefreshExceptQuery[nQENext]) + maConnLbArr[i+1]->set_active( static_cast<sal_uInt16>(theQueryData.GetEntry(nQENext).eConnect) ); + else + maConnLbArr[i+1]->set_active(-1); + } + } + else + { + if(theQueryData.GetEntry( nQE-1).bDoQuery) + maConnLbArr[i]->set_sensitive(true); + else + maConnLbArr[i]->set_sensitive(false); + + if (maRefreshExceptQuery.size() < nQE + 1) + maRefreshExceptQuery.resize(nQE + 1, false); + if(rEntry.bDoQuery || maRefreshExceptQuery[nQE]) + maConnLbArr[i]->set_active( static_cast<sal_uInt16>(rEntry.eConnect) ); + else + maConnLbArr[i]->set_active(-1); + } + + } + else + { + if (nOffset==0) + { + if(i<3) + { + maConnLbArr[i+1]->set_active(-1); + maConnLbArr[i+1]->set_sensitive(false); + } + } + else + { + if(theQueryData.GetEntry( nQE-1).bDoQuery) + maConnLbArr[i]->set_sensitive(true); + else + maConnLbArr[i]->set_sensitive(false); + maConnLbArr[i]->set_active(-1); + } + maFieldLbArr[i]->set_sensitive(false); + maCondLbArr[i]->set_sensitive(false); + maValueEdArr[i]->set_sensitive(false); + maRemoveBtnArr[i]->set_sensitive(false); + } + maFieldLbArr[i]->set_active( nFieldSelPos ); + maCondLbArr [i]->set_active( nCondPos ); + maValueEdArr[i]->set_entry_text( aValStr ); + UpdateValueList(i+1); + UpdateColorList(i+1); + } +} + +void ScFilterDlg::SetValString( const OUString& rQueryStr, const ScQueryEntry::Item& rItem, + OUString& rValStr ) +{ + if (rQueryStr.isEmpty()) + { + pDoc = pViewData ? &pViewData->GetDocument() : nullptr; + if (rItem.meType == ScQueryEntry::ByValue) + { + if (pDoc) + { + pDoc->GetFormatTable()->GetInputLineString(rItem.mfVal, 0, rValStr); + } + } + else if (rItem.meType == ScQueryEntry::ByDate) + { + if (pDoc) + { + SvNumberFormatter* pFormatter = pDoc->GetFormatTable(); + pFormatter->GetInputLineString(rItem.mfVal, + pFormatter->GetStandardFormat( SvNumFormatType::DATE), rValStr); + } + } + else + { + SAL_WARN( "sc", "ScFilterDlg::SetValString: empty query string, really?"); + rValStr = rQueryStr; + } + } + else + { + // XXX NOTE: if not ByString we just assume this has been + // set to a proper string corresponding to the numeric + // value earlier! + rValStr = rQueryStr; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/foptmgr.cxx b/sc/source/ui/dbgui/foptmgr.cxx new file mode 100644 index 0000000000..decaa622ba --- /dev/null +++ b/sc/source/ui/dbgui/foptmgr.cxx @@ -0,0 +1,260 @@ +/* -*- 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 <osl/diagnose.h> + +#include <rangeutl.hxx> +#include <dbdata.hxx> +#include <viewdata.hxx> +#include <document.hxx> +#include <queryparam.hxx> +#include <globalnames.hxx> + +#include <foptmgr.hxx> +#include <formula/funcutl.hxx> + +// ScFilterOptionsMgr (.ui's option helper) + +ScFilterOptionsMgr::ScFilterOptionsMgr( + ScViewData* ptrViewData, + const ScQueryParam& refQueryData, + weld::CheckButton* refBtnCase, + weld::CheckButton* refBtnRegExp, + weld::CheckButton* refBtnHeader, + weld::CheckButton* refBtnUnique, + weld::CheckButton* refBtnCopyResult, + weld::CheckButton* refBtnDestPers, + weld::ComboBox* refLbCopyArea, + formula::RefEdit* refEdCopyArea, + formula::RefButton* refRbCopyArea, + weld::Label* refFtDbAreaLabel, + weld::Label* refFtDbArea, + const OUString& refStrUndefined ) + + : pViewData ( ptrViewData ), + pDoc ( ptrViewData ? &ptrViewData->GetDocument() : nullptr ), + pBtnCase ( refBtnCase ), + pBtnRegExp ( refBtnRegExp ), + pBtnHeader ( refBtnHeader ), + pBtnUnique ( refBtnUnique ), + pBtnCopyResult ( refBtnCopyResult ), + pBtnDestPers ( refBtnDestPers ), + pLbCopyArea ( refLbCopyArea ), + pEdCopyArea ( refEdCopyArea ), + pRbCopyArea ( refRbCopyArea ), + pFtDbAreaLabel ( refFtDbAreaLabel ), + pFtDbArea ( refFtDbArea ), + rStrUndefined ( refStrUndefined ), + rQueryData ( refQueryData ) +{ + Init(); +} + +void ScFilterOptionsMgr::Init() +{ +//moggi:TODO + OSL_ENSURE( pViewData && pDoc, "Init failed :-/" ); + + pLbCopyArea->connect_changed( LINK( this, ScFilterOptionsMgr, LbAreaSelHdl ) ); + pEdCopyArea->SetModifyHdl ( LINK( this, ScFilterOptionsMgr, EdAreaModifyHdl ) ); + pBtnCopyResult->connect_toggled( LINK( this, ScFilterOptionsMgr, BtnCopyResultHdl ) ); + + pBtnCase->set_active( rQueryData.bCaseSens ); + pBtnHeader->set_active( rQueryData.bHasHeader ); + pBtnRegExp->set_active( rQueryData.eSearchType == utl::SearchParam::SearchType::Regexp ); + pBtnUnique->set_active( !rQueryData.bDuplicate ); + + if ( pViewData && pDoc ) + { + OUString theAreaStr; + ScRange theCurArea ( ScAddress( rQueryData.nCol1, + rQueryData.nRow1, + pViewData->GetTabNo() ), + ScAddress( rQueryData.nCol2, + rQueryData.nRow2, + pViewData->GetTabNo() ) ); + ScDBCollection* pDBColl = pDoc->GetDBCollection(); + OUString theDbArea; + OUString theDbName(STR_DB_LOCAL_NONAME); + const formula::FormulaGrammar::AddressConvention eConv = pDoc->GetAddressConvention(); + + theAreaStr = theCurArea.Format(*pDoc, ScRefFlags::RANGE_ABS_3D, eConv); + + // fill the target area list + + pLbCopyArea->clear(); + pLbCopyArea->append_text(rStrUndefined); + + ScAreaNameIterator aIter( *pDoc ); + OUString aName; + ScRange aRange; + while ( aIter.Next( aName, aRange ) ) + { + OUString aRefStr(aRange.aStart.Format(ScRefFlags::ADDR_ABS_3D, pDoc, eConv)); + pLbCopyArea->append(aRefStr, aName); + } + + pBtnDestPers->set_active(true); // always on when called + pLbCopyArea->set_active( 0 ); + pEdCopyArea->SetText( OUString() ); + + /* + * Check whether the transferred area is a database area: + */ + + theDbArea = theAreaStr; + + if ( pDBColl ) + { + ScAddress& rStart = theCurArea.aStart; + ScAddress& rEnd = theCurArea.aEnd; + const ScDBData* pDBData = pDBColl->GetDBAtArea( + rStart.Tab(), rStart.Col(), rStart.Row(), rEnd.Col(), rEnd.Row()); + + if ( pDBData ) + { + pBtnHeader->set_active( pDBData->HasHeader() ); + theDbName = pDBData->GetName(); + + pBtnHeader->set_sensitive(theDbName == STR_DB_LOCAL_NONAME); + } + } + + if ( theDbName != STR_DB_LOCAL_NONAME ) + { + theDbArea += " (" + theDbName + ")"; + + pFtDbArea->set_label( theDbArea ); + } + else + { + pFtDbAreaLabel->set_label( OUString() ); + pFtDbArea->set_label( OUString() ); + } + + // position to copy to: + + if ( !rQueryData.bInplace ) + { + OUString aString = + ScAddress( rQueryData.nDestCol, + rQueryData.nDestRow, + rQueryData.nDestTab + ).Format(ScRefFlags::ADDR_ABS_3D, pDoc, eConv); + + pBtnCopyResult->set_active(true); + pEdCopyArea->SetText( aString ); + EdAreaModifyHdl( *pEdCopyArea ); + pLbCopyArea->set_sensitive(true); + pEdCopyArea->GetWidget()->set_sensitive(true); + pRbCopyArea->GetWidget()->set_sensitive(true); + pBtnDestPers->set_sensitive(true); + } + else + { + pBtnCopyResult->set_active( false ); + pEdCopyArea->SetText( OUString() ); + pLbCopyArea->set_sensitive(false); + pEdCopyArea->GetWidget()->set_sensitive(false); + pRbCopyArea->GetWidget()->set_sensitive(false); + pBtnDestPers->set_sensitive(false); + } + } + else + pEdCopyArea->SetText( OUString() ); +} + +bool ScFilterOptionsMgr::VerifyPosStr( const OUString& rPosStr ) const +{ + OUString aPosStr( rPosStr ); + sal_Int32 nColonPos = aPosStr.indexOf( ':' ); + + if ( -1 != nColonPos ) + aPosStr = aPosStr.copy( 0, nColonPos ); + + ScRefFlags nResult = ScAddress().Parse( aPosStr, *pDoc, pDoc->GetAddressConvention() ); + + return (nResult & ScRefFlags::VALID) == ScRefFlags::VALID; +} + +// Handler: + +IMPL_LINK( ScFilterOptionsMgr, LbAreaSelHdl, weld::ComboBox&, rLb, void ) +{ + if ( &rLb == pLbCopyArea ) + { + OUString aString; + const sal_Int32 nSelPos = pLbCopyArea->get_active(); + + if ( nSelPos > 0 ) + aString = pLbCopyArea->get_id(nSelPos); + + pEdCopyArea->SetText( aString ); + } +} + +IMPL_LINK( ScFilterOptionsMgr, EdAreaModifyHdl, formula::RefEdit&, rEd, void ) +{ + if ( &rEd != pEdCopyArea ) + return; + + OUString theCurPosStr = rEd.GetText(); + ScRefFlags nResult = ScAddress().Parse( theCurPosStr, *pDoc, pDoc->GetAddressConvention() ); + + if ( (nResult & ScRefFlags::VALID) == ScRefFlags::VALID) + { + const sal_Int32 nCount = pLbCopyArea->get_count(); + + for ( sal_Int32 i=2; i<nCount; ++i ) + { + OUString aStr = pLbCopyArea->get_id(i); + if (theCurPosStr == aStr) + { + pLbCopyArea->set_active( i ); + return; + } + } + + } + pLbCopyArea->set_active( 0 ); +} + +IMPL_LINK( ScFilterOptionsMgr, BtnCopyResultHdl, weld::Toggleable&, rBox, void ) +{ + if ( &rBox != pBtnCopyResult ) + return; + + if ( rBox.get_active() ) + { + pBtnDestPers->set_sensitive(true); + pLbCopyArea->set_sensitive(true); + pEdCopyArea->GetWidget()->set_sensitive(true); + pRbCopyArea->GetWidget()->set_sensitive(true); + pEdCopyArea->GrabFocus(); + } + else + { + pBtnDestPers->set_sensitive(false); + pLbCopyArea->set_sensitive(false); + pEdCopyArea->GetWidget()->set_sensitive(false); + pRbCopyArea->GetWidget()->set_sensitive(false); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/imoptdlg.cxx b/sc/source/ui/dbgui/imoptdlg.cxx new file mode 100644 index 0000000000..2777eb9e45 --- /dev/null +++ b/sc/source/ui/dbgui/imoptdlg.cxx @@ -0,0 +1,140 @@ +/* -*- 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 <imoptdlg.hxx> +#include <asciiopt.hxx> +#include <comphelper/string.hxx> +#include <unotools/charclass.hxx> +#include <osl/thread.h> +#include <o3tl/string_view.hxx> +#include <global.hxx> + +const char pStrFix[] = "FIX"; + +// The option string can no longer contain a semicolon (because of pick list), +// therefore, starting with version 336 comma instead + +ScImportOptions::ScImportOptions( std::u16string_view rStr ) +{ + // Use the same string format as ScAsciiOptions, + // because the import options string is passed here when a CSV file is loaded and saved again. + // The old format is still supported because it might be used in macros. + + bFixedWidth = false; + nFieldSepCode = 0; + nTextSepCode = 0; + eCharSet = RTL_TEXTENCODING_DONTKNOW; + bSaveAsShown = true; // "true" if not in string (after CSV import) + bQuoteAllText = false; + bSaveNumberAsSuch = true; + bSaveFormulas = false; + bRemoveSpace = false; + nSheetToExport = 0; + bEvaluateFormulas = true; // true if not present at all, for compatibility + bIncludeBOM = false; + sal_Int32 nTokenCount = comphelper::string::getTokenCount(rStr, ','); + if ( nTokenCount < 3 ) + return; + + sal_Int32 nIdx{ 0 }; + // first 3 tokens: common + OUString aToken( o3tl::getToken(rStr, 0, ',', nIdx ) ); + if( aToken.equalsIgnoreAsciiCase( pStrFix ) ) + bFixedWidth = true; + else + nFieldSepCode = ScAsciiOptions::GetWeightedFieldSep( aToken, true); + nTextSepCode = static_cast<sal_Unicode>(o3tl::toInt32(o3tl::getToken(rStr, 0, ',', nIdx))); + aStrFont = o3tl::getToken(rStr, 0, ',', nIdx); + eCharSet = ScGlobal::GetCharsetValue(aStrFont); + + if ( nTokenCount == 4 ) + { + // compatibility with old options string: "Save as shown" as 4th token, numeric + bSaveAsShown = o3tl::toInt32(o3tl::getToken(rStr, 0, ',', nIdx)) != 0; + bQuoteAllText = true; // use old default then + } + else + { + // look at the same positions as in ScAsciiOptions + if ( nTokenCount >= 7 ) + bQuoteAllText = o3tl::getToken(rStr, 3, ',', nIdx) == u"true"; // 7th token + if ( nTokenCount >= 8 ) + bSaveNumberAsSuch = o3tl::getToken(rStr, 0, ',', nIdx) == u"true"; + if ( nTokenCount >= 9 ) + bSaveAsShown = o3tl::getToken(rStr, 0, ',', nIdx) == u"true"; + if ( nTokenCount >= 10 ) + bSaveFormulas = o3tl::getToken(rStr, 0, ',', nIdx) == u"true"; + if ( nTokenCount >= 11 ) + bRemoveSpace = o3tl::getToken(rStr, 0, ',', nIdx) == u"true"; + if ( nTokenCount >= 12 ) + { + const OUString aTok(o3tl::getToken(rStr,0, ',', nIdx)); + if (aTok == "-1") + nSheetToExport = -1; // all + else if (aTok.isEmpty() || CharClass::isAsciiNumeric(aTok)) + nSheetToExport = aTok.toInt32(); + else + nSheetToExport = -23; // invalid, force error + } + if ( nTokenCount >= 13 ) + // If present, defaults to "false". + bEvaluateFormulas = o3tl::getToken(rStr, 0, ',', nIdx) == u"true"; + if (nTokenCount >= 14) + bIncludeBOM = o3tl::getToken(rStr, 0, ',', nIdx) == u"true"; + } +} + +OUString ScImportOptions::BuildString() const +{ + OUString aResult; + + if( bFixedWidth ) + aResult += pStrFix; + else + aResult += OUString::number(nFieldSepCode); + aResult += "," + OUString::number(nTextSepCode) + "," + aStrFont + + // use the same string format as ScAsciiOptions: + ",1,,0," + // first row, no column info, default language + OUString::boolean( bQuoteAllText ) + // same as "quoted field as text" in ScAsciiOptions + "," + + OUString::boolean( bSaveNumberAsSuch ) + // "save number as such": not in ScAsciiOptions + "," + + OUString::boolean( bSaveAsShown ) + // "save as shown": not in ScAsciiOptions + "," + + OUString::boolean( bSaveFormulas ) + // "save formulas": not in ScAsciiOptions + "," + + OUString::boolean( bRemoveSpace ) + // same as "Remove space" in ScAsciiOptions + "," + + OUString::number(nSheetToExport) + // Only available for command line --convert-to + "," + + OUString::boolean( bEvaluateFormulas ) + // same as "Evaluate formulas" in ScAsciiOptions + "," + + OUString::boolean(bIncludeBOM) ; // same as "Include BOM" in ScAsciiOptions + + return aResult; +} + +void ScImportOptions::SetTextEncoding( rtl_TextEncoding nEnc ) +{ + eCharSet = (nEnc == RTL_TEXTENCODING_DONTKNOW ? + osl_getThreadTextEncoding() : nEnc); + aStrFont = ScGlobal::GetCharsetString( nEnc ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/pfiltdlg.cxx b/sc/source/ui/dbgui/pfiltdlg.cxx new file mode 100644 index 0000000000..e87d676b59 --- /dev/null +++ b/sc/source/ui/dbgui/pfiltdlg.cxx @@ -0,0 +1,507 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <viewdata.hxx> +#include <document.hxx> +#include <uiitems.hxx> +#include <global.hxx> +#include <globalnames.hxx> +#include <dbdata.hxx> +#include <scresid.hxx> +#include <queryentry.hxx> +#include <filterentries.hxx> + +#include <sc.hrc> +#include <strings.hrc> + +#include <pfiltdlg.hxx> +#include <svl/sharedstringpool.hxx> +#include <osl/diagnose.h> + +ScPivotFilterDlg::ScPivotFilterDlg(weld::Window* pParent, const SfxItemSet& rArgSet, + SCTAB nSourceTab ) + : GenericDialogController(pParent, "modules/scalc/ui/pivotfilterdialog.ui", "PivotFilterDialog") + , aStrNone(ScResId(SCSTR_NONE)) + , aStrEmpty(ScResId(SCSTR_FILTER_EMPTY)) + , aStrNotEmpty(ScResId(SCSTR_FILTER_NOTEMPTY)) + , aStrColumn(ScResId(SCSTR_COLUMN_LETTER)) + , nWhichQuery(rArgSet.GetPool()->GetWhich(SID_QUERY)) + , theQueryData(static_cast<const ScQueryItem&>(rArgSet.Get(nWhichQuery)).GetQueryData()) + , pViewData(nullptr) + , pDoc(nullptr) + , nSrcTab(nSourceTab) // is not in QueryParam + , m_xLbField1(m_xBuilder->weld_combo_box("field1")) + , m_xLbCond1(m_xBuilder->weld_combo_box("cond1")) + , m_xEdVal1(m_xBuilder->weld_combo_box("val1")) + , m_xLbConnect1(m_xBuilder->weld_combo_box("connect1")) + , m_xLbField2(m_xBuilder->weld_combo_box("field2")) + , m_xLbCond2(m_xBuilder->weld_combo_box("cond2")) + , m_xEdVal2(m_xBuilder->weld_combo_box("val2")) + , m_xLbConnect2(m_xBuilder->weld_combo_box("connect2")) + , m_xLbField3(m_xBuilder->weld_combo_box("field3")) + , m_xLbCond3(m_xBuilder->weld_combo_box("cond3")) + , m_xEdVal3(m_xBuilder->weld_combo_box("val3")) + , m_xBtnCase(m_xBuilder->weld_check_button("case")) + , m_xBtnRegExp(m_xBuilder->weld_check_button("regexp")) + , m_xBtnUnique(m_xBuilder->weld_check_button("unique")) + , m_xFtDbArea(m_xBuilder->weld_label("dbarea")) +{ + Init( rArgSet ); +} + +ScPivotFilterDlg::~ScPivotFilterDlg() +{ +} + +void ScPivotFilterDlg::Init( const SfxItemSet& rArgSet ) +{ + const ScQueryItem& rQueryItem = static_cast<const ScQueryItem&>( + rArgSet.Get( nWhichQuery )); + + m_xBtnCase->connect_toggled( LINK( this, ScPivotFilterDlg, CheckBoxHdl ) ); + + m_xLbField1->connect_changed ( LINK( this, ScPivotFilterDlg, LbSelectHdl ) ); + m_xLbField2->connect_changed ( LINK( this, ScPivotFilterDlg, LbSelectHdl ) ); + m_xLbField3->connect_changed ( LINK( this, ScPivotFilterDlg, LbSelectHdl ) ); + m_xLbConnect1->connect_changed( LINK( this, ScPivotFilterDlg, LbSelectHdl ) ); + m_xLbConnect2->connect_changed( LINK( this, ScPivotFilterDlg, LbSelectHdl ) ); + + m_xBtnCase->set_active( theQueryData.bCaseSens ); + m_xBtnRegExp->set_active( theQueryData.eSearchType == utl::SearchParam::SearchType::Regexp ); + m_xBtnUnique->set_active( !theQueryData.bDuplicate ); + + pViewData = rQueryItem.GetViewData(); + pDoc = pViewData ? &pViewData->GetDocument() : nullptr; + + // for easier access: + aFieldLbArr [0] = m_xLbField1.get(); + aFieldLbArr [1] = m_xLbField2.get(); + aFieldLbArr [2] = m_xLbField3.get(); + aValueEdArr [0] = m_xEdVal1.get(); + aValueEdArr [1] = m_xEdVal2.get(); + aValueEdArr [2] = m_xEdVal3.get(); + aCondLbArr [0] = m_xLbCond1.get(); + aCondLbArr [1] = m_xLbCond2.get(); + aCondLbArr [2] = m_xLbCond3.get(); + + if ( pViewData && pDoc ) + { + ScRange theCurArea ( ScAddress( theQueryData.nCol1, + theQueryData.nRow1, + nSrcTab ), + ScAddress( theQueryData.nCol2, + theQueryData.nRow2, + nSrcTab ) ); + ScDBCollection* pDBColl = pDoc->GetDBCollection(); + OUString theDbName = STR_DB_LOCAL_NONAME; + + // Check if the passed range is a database range + + if ( pDBColl ) + { + ScAddress& rStart = theCurArea.aStart; + ScAddress& rEnd = theCurArea.aEnd; + ScDBData* pDBData = pDBColl->GetDBAtArea( rStart.Tab(), + rStart.Col(), rStart.Row(), + rEnd.Col(), rEnd.Row() ); + if ( pDBData ) + theDbName = pDBData->GetName(); + } + + OUString sLabel = " (" + theDbName + ")"; + m_xFtDbArea->set_label(sLabel); + } + else + { + m_xFtDbArea->set_label(OUString()); + } + + // Read the field lists and select the entries: + + FillFieldLists(); + + for ( SCSIZE i=0; i<3; i++ ) + { + if ( theQueryData.GetEntry(i).bDoQuery ) + { + const ScQueryEntry& rEntry = theQueryData.GetEntry(i); + const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + OUString aValStr = rItem.maString.getString(); + if (rEntry.IsQueryByEmpty()) + aValStr = aStrEmpty; + else if (rEntry.IsQueryByNonEmpty()) + aValStr = aStrNotEmpty; + sal_uInt16 nCondPos = static_cast<sal_uInt16>(rEntry.eOp); + sal_uInt16 nFieldSelPos = GetFieldSelPos( static_cast<SCCOL>(rEntry.nField) ); + + aFieldLbArr[i]->set_active( nFieldSelPos ); + aCondLbArr [i]->set_active( nCondPos ); + UpdateValueList( static_cast<sal_uInt16>(i+1) ); + aValueEdArr[i]->set_entry_text(aValStr); + if (aValStr == aStrEmpty || aValStr == aStrNotEmpty) + aCondLbArr[i]->set_sensitive(false); + } + else + { + aFieldLbArr[i]->set_active( 0 ); // "none" selected + aCondLbArr [i]->set_active( 0 ); // "=" selected + UpdateValueList( static_cast<sal_uInt16>(i) ); + aValueEdArr[i]->set_entry_text(OUString()); + } + aValueEdArr[i]->connect_changed( LINK( this, ScPivotFilterDlg, ValModifyHdl ) ); + } + + // disable/enable logic: + + if (m_xLbField1->get_active() != 0 && m_xLbField2->get_active() != 0) + m_xLbConnect1->set_active( static_cast<sal_uInt16>(theQueryData.GetEntry(1).eConnect) ); + else + m_xLbConnect1->set_active(-1); + + if (m_xLbField2->get_active() != 0 && m_xLbField3->get_active() != 0) + m_xLbConnect2->set_active( static_cast<sal_uInt16>(theQueryData.GetEntry(2).eConnect) ); + else + m_xLbConnect2->set_active(-1); + + if (m_xLbField1->get_active() == 0) + { + m_xLbConnect1->set_sensitive(false); + m_xLbField2->set_sensitive(false); + m_xLbCond2->set_sensitive(false); + m_xEdVal2->set_sensitive(false); + } + else if (m_xLbConnect1->get_active() == -1) + { + m_xLbField2->set_sensitive(false); + m_xLbCond2->set_sensitive(false); + m_xEdVal2->set_sensitive(false); + } + + if (m_xLbField2->get_active() == 0) + { + m_xLbConnect2->set_sensitive(false); + m_xLbField3->set_sensitive(false); + m_xLbCond3->set_sensitive(false); + m_xEdVal3->set_sensitive(false); + } + else if (m_xLbConnect2->get_active() == -1) + { + m_xLbField3->set_sensitive(false); + m_xLbCond3->set_sensitive(false); + m_xEdVal3->set_sensitive(false); + } +} + +void ScPivotFilterDlg::FillFieldLists() +{ + m_xLbField1->clear(); + m_xLbField2->clear(); + m_xLbField3->clear(); + m_xLbField1->append_text(aStrNone); + m_xLbField2->append_text(aStrNone); + m_xLbField3->append_text(aStrNone); + + if ( !pDoc ) + return; + + OUString aFieldName; + SCTAB nTab = nSrcTab; + SCCOL nFirstCol = theQueryData.nCol1; + SCROW nFirstRow = theQueryData.nRow1; + SCCOL nMaxCol = theQueryData.nCol2; + SCCOL col = 0; + + for ( col=nFirstCol; col<=nMaxCol; col++ ) + { + aFieldName = pDoc->GetString(col, nFirstRow, nTab); + if ( aFieldName.isEmpty() ) + { + aFieldName = ScGlobal::ReplaceOrAppend( aStrColumn, u"%1", ScColToAlpha( col )); + } + m_xLbField1->append_text(aFieldName); + m_xLbField2->append_text(aFieldName); + m_xLbField3->append_text(aFieldName); + } +} + +void ScPivotFilterDlg::UpdateValueList( sal_uInt16 nList ) +{ + if ( !(pDoc && nList>0 && nList<=3) ) + return; + + weld::ComboBox* pValList = aValueEdArr[nList-1]; + sal_Int32 nFieldSelPos = aFieldLbArr[nList-1]->get_active(); + OUString aCurValue = pValList->get_active_text(); + + pValList->clear(); + pValList->append_text(aStrNotEmpty); + pValList->append_text(aStrEmpty); + + if ( pDoc && nFieldSelPos ) + { + SCCOL nColumn = theQueryData.nCol1 + static_cast<SCCOL>(nFieldSelPos) - 1; + if (!m_pEntryLists[nColumn]) + { + weld::WaitObject aWaiter(m_xDialog.get()); + + SCTAB nTab = nSrcTab; + SCROW nFirstRow = theQueryData.nRow1; + SCROW nLastRow = theQueryData.nRow2; + nFirstRow++; + bool bCaseSens = m_xBtnCase->get_active(); + m_pEntryLists[nColumn].reset( new ScFilterEntries); + pDoc->GetFilterEntriesArea( + nColumn, nFirstRow, nLastRow, nTab, bCaseSens, *m_pEntryLists[nColumn]); + } + + const ScFilterEntries* pColl = m_pEntryLists[nColumn].get(); + for (const auto& rEntry : *pColl) + { + pValList->append_text(rEntry.GetString()); + } + } + pValList->set_entry_text(aCurValue); +} + +void ScPivotFilterDlg::ClearValueList( sal_uInt16 nList ) +{ + if ( nList>0 && nList<=3 ) + { + weld::ComboBox* pValList = aValueEdArr[nList-1]; + pValList->clear(); + pValList->append_text(aStrNotEmpty); + pValList->append_text(aStrEmpty); + pValList->set_entry_text(OUString()); + } +} + +sal_uInt16 ScPivotFilterDlg::GetFieldSelPos( SCCOL nField ) +{ + if ( nField >= theQueryData.nCol1 && nField <= theQueryData.nCol2 ) + return static_cast<sal_uInt16>(nField - theQueryData.nCol1 + 1); + else + return 0; +} + +const ScQueryItem& ScPivotFilterDlg::GetOutputItem() +{ + ScQueryParam theParam( theQueryData ); + sal_Int32 nConnect1 = m_xLbConnect1->get_active(); + sal_Int32 nConnect2 = m_xLbConnect2->get_active(); + + svl::SharedStringPool& rPool = pViewData->GetDocument().GetSharedStringPool(); + + for ( SCSIZE i=0; i<3; i++ ) + { + const sal_Int32 nField = aFieldLbArr[i]->get_active(); + ScQueryOp eOp = static_cast<ScQueryOp>(aCondLbArr[i]->get_active()); + + bool bDoThis = (aFieldLbArr[i]->get_active() != 0); + theParam.GetEntry(i).bDoQuery = bDoThis; + + if ( bDoThis ) + { + ScQueryEntry& rEntry = theParam.GetEntry(i); + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + + OUString aStrVal = aValueEdArr[i]->get_active_text(); + + /* + * The dialog returns the specific field values "empty"/"non empty" + * as constant in nVal in connection with the bQueryByString switch + * set to false + */ + if ( aStrVal == aStrEmpty ) + { + OSL_ASSERT(eOp == SC_EQUAL); + rEntry.SetQueryByEmpty(); + } + else if ( aStrVal == aStrNotEmpty ) + { + OSL_ASSERT(eOp == SC_EQUAL); + rEntry.SetQueryByNonEmpty(); + } + else + { + rItem.maString = rPool.intern(aStrVal); + rItem.mfVal = 0.0; + rItem.meType = ScQueryEntry::ByString; + } + + rEntry.nField = nField ? (theQueryData.nCol1 + + static_cast<SCCOL>(nField) - 1) : static_cast<SCCOL>(0); + rEntry.eOp = eOp; + } + } + + theParam.GetEntry(1).eConnect = (nConnect1 != -1) + ? static_cast<ScQueryConnect>(nConnect1) + : SC_AND; + theParam.GetEntry(2).eConnect = (nConnect2 != -1) + ? static_cast<ScQueryConnect>(nConnect2) + : SC_AND; + + theParam.bInplace = false; + theParam.nDestTab = 0; // Where do those values come from? + theParam.nDestCol = 0; + theParam.nDestRow = 0; + + theParam.bDuplicate = !m_xBtnUnique->get_active(); + theParam.bCaseSens = m_xBtnCase->get_active(); + theParam.eSearchType = m_xBtnRegExp->get_active() ? utl::SearchParam::SearchType::Regexp : utl::SearchParam::SearchType::Normal; + + pOutItem.reset( new ScQueryItem( nWhichQuery, &theParam ) ); + + return *pOutItem; +} + +// Handler: + +IMPL_LINK( ScPivotFilterDlg, LbSelectHdl, weld::ComboBox&, rLb, void ) +{ + /* + * Handling the enable/disable logic based on which ListBox was touched: + */ + if (&rLb == m_xLbConnect1.get()) + { + if ( !m_xLbField2->get_sensitive() ) + { + m_xLbField2->set_sensitive(true); + m_xLbCond2->set_sensitive(true); + m_xEdVal2->set_sensitive(true); + } + } + else if (&rLb == m_xLbConnect2.get()) + { + if ( !m_xLbField3->get_sensitive() ) + { + m_xLbField3->set_sensitive(true); + m_xLbCond3->set_sensitive(true); + m_xEdVal3->set_sensitive(true); + } + } + else if (&rLb == m_xLbField1.get()) + { + if ( m_xLbField1->get_active() == 0 ) + { + m_xLbConnect1->set_active(-1); + m_xLbConnect2->set_active(-1); + m_xLbField2->set_active( 0 ); + m_xLbField3->set_active( 0 ); + m_xLbCond2->set_active( 0 ); + m_xLbCond3->set_active( 0 ); + ClearValueList( 1 ); + ClearValueList( 2 ); + ClearValueList( 3 ); + + m_xLbConnect1->set_sensitive(false); + m_xLbConnect2->set_sensitive(false); + m_xLbField2->set_sensitive(false); + m_xLbField3->set_sensitive(false); + m_xLbCond2->set_sensitive(false); + m_xLbCond3->set_sensitive(false); + m_xEdVal2->set_sensitive(false); + m_xEdVal3->set_sensitive(false); + } + else + { + UpdateValueList( 1 ); + if ( !m_xLbConnect1->get_sensitive() ) + { + m_xLbConnect1->set_sensitive(true); + } + } + } + else if (&rLb == m_xLbField2.get()) + { + if ( m_xLbField2->get_active() == 0 ) + { + m_xLbConnect2->set_active(-1); + m_xLbField3->set_active( 0 ); + m_xLbCond3->set_active( 0 ); + ClearValueList( 2 ); + ClearValueList( 3 ); + + m_xLbConnect2->set_sensitive(false); + m_xLbField3->set_sensitive(false); + m_xLbCond3->set_sensitive(false); + m_xEdVal3->set_sensitive(false); + } + else + { + UpdateValueList( 2 ); + if (!m_xLbConnect2->get_sensitive()) + { + m_xLbConnect2->set_sensitive(true); + } + } + } + else if (&rLb == m_xLbField3.get()) + { + if (m_xLbField3->get_active() == 0) + ClearValueList(3); + else + UpdateValueList(3); + } +} + +IMPL_LINK(ScPivotFilterDlg, CheckBoxHdl, weld::Toggleable&, rBox, void) +{ + // update the value lists when dealing with uppercase/lowercase + + if (&rBox != m_xBtnCase.get()) // value lists + return; + + for (auto& a : m_pEntryLists) + a.reset(); + + OUString aCurVal1 = m_xEdVal1->get_active_text(); + OUString aCurVal2 = m_xEdVal2->get_active_text(); + OUString aCurVal3 = m_xEdVal3->get_active_text(); + UpdateValueList( 1 ); + UpdateValueList( 2 ); + UpdateValueList( 3 ); + m_xEdVal1->set_entry_text(aCurVal1); + m_xEdVal2->set_entry_text(aCurVal2); + m_xEdVal3->set_entry_text(aCurVal3); +} + +IMPL_LINK( ScPivotFilterDlg, ValModifyHdl, weld::ComboBox&, rEd, void ) +{ + OUString aStrVal = rEd.get_active_text(); + weld::ComboBox* pLb = m_xLbCond1.get(); + + if ( &rEd == m_xEdVal2.get() ) pLb = m_xLbCond2.get(); + else if ( &rEd == m_xEdVal3.get() ) pLb = m_xLbCond3.get(); + + // if cond of the special values "empty"/"non-empty" was chosen only the + // =-operand makes sense: + + if ( aStrEmpty == aStrVal || aStrNotEmpty == aStrVal ) + { + pLb->set_active_text(OUString('=')); + pLb->set_sensitive(false); + } + else + pLb->set_sensitive(true); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/pvfundlg.cxx b/sc/source/ui/dbgui/pvfundlg.cxx new file mode 100644 index 0000000000..7f97e25767 --- /dev/null +++ b/sc/source/ui/dbgui/pvfundlg.cxx @@ -0,0 +1,964 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <pvfundlg.hxx> + +#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp> +#include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp> +#include <com/sun/star/sheet/DataPilotFieldLayoutMode.hpp> +#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp> +#include <com/sun/star/sheet/DataPilotFieldShowItemsMode.hpp> + +#include <osl/diagnose.h> + +#include <scresid.hxx> +#include <dpobject.hxx> +#include <dpsave.hxx> +#include <pvfundlg.hrc> +#include <globstr.hrc> +#include <dputil.hxx> + +#include <vector> + +using namespace ::com::sun::star::sheet; + +using ::com::sun::star::uno::Sequence; +using ::std::vector; + +namespace { + +/** Appends all strings from the Sequence to the list box. + + Empty strings are replaced by a localized "(empty)" entry and inserted at + the specified position. + + @return true = The passed string list contains an empty string entry. + */ + +bool lclFillListBox(weld::ComboBox& rLBox, const Sequence< OUString >& rStrings) +{ + bool bEmpty = false; + for (const OUString& str : rStrings) + { + if (!str.isEmpty()) + rLBox.append_text(str); + else + { + rLBox.append_text(ScResId(STR_EMPTYDATA)); + bEmpty = true; + } + } + return bEmpty; +} + +bool lclFillListBox(weld::ComboBox& rLBox, const vector<ScDPLabelData::Member>& rMembers, int nEmptyPos) +{ + bool bEmpty = false; + vector<ScDPLabelData::Member>::const_iterator itr = rMembers.begin(), itrEnd = rMembers.end(); + for (; itr != itrEnd; ++itr) + { + OUString aName = itr->getDisplayName(); + if (!aName.isEmpty()) + rLBox.append_text(aName); + else + { + rLBox.insert_text(nEmptyPos, ScResId(STR_EMPTYDATA)); + bEmpty = true; + } + } + return bEmpty; +} + +bool lclFillListBox(weld::TreeView& rLBox, const vector<ScDPLabelData::Member>& rMembers) +{ + bool bEmpty = false; + for (const auto& rMember : rMembers) + { + rLBox.append(); + int pos = rLBox.n_children() - 1; + rLBox.set_toggle(pos, TRISTATE_FALSE); + OUString aName = rMember.getDisplayName(); + if (!aName.isEmpty()) + rLBox.set_text(pos, aName, 0); + else + { + rLBox.set_text(pos, ScResId(STR_EMPTYDATA), 0); + bEmpty = true; + } + } + return bEmpty; +} + +/** This table represents the order of the strings in the resource string array. */ +const PivotFunc spnFunctions[] = +{ + PivotFunc::Sum, + PivotFunc::Count, + PivotFunc::Average, + PivotFunc::Median, + PivotFunc::Max, + PivotFunc::Min, + PivotFunc::Product, + PivotFunc::CountNum, + PivotFunc::StdDev, + PivotFunc::StdDevP, + PivotFunc::StdVar, + PivotFunc::StdVarP +}; + +const sal_uInt16 SC_BASEITEM_PREV_POS = 0; +const sal_uInt16 SC_BASEITEM_NEXT_POS = 1; +const sal_uInt16 SC_BASEITEM_USER_POS = 2; + +const sal_uInt16 SC_SORTNAME_POS = 0; +const sal_uInt16 SC_SORTDATA_POS = 1; + +const tools::Long SC_SHOW_DEFAULT = 10; + +} // namespace + +ScDPFunctionListBox::ScDPFunctionListBox(std::unique_ptr<weld::TreeView> xControl) + : m_xControl(std::move(xControl)) +{ + FillFunctionNames(); +} + +void ScDPFunctionListBox::SetSelection( PivotFunc nFuncMask ) +{ + if( (nFuncMask == PivotFunc::NONE) || (nFuncMask == PivotFunc::Auto) ) + m_xControl->unselect_all(); + else + { + for( sal_Int32 nEntry = 0, nCount = m_xControl->n_children(); nEntry < nCount; ++nEntry ) + { + if (bool(nFuncMask & spnFunctions[ nEntry ])) + m_xControl->select(nEntry); + else + m_xControl->unselect(nEntry); + } + } +} + +PivotFunc ScDPFunctionListBox::GetSelection() const +{ + PivotFunc nFuncMask = PivotFunc::NONE; + std::vector<int> aRows = m_xControl->get_selected_rows(); + for (int nSel : aRows) + nFuncMask |= spnFunctions[nSel]; + return nFuncMask; +} + +void ScDPFunctionListBox::FillFunctionNames() +{ + OSL_ENSURE( !m_xControl->n_children(), "ScDPMultiFuncListBox::FillFunctionNames - do not add texts to resource" ); + m_xControl->clear(); + m_xControl->freeze(); + for (size_t nIndex = 0; nIndex < SAL_N_ELEMENTS(SCSTR_DPFUNCLISTBOX); ++nIndex) + m_xControl->append_text(ScResId(SCSTR_DPFUNCLISTBOX[nIndex])); + m_xControl->thaw(); + assert(m_xControl->n_children() == SAL_N_ELEMENTS(spnFunctions)); +} + +namespace +{ + int FromDataPilotFieldReferenceType(int eMode) + { + switch (eMode) + { + case DataPilotFieldReferenceType::NONE: + return 0; + case DataPilotFieldReferenceType::ITEM_DIFFERENCE: + return 1; + case DataPilotFieldReferenceType::ITEM_PERCENTAGE: + return 2; + case DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE: + return 3; + case DataPilotFieldReferenceType::RUNNING_TOTAL: + return 4; + case DataPilotFieldReferenceType::ROW_PERCENTAGE: + return 5; + case DataPilotFieldReferenceType::COLUMN_PERCENTAGE: + return 6; + case DataPilotFieldReferenceType::TOTAL_PERCENTAGE: + return 7; + case DataPilotFieldReferenceType::INDEX: + return 8; + } + return -1; + } + + int ToDataPilotFieldReferenceType(int nPos) + { + switch (nPos) + { + case 0: + return DataPilotFieldReferenceType::NONE; + case 1: + return DataPilotFieldReferenceType::ITEM_DIFFERENCE; + case 2: + return DataPilotFieldReferenceType::ITEM_PERCENTAGE; + case 3: + return DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE; + case 4: + return DataPilotFieldReferenceType::RUNNING_TOTAL; + case 5: + return DataPilotFieldReferenceType::ROW_PERCENTAGE; + case 6: + return DataPilotFieldReferenceType::COLUMN_PERCENTAGE; + case 7: + return DataPilotFieldReferenceType::TOTAL_PERCENTAGE; + case 8: + return DataPilotFieldReferenceType::INDEX; + } + return DataPilotFieldReferenceType::NONE; + + } +} + +ScDPFunctionDlg::ScDPFunctionDlg( + weld::Widget* pParent, const ScDPLabelDataVector& rLabelVec, + const ScDPLabelData& rLabelData, const ScPivotFuncData& rFuncData) + : GenericDialogController(pParent, "modules/scalc/ui/datafielddialog.ui", "DataFieldDialog") + , mxLbFunc(new ScDPFunctionListBox(m_xBuilder->weld_tree_view("functions"))) + , mxFtName(m_xBuilder->weld_label("name")) + , mxLbType(m_xBuilder->weld_combo_box("type")) + , mxFtBaseField(m_xBuilder->weld_label("basefieldft")) + , mxLbBaseField(m_xBuilder->weld_combo_box("basefield")) + , mxFtBaseItem(m_xBuilder->weld_label("baseitemft")) + , mxLbBaseItem(m_xBuilder->weld_combo_box("baseitem")) + , mxBtnOk(m_xBuilder->weld_button("ok")) + , mxBtnCancel(m_xBuilder->weld_button("cancel")) + , mrLabelVec(rLabelVec) + , mbEmptyItem(false) +{ + mxLbFunc->set_size_request(-1, mxLbFunc->get_height_rows(8)); + + Init(rLabelData, rFuncData); +} + +ScDPFunctionDlg::~ScDPFunctionDlg() +{ +} + +PivotFunc ScDPFunctionDlg::GetFuncMask() const +{ + return mxLbFunc->GetSelection(); +} + +DataPilotFieldReference ScDPFunctionDlg::GetFieldRef() const +{ + DataPilotFieldReference aRef; + + aRef.ReferenceType = ToDataPilotFieldReferenceType(mxLbType->get_active()); + aRef.ReferenceField = GetBaseFieldName(mxLbBaseField->get_active_text()); + + sal_Int32 nBaseItemPos = mxLbBaseItem->get_active(); + switch( nBaseItemPos ) + { + case SC_BASEITEM_PREV_POS: + aRef.ReferenceItemType = DataPilotFieldReferenceItemType::PREVIOUS; + break; + case SC_BASEITEM_NEXT_POS: + aRef.ReferenceItemType = DataPilotFieldReferenceItemType::NEXT; + break; + default: + { + aRef.ReferenceItemType = DataPilotFieldReferenceItemType::NAMED; + if( !mbEmptyItem || (nBaseItemPos > SC_BASEITEM_USER_POS) ) + aRef.ReferenceItemName = GetBaseItemName(mxLbBaseItem->get_active_text()); + } + } + + return aRef; +} + +void ScDPFunctionDlg::Init( const ScDPLabelData& rLabelData, const ScPivotFuncData& rFuncData ) +{ + mxBtnOk->connect_clicked( LINK( this, ScDPFunctionDlg, ButtonClicked ) ); + mxBtnCancel->connect_clicked( LINK( this, ScDPFunctionDlg, ButtonClicked ) ); + + // list box + PivotFunc nFuncMask = (rFuncData.mnFuncMask == PivotFunc::NONE) ? PivotFunc::Sum : rFuncData.mnFuncMask; + mxLbFunc->SetSelection( nFuncMask ); + + // field name + mxFtName->set_label(rLabelData.getDisplayName()); + + // handlers + mxLbFunc->connect_row_activated( LINK( this, ScDPFunctionDlg, DblClickHdl ) ); + mxLbType->connect_changed( LINK( this, ScDPFunctionDlg, SelectHdl ) ); + mxLbBaseField->connect_changed( LINK( this, ScDPFunctionDlg, SelectHdl ) ); + + // base field list box + OUString aSelectedEntry; + for( const auto& rxLabel : mrLabelVec ) + { + mxLbBaseField->append_text(rxLabel->getDisplayName()); + maBaseFieldNameMap.emplace(rxLabel->getDisplayName(), rxLabel->maName); + if (rxLabel->maName == rFuncData.maFieldRef.ReferenceField) + aSelectedEntry = rxLabel->getDisplayName(); + } + + // select field reference type + mxLbType->set_active(FromDataPilotFieldReferenceType(rFuncData.maFieldRef.ReferenceType)); + SelectHdl( *mxLbType ); // enables base field/item list boxes + + // select base field + mxLbBaseField->set_active_text(aSelectedEntry); + if (mxLbBaseField->get_active() == -1) + mxLbBaseField->set_active(0); + SelectHdl( *mxLbBaseField ); // fills base item list, selects base item + + // select base item + switch( rFuncData.maFieldRef.ReferenceItemType ) + { + case DataPilotFieldReferenceItemType::PREVIOUS: + mxLbBaseItem->set_active( SC_BASEITEM_PREV_POS ); + break; + case DataPilotFieldReferenceItemType::NEXT: + mxLbBaseItem->set_active( SC_BASEITEM_NEXT_POS ); + break; + default: + { + if( mbEmptyItem && rFuncData.maFieldRef.ReferenceItemName.isEmpty() ) + { + // select special "(empty)" entry added before other items + mxLbBaseItem->set_active( SC_BASEITEM_USER_POS ); + } + else + { + sal_Int32 nStartPos = mbEmptyItem ? (SC_BASEITEM_USER_POS + 1) : SC_BASEITEM_USER_POS; + sal_Int32 nPos = FindBaseItemPos( rFuncData.maFieldRef.ReferenceItemName, nStartPos ); + if( nPos == -1) + nPos = (mxLbBaseItem->get_count() > SC_BASEITEM_USER_POS) ? SC_BASEITEM_USER_POS : SC_BASEITEM_PREV_POS; + mxLbBaseItem->set_active( nPos ); + } + } + } +} + +const OUString& ScDPFunctionDlg::GetBaseFieldName(const OUString& rLayoutName) const +{ + NameMapType::const_iterator itr = maBaseFieldNameMap.find(rLayoutName); + return itr == maBaseFieldNameMap.end() ? rLayoutName : itr->second; +} + +const OUString& ScDPFunctionDlg::GetBaseItemName(const OUString& rLayoutName) const +{ + NameMapType::const_iterator itr = maBaseItemNameMap.find(rLayoutName); + return itr == maBaseItemNameMap.end() ? rLayoutName : itr->second; +} + +sal_Int32 ScDPFunctionDlg::FindBaseItemPos( std::u16string_view rEntry, sal_Int32 nStartPos ) const +{ + sal_Int32 nPos = nStartPos; + bool bFound = false; + while (nPos < mxLbBaseItem->get_count()) + { + // translate the displayed field name back to its original field name. + const OUString& rInName = mxLbBaseItem->get_text(nPos); + const OUString& rName = GetBaseItemName(rInName); + if (rName == rEntry) + { + bFound = true; + break; + } + ++nPos; + } + return bFound ? nPos : -1; +} + +IMPL_LINK( ScDPFunctionDlg, SelectHdl, weld::ComboBox&, rLBox, void ) +{ + if (&rLBox == mxLbType.get()) + { + bool bEnableField, bEnableItem; + switch (ToDataPilotFieldReferenceType(mxLbType->get_active())) + { + case DataPilotFieldReferenceType::ITEM_DIFFERENCE: + case DataPilotFieldReferenceType::ITEM_PERCENTAGE: + case DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE: + bEnableField = bEnableItem = true; + break; + + case DataPilotFieldReferenceType::RUNNING_TOTAL: + bEnableField = true; + bEnableItem = false; + break; + + default: + bEnableField = bEnableItem = false; + } + + bEnableField &= (mxLbBaseField->get_count() > 0); + mxFtBaseField->set_sensitive( bEnableField ); + mxLbBaseField->set_sensitive( bEnableField ); + + bEnableItem &= bEnableField; + mxFtBaseItem->set_sensitive( bEnableItem ); + mxLbBaseItem->set_sensitive( bEnableItem ); + } + else if (&rLBox == mxLbBaseField.get()) + { + // keep "previous" and "next" entries + while (mxLbBaseItem->get_count() > SC_BASEITEM_USER_POS) + mxLbBaseItem->remove(SC_BASEITEM_USER_POS); + + // update item list for current base field + mbEmptyItem = false; + size_t nBasePos = mxLbBaseField->get_active(); + if (nBasePos < mrLabelVec.size()) + { + const vector<ScDPLabelData::Member>& rMembers = mrLabelVec[nBasePos]->maMembers; + mbEmptyItem = lclFillListBox(*mxLbBaseItem, rMembers, SC_BASEITEM_USER_POS); + // build cache for base names. + NameMapType aMap; + for (const auto& rMember : rMembers) + aMap.emplace(rMember.getDisplayName(), rMember.maName); + maBaseItemNameMap.swap(aMap); + } + + // select base item + sal_uInt16 nItemPos = (mxLbBaseItem->get_count() > SC_BASEITEM_USER_POS) ? SC_BASEITEM_USER_POS : SC_BASEITEM_PREV_POS; + mxLbBaseItem->set_active( nItemPos ); + } +} + +IMPL_LINK(ScDPFunctionDlg, ButtonClicked, weld::Button&, rButton, void) +{ + if (&rButton == mxBtnOk.get()) + response(RET_OK); + else + response(RET_CANCEL); +} + +IMPL_LINK_NOARG(ScDPFunctionDlg, DblClickHdl, weld::TreeView&, bool) +{ + m_xDialog->response(RET_OK); + return true; +} + +ScDPSubtotalDlg::ScDPSubtotalDlg(weld::Widget* pParent, ScDPObject& rDPObj, + const ScDPLabelData& rLabelData, const ScPivotFuncData& rFuncData, + const ScDPNameVec& rDataFields, bool bEnableLayout) + : GenericDialogController(pParent, "modules/scalc/ui/pivotfielddialog.ui", "PivotFieldDialog") + , mrDPObj(rDPObj) + , mrDataFields(rDataFields) + , maLabelData(rLabelData) + , mbEnableLayout(bEnableLayout) + , mxRbNone(m_xBuilder->weld_radio_button("none")) + , mxRbAuto(m_xBuilder->weld_radio_button("auto")) + , mxRbUser(m_xBuilder->weld_radio_button("user")) + , mxLbFunc(new ScDPFunctionListBox(m_xBuilder->weld_tree_view("functions"))) + , mxFtName(m_xBuilder->weld_label("name")) + , mxCbShowAll(m_xBuilder->weld_check_button("showall")) + , mxBtnOk(m_xBuilder->weld_button("ok")) + , mxBtnCancel(m_xBuilder->weld_button("cancel")) + , mxBtnOptions(m_xBuilder->weld_button("options")) +{ + mxLbFunc->set_selection_mode(SelectionMode::Multiple); + mxLbFunc->set_size_request(-1, mxLbFunc->get_height_rows(8)); + Init(rLabelData, rFuncData); +} + +ScDPSubtotalDlg::~ScDPSubtotalDlg() +{ + CloseSubdialog(); +} + +void ScDPSubtotalDlg::CloseSubdialog() +{ + if (mxOptionsDlg && mxOptionsDlg->getDialog()) + { + mxOptionsDlg->getDialog()->response(RET_CANCEL); + mxOptionsDlg = nullptr; + } +} + +PivotFunc ScDPSubtotalDlg::GetFuncMask() const +{ + PivotFunc nFuncMask = PivotFunc::NONE; + + if (mxRbAuto->get_active()) + nFuncMask = PivotFunc::Auto; + else if (mxRbUser->get_active()) + nFuncMask = mxLbFunc->GetSelection(); + + return nFuncMask; +} + +void ScDPSubtotalDlg::FillLabelData( ScDPLabelData& rLabelData ) const +{ + rLabelData.mnFuncMask = GetFuncMask(); + rLabelData.mnUsedHier = maLabelData.mnUsedHier; + rLabelData.mbShowAll = mxCbShowAll->get_active(); + rLabelData.maMembers = maLabelData.maMembers; + rLabelData.maSortInfo = maLabelData.maSortInfo; + rLabelData.maLayoutInfo = maLabelData.maLayoutInfo; + rLabelData.maShowInfo = maLabelData.maShowInfo; + rLabelData.mbRepeatItemLabels = maLabelData.mbRepeatItemLabels; +} + +void ScDPSubtotalDlg::Init( const ScDPLabelData& rLabelData, const ScPivotFuncData& rFuncData ) +{ + mxBtnOk->connect_clicked( LINK( this, ScDPSubtotalDlg, ButtonClicked ) ); + mxBtnCancel->connect_clicked( LINK( this, ScDPSubtotalDlg, ButtonClicked ) ); + + // field name + mxFtName->set_label(rLabelData.getDisplayName()); + + // radio buttons + mxRbNone->connect_toggled( LINK( this, ScDPSubtotalDlg, RadioClickHdl ) ); + mxRbAuto->connect_toggled( LINK( this, ScDPSubtotalDlg, RadioClickHdl ) ); + mxRbUser->connect_toggled( LINK( this, ScDPSubtotalDlg, RadioClickHdl ) ); + + weld::RadioButton* pRBtn = nullptr; + switch( rFuncData.mnFuncMask ) + { + case PivotFunc::NONE: pRBtn = mxRbNone.get(); break; + case PivotFunc::Auto: pRBtn = mxRbAuto.get(); break; + default: pRBtn = mxRbUser.get(); + } + pRBtn->set_active(true); + RadioClickHdl(*pRBtn); + + // list box + mxLbFunc->SetSelection( rFuncData.mnFuncMask ); + mxLbFunc->connect_row_activated( LINK( this, ScDPSubtotalDlg, DblClickHdl ) ); + + // show all + mxCbShowAll->set_active( rLabelData.mbShowAll ); + + // options + mxBtnOptions->connect_clicked( LINK( this, ScDPSubtotalDlg, ClickHdl ) ); +} + +IMPL_LINK(ScDPSubtotalDlg, ButtonClicked, weld::Button&, rButton, void) +{ + CloseSubdialog(); + + if (&rButton == mxBtnOk.get()) + response(RET_OK); + else + response(RET_CANCEL); +} + +IMPL_LINK(ScDPSubtotalDlg, RadioClickHdl, weld::Toggleable&, rBtn, void) +{ + if (!rBtn.get_active()) + return; + mxLbFunc->set_sensitive(mxRbUser->get_active()); +} + +IMPL_LINK_NOARG(ScDPSubtotalDlg, DblClickHdl, weld::TreeView&, bool) +{ + m_xDialog->response(RET_OK); + return true; +} + +IMPL_LINK(ScDPSubtotalDlg, ClickHdl, weld::Button&, rBtn, void) +{ + if (&rBtn == mxBtnOptions.get()) + { + mxOptionsDlg = std::make_shared<ScDPSubtotalOptDlg>(m_xDialog.get(), mrDPObj, maLabelData, mrDataFields, mbEnableLayout); + + weld::DialogController::runAsync(mxOptionsDlg, [this](int nResult) { + if (nResult == RET_OK) + mxOptionsDlg->FillLabelData(maLabelData); + mxOptionsDlg = nullptr; + }); + } +} + +namespace +{ + int FromDataPilotFieldLayoutMode(int eMode) + { + switch (eMode) + { + case DataPilotFieldLayoutMode::TABULAR_LAYOUT: + return 0; + case DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_TOP: + return 1; + case DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_BOTTOM: + return 2; + case DataPilotFieldLayoutMode::COMPACT_LAYOUT: + return 3; + } + return -1; + } + + int ToDataPilotFieldLayoutMode(int nPos) + { + switch (nPos) + { + case 0: + return DataPilotFieldLayoutMode::TABULAR_LAYOUT; + case 1: + return DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_TOP; + case 2: + return DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_BOTTOM; + case 3: + return DataPilotFieldLayoutMode::COMPACT_LAYOUT; + } + return DataPilotFieldLayoutMode::TABULAR_LAYOUT; + } + + int FromDataPilotFieldShowItemsMode(int eMode) + { + switch (eMode) + { + case DataPilotFieldShowItemsMode::FROM_TOP: + return 0; + case DataPilotFieldShowItemsMode::FROM_BOTTOM: + return 1; + } + return -1; + } + + int ToDataPilotFieldShowItemsMode(int nPos) + { + switch (nPos) + { + case 0: + return DataPilotFieldShowItemsMode::FROM_TOP; + case 1: + return DataPilotFieldShowItemsMode::FROM_BOTTOM; + } + return DataPilotFieldShowItemsMode::FROM_TOP; + } +} + +ScDPSubtotalOptDlg::ScDPSubtotalOptDlg(weld::Window* pParent, ScDPObject& rDPObj, + const ScDPLabelData& rLabelData, const ScDPNameVec& rDataFields, + bool bEnableLayout ) + : GenericDialogController(pParent, "modules/scalc/ui/datafieldoptionsdialog.ui", + "DataFieldOptionsDialog") + , m_xLbSortBy(m_xBuilder->weld_combo_box("sortby")) + , m_xRbSortAsc(m_xBuilder->weld_radio_button("ascending")) + , m_xRbSortDesc(m_xBuilder->weld_radio_button("descending")) + , m_xRbSortMan(m_xBuilder->weld_radio_button("manual")) + , m_xLayoutFrame(m_xBuilder->weld_widget("layoutframe")) + , m_xLbLayout(m_xBuilder->weld_combo_box("layout")) + , m_xCbLayoutEmpty(m_xBuilder->weld_check_button("emptyline")) + , m_xCbRepeatItemLabels(m_xBuilder->weld_check_button("repeatitemlabels")) + , m_xCbShow(m_xBuilder->weld_check_button("show")) + , m_xNfShow(m_xBuilder->weld_spin_button("items")) + , m_xFtShow(m_xBuilder->weld_label("showft")) + , m_xFtShowFrom(m_xBuilder->weld_label("showfromft")) + , m_xLbShowFrom(m_xBuilder->weld_combo_box("from")) + , m_xFtShowUsing(m_xBuilder->weld_label("usingft")) + , m_xLbShowUsing(m_xBuilder->weld_combo_box("using")) + , m_xHideFrame(m_xBuilder->weld_widget("hideframe")) + , m_xLbHide(m_xBuilder->weld_tree_view("hideitems")) + , m_xFtHierarchy(m_xBuilder->weld_label("hierarchyft")) + , m_xLbHierarchy(m_xBuilder->weld_combo_box("hierarchy")) + , m_xBtnOk(m_xBuilder->weld_button("ok")) + , m_xBtnCancel(m_xBuilder->weld_button("cancel")) + , mrDPObj(rDPObj) + , maLabelData(rLabelData) +{ + m_xLbHide->enable_toggle_buttons(weld::ColumnToggleType::Check); + + m_xLbSortBy->set_size_request(m_xLbSortBy->get_approximate_digit_width() * 18, -1); + m_xLbHide->set_size_request(-1, m_xLbHide->get_height_rows(5)); + Init(rDataFields, bEnableLayout); +} + +ScDPSubtotalOptDlg::~ScDPSubtotalOptDlg() +{ +} + +void ScDPSubtotalOptDlg::FillLabelData( ScDPLabelData& rLabelData ) const +{ + // *** SORTING *** + + if (m_xRbSortMan->get_active()) + rLabelData.maSortInfo.Mode = DataPilotFieldSortMode::MANUAL; + else if (m_xLbSortBy->get_active() == SC_SORTNAME_POS) + rLabelData.maSortInfo.Mode = DataPilotFieldSortMode::NAME; + else + rLabelData.maSortInfo.Mode = DataPilotFieldSortMode::DATA; + + ScDPName aFieldName = GetFieldName(m_xLbSortBy->get_active_text()); + if (!aFieldName.maName.isEmpty()) + { + rLabelData.maSortInfo.Field = + ScDPUtil::createDuplicateDimensionName(aFieldName.maName, aFieldName.mnDupCount); + } + + if (rLabelData.maSortInfo.Mode != DataPilotFieldSortMode::MANUAL) + rLabelData.maSortInfo.IsAscending = m_xRbSortAsc->get_active(); + + // *** LAYOUT MODE *** + + rLabelData.maLayoutInfo.LayoutMode = ToDataPilotFieldLayoutMode(m_xLbLayout->get_active()); + rLabelData.maLayoutInfo.AddEmptyLines = m_xCbLayoutEmpty->get_active(); + rLabelData.mbRepeatItemLabels = m_xCbRepeatItemLabels->get_active(); + + // *** AUTO SHOW *** + + aFieldName = GetFieldName(m_xLbShowUsing->get_active_text()); + if (!aFieldName.maName.isEmpty()) + { + rLabelData.maShowInfo.IsEnabled = m_xCbShow->get_active(); + rLabelData.maShowInfo.ShowItemsMode = ToDataPilotFieldShowItemsMode(m_xLbShowFrom->get_active()); + rLabelData.maShowInfo.ItemCount = sal::static_int_cast<sal_Int32>( m_xNfShow->get_value() ); + rLabelData.maShowInfo.DataField = + ScDPUtil::createDuplicateDimensionName(aFieldName.maName, aFieldName.mnDupCount); + } + + // *** HIDDEN ITEMS *** + + rLabelData.maMembers = maLabelData.maMembers; + int nVisCount = m_xLbHide->n_children(); + for (int nPos = 0; nPos < nVisCount; ++nPos) + rLabelData.maMembers[nPos].mbVisible = m_xLbHide->get_toggle(nPos) == TRISTATE_FALSE; + + // *** HIERARCHY *** + + rLabelData.mnUsedHier = m_xLbHierarchy->get_active() != -1 ? m_xLbHierarchy->get_active() : 0; +} + +void ScDPSubtotalOptDlg::Init( const ScDPNameVec& rDataFields, bool bEnableLayout ) +{ + m_xBtnOk->connect_clicked(LINK(this, ScDPSubtotalOptDlg, ButtonClicked)); + m_xBtnCancel->connect_clicked(LINK(this, ScDPSubtotalOptDlg, ButtonClicked)); + + // *** SORTING *** + + sal_Int32 nSortMode = maLabelData.maSortInfo.Mode; + + // sort fields list box + m_xLbSortBy->append_text(maLabelData.getDisplayName()); + + for( const auto& rDataField : rDataFields ) + { + // Cache names for later lookup. + maDataFieldNameMap.emplace(rDataField.maLayoutName, rDataField); + + m_xLbSortBy->append_text(rDataField.maLayoutName); + m_xLbShowUsing->append_text(rDataField.maLayoutName); // for AutoShow + } + + sal_Int32 nSortPos = SC_SORTNAME_POS; + if( nSortMode == DataPilotFieldSortMode::DATA ) + { + nSortPos = FindListBoxEntry( *m_xLbSortBy, maLabelData.maSortInfo.Field, SC_SORTDATA_POS ); + if( nSortPos == -1 ) + { + nSortPos = SC_SORTNAME_POS; + nSortMode = DataPilotFieldSortMode::MANUAL; + } + } + m_xLbSortBy->set_active(nSortPos); + + weld::RadioButton* pRBtn = nullptr; + switch( nSortMode ) + { + case DataPilotFieldSortMode::NONE: + case DataPilotFieldSortMode::MANUAL: + pRBtn = m_xRbSortMan.get(); + break; + default: + pRBtn = maLabelData.maSortInfo.IsAscending ? m_xRbSortAsc.get() : m_xRbSortDesc.get(); + } + pRBtn->set_active(true); + + // *** LAYOUT MODE *** + + m_xLayoutFrame->set_sensitive(bEnableLayout); + + m_xLbLayout->set_active(FromDataPilotFieldLayoutMode(maLabelData.maLayoutInfo.LayoutMode)); + m_xCbLayoutEmpty->set_active( maLabelData.maLayoutInfo.AddEmptyLines ); + m_xCbRepeatItemLabels->set_active( maLabelData.mbRepeatItemLabels ); + + // *** AUTO SHOW *** + + m_xCbShow->set_active( maLabelData.maShowInfo.IsEnabled ); + m_xCbShow->connect_toggled( LINK( this, ScDPSubtotalOptDlg, CheckHdl ) ); + + m_xLbShowFrom->set_active(FromDataPilotFieldShowItemsMode(maLabelData.maShowInfo.ShowItemsMode)); + tools::Long nCount = static_cast< tools::Long >( maLabelData.maShowInfo.ItemCount ); + if( nCount < 1 ) + nCount = SC_SHOW_DEFAULT; + m_xNfShow->set_value( nCount ); + + // m_xLbShowUsing already filled above + m_xLbShowUsing->set_active_text(maLabelData.maShowInfo.DataField); + if (m_xLbShowUsing->get_active() == -1) + m_xLbShowUsing->set_active(0); + + CheckHdl(*m_xCbShow); // enable/disable dependent controls + + // *** HIDDEN ITEMS *** + + InitHideListBox(); + + // *** HIERARCHY *** + + if( maLabelData.maHiers.getLength() > 1 ) + { + lclFillListBox(*m_xLbHierarchy, maLabelData.maHiers); + sal_Int32 nHier = maLabelData.mnUsedHier; + if( (nHier < 0) || (nHier >= maLabelData.maHiers.getLength()) ) nHier = 0; + m_xLbHierarchy->set_active( nHier ); + m_xLbHierarchy->connect_changed( LINK( this, ScDPSubtotalOptDlg, SelectHdl ) ); + } + else + { + m_xFtHierarchy->set_sensitive(false); + m_xLbHierarchy->set_sensitive(false); + } +} + +void ScDPSubtotalOptDlg::InitHideListBox() +{ + m_xLbHide->clear(); + lclFillListBox(*m_xLbHide, maLabelData.maMembers); + size_t n = maLabelData.maMembers.size(); + for (size_t i = 0; i < n; ++i) + m_xLbHide->set_toggle(i, maLabelData.maMembers[i].mbVisible ? TRISTATE_FALSE : TRISTATE_TRUE); + bool bEnable = m_xLbHide->n_children() > 0; + m_xHideFrame->set_sensitive(bEnable); +} + +ScDPName ScDPSubtotalOptDlg::GetFieldName(const OUString& rLayoutName) const +{ + NameMapType::const_iterator itr = maDataFieldNameMap.find(rLayoutName); + return itr == maDataFieldNameMap.end() ? ScDPName() : itr->second; +} + +sal_Int32 ScDPSubtotalOptDlg::FindListBoxEntry( + const weld::ComboBox& rLBox, std::u16string_view rEntry, sal_Int32 nStartPos ) const +{ + sal_Int32 nPos = nStartPos; + bool bFound = false; + while (nPos < rLBox.get_count()) + { + // translate the displayed field name back to its original field name. + ScDPName aName = GetFieldName(rLBox.get_text(nPos)); + OUString aUnoName = ScDPUtil::createDuplicateDimensionName(aName.maName, aName.mnDupCount); + if (aUnoName == rEntry) + { + bFound = true; + break; + } + ++nPos; + } + return bFound ? nPos : -1; +} + +IMPL_LINK(ScDPSubtotalOptDlg, ButtonClicked, weld::Button&, rButton, void) +{ + if (&rButton == m_xBtnOk.get()) + response(RET_OK); + else + response(RET_CANCEL); +} + +IMPL_LINK(ScDPSubtotalOptDlg, CheckHdl, weld::Toggleable&, rCBox, void) +{ + if (&rCBox == m_xCbShow.get()) + { + bool bEnable = m_xCbShow->get_active(); + m_xNfShow->set_sensitive( bEnable ); + m_xFtShow->set_sensitive( bEnable ); + m_xFtShowFrom->set_sensitive( bEnable ); + m_xLbShowFrom->set_sensitive( bEnable ); + + bool bEnableUsing = bEnable && (m_xLbShowUsing->get_count() > 0); + m_xFtShowUsing->set_sensitive(bEnableUsing); + m_xLbShowUsing->set_sensitive(bEnableUsing); + } +} + +IMPL_LINK_NOARG(ScDPSubtotalOptDlg, SelectHdl, weld::ComboBox&, void) +{ + mrDPObj.GetMembers(maLabelData.mnCol, m_xLbHierarchy->get_active(), maLabelData.maMembers); + InitHideListBox(); +} + +ScDPShowDetailDlg::ScDPShowDetailDlg(weld::Window* pParent, ScDPObject& rDPObj, css::sheet::DataPilotFieldOrientation nOrient) + : GenericDialogController(pParent, "modules/scalc/ui/showdetaildialog.ui", "ShowDetail") + , mrDPObj(rDPObj) + , mxLbDims(m_xBuilder->weld_tree_view("dimsTreeview")) +{ + ScDPSaveData* pSaveData = rDPObj.GetSaveData(); + tools::Long nDimCount = rDPObj.GetDimCount(); + for (tools::Long nDim=0; nDim<nDimCount; nDim++) + { + bool bIsDataLayout; + sal_Int32 nDimFlags = 0; + OUString aName = rDPObj.GetDimName( nDim, bIsDataLayout, &nDimFlags ); + if ( !bIsDataLayout && !rDPObj.IsDuplicated( nDim ) && ScDPObject::IsOrientationAllowed( nOrient, nDimFlags ) ) + { + const ScDPSaveDimension* pDimension = pSaveData ? pSaveData->GetExistingDimensionByName(aName) : nullptr; + if ( !pDimension || (pDimension->GetOrientation() != nOrient) ) + { + if (pDimension) + { + const std::optional<OUString> & pLayoutName = pDimension->GetLayoutName(); + if (pLayoutName) + aName = *pLayoutName; + } + mxLbDims->append_text(aName); + maNameIndexMap.emplace(aName, nDim); + } + } + } + if (mxLbDims->n_children()) + mxLbDims->select(0); + + mxLbDims->connect_row_activated(LINK(this, ScDPShowDetailDlg, DblClickHdl)); +} + +ScDPShowDetailDlg::~ScDPShowDetailDlg() +{ +} + +short ScDPShowDetailDlg::run() +{ + return mxLbDims->n_children() ? GenericDialogController::run() : static_cast<short>(RET_CANCEL); +} + +OUString ScDPShowDetailDlg::GetDimensionName() const +{ + // Look up the internal dimension name which may be different from the + // displayed field name. + OUString aSelectedName = mxLbDims->get_selected_text(); + DimNameIndexMap::const_iterator itr = maNameIndexMap.find(aSelectedName); + if (itr == maNameIndexMap.end()) + // This should never happen! + return aSelectedName; + + tools::Long nDim = itr->second; + bool bIsDataLayout = false; + return mrDPObj.GetDimName(nDim, bIsDataLayout); +} + +IMPL_LINK_NOARG(ScDPShowDetailDlg, DblClickHdl, weld::TreeView&, bool) +{ + m_xDialog->response(RET_OK); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/scendlg.cxx b/sc/source/ui/dbgui/scendlg.cxx new file mode 100644 index 0000000000..543914e146 --- /dev/null +++ b/sc/source/ui/dbgui/scendlg.cxx @@ -0,0 +1,163 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <comphelper/string.hxx> +#include <svx/colorbox.hxx> +#include <unotools/useroptions.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <unotools/localedatawrapper.hxx> + +#include <global.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <tabvwsh.hxx> +#include <viewdata.hxx> +#include <document.hxx> +#include <scendlg.hxx> + +ScNewScenarioDlg::ScNewScenarioDlg(weld::Window* pParent, const OUString& rName, bool bEdit, bool bSheetProtected) + : GenericDialogController(pParent, "modules/scalc/ui/scenariodialog.ui", "ScenarioDialog") + , aDefScenarioName(rName) + , bIsEdit(bEdit) + , m_xEdName(m_xBuilder->weld_entry("name")) + , m_xEdComment(m_xBuilder->weld_text_view("comment")) + , m_xCbShowFrame(m_xBuilder->weld_check_button("showframe")) + , m_xLbColor(new ColorListBox(m_xBuilder->weld_menu_button("bordercolor"), [this] { return m_xDialog.get(); })) + , m_xCbTwoWay(m_xBuilder->weld_check_button("copyback")) + , m_xCbCopyAll(m_xBuilder->weld_check_button("copysheet")) + , m_xCbProtect(m_xBuilder->weld_check_button("preventchanges")) + , m_xBtnOk(m_xBuilder->weld_button("ok")) + , m_xAltTitle(m_xBuilder->weld_label("alttitle")) + , m_xCreatedFt(m_xBuilder->weld_label("createdft")) + , m_xOnFt(m_xBuilder->weld_label("onft")) +{ + m_xEdComment->set_size_request(m_xEdComment->get_approximate_digit_width() * 60, + m_xEdComment->get_height_rows(6)); + + if (bIsEdit) + m_xDialog->set_title(m_xAltTitle->get_label()); + + SvtUserOptions aUserOpt; + + OUString sCreatedBy(m_xCreatedFt->get_label()); + OUString sOn(m_xOnFt->get_label()); + + OUString aComment(sCreatedBy + " " + aUserOpt.GetFirstName() + " " +aUserOpt.GetLastName() + + ", " + sOn + " " + ScGlobal::getLocaleData().getDate(Date(Date::SYSTEM)) + + ", " + ScGlobal::getLocaleData().getTime(tools::Time(tools::Time::SYSTEM))); + + m_xEdComment->set_text(aComment); + m_xEdName->set_text(rName); + m_xBtnOk->connect_clicked(LINK(this, ScNewScenarioDlg, OkHdl)); + m_xCbShowFrame->connect_toggled(LINK(this, ScNewScenarioDlg, EnableHdl)); + + m_xLbColor->SelectEntry( COL_LIGHTGRAY ); + m_xCbShowFrame->set_active(true); + m_xCbTwoWay->set_active(true); + m_xCbCopyAll->set_active(false); + m_xCbProtect->set_active(true); + + if (bIsEdit) + m_xCbCopyAll->set_active(false); + // If the Sheet is protected then we disable the Scenario Protect input + // and default it to true above. Note we are in 'Add' mode here as: if + // Sheet && scenario protection are true, then we cannot edit this dialog. + if (bSheetProtected) + m_xCbProtect->set_active(false); +} + +ScNewScenarioDlg::~ScNewScenarioDlg() +{ +} + +void ScNewScenarioDlg::GetScenarioData( OUString& rName, OUString& rComment, + Color& rColor, ScScenarioFlags& rFlags ) const +{ + rComment = m_xEdComment->get_text(); + rName = m_xEdName->get_text(); + + if (rName.isEmpty()) + rName = aDefScenarioName; + + rColor = m_xLbColor->GetSelectEntryColor(); + ScScenarioFlags nBits = ScScenarioFlags::NONE; + if (m_xCbShowFrame->get_active()) + nBits |= ScScenarioFlags::ShowFrame; + if (m_xCbTwoWay->get_active()) + nBits |= ScScenarioFlags::TwoWay; + if (m_xCbCopyAll->get_active()) + nBits |= ScScenarioFlags::CopyAll; + if (m_xCbProtect->get_active()) + nBits |= ScScenarioFlags::Protected; + rFlags = nBits; +} + +void ScNewScenarioDlg::SetScenarioData(const OUString& rName, const OUString& rComment, + const Color& rColor, ScScenarioFlags nFlags) +{ + m_xEdComment->set_text(rComment); + m_xEdName->set_text(rName); + m_xLbColor->SelectEntry(rColor); + + m_xCbShowFrame->set_active( (nFlags & ScScenarioFlags::ShowFrame) != ScScenarioFlags::NONE ); + EnableHdl(*m_xCbShowFrame); + m_xCbTwoWay->set_active( (nFlags & ScScenarioFlags::TwoWay) != ScScenarioFlags::NONE ); + // not CopyAll + m_xCbProtect->set_active( (nFlags & ScScenarioFlags::Protected) != ScScenarioFlags::NONE ); +} + +IMPL_LINK_NOARG(ScNewScenarioDlg, OkHdl, weld::Button&, void) +{ + OUString aName = comphelper::string::strip(m_xEdName->get_text(), ' '); + ScDocument& rDoc = static_cast<ScTabViewShell*>(SfxViewShell::Current())->GetViewData().GetDocument(); + + m_xEdName->set_text(aName); + + if ( !ScDocument::ValidTabName( aName ) ) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_INVALIDTABNAME))); + xInfoBox->run(); + m_xEdName->grab_focus(); + } + else if ( !bIsEdit && !rDoc.ValidNewTabName( aName ) ) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_NEWTABNAMENOTUNIQUE))); + xInfoBox->run(); + m_xEdName->grab_focus(); + } + else + m_xDialog->response(RET_OK); + + //! when editing, test whether another table has the name! +} + +IMPL_LINK(ScNewScenarioDlg, EnableHdl, weld::Toggleable&, rBox, void) +{ + if (&rBox == m_xCbShowFrame.get()) + m_xLbColor->set_sensitive(m_xCbShowFrame->get_active()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/scuiasciiopt.cxx b/sc/source/ui/dbgui/scuiasciiopt.cxx new file mode 100644 index 0000000000..601323a658 --- /dev/null +++ b/sc/source/ui/dbgui/scuiasciiopt.cxx @@ -0,0 +1,975 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <svx/txencbox.hxx> + +#include <global.hxx> +#include <scresid.hxx> +#include <impex.hxx> +#include <scuiasciiopt.hxx> +#include <strings.hrc> +#include <strings.hxx> +#include <csvtablebox.hxx> +#include <osl/thread.h> +#include <unotools/transliterationwrapper.hxx> + +#include <optutil.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <miscuno.hxx> +#include <osl/diagnose.h> +#include <vcl/svapp.hxx> +#include <comphelper/lok.hxx> +#include <o3tl/string_view.hxx> + +#include <unicode/ucsdet.h> + +//! TODO make dynamic +const SCSIZE ASCIIDLG_MAXROWS = MAXROWCOUNT; + +// Maximum number of source lines to concatenate while generating the preview +// for one logical line. This may result in a wrong preview if the actual +// number of embedded line feeds is greater, but a number too high would take +// too much time (loop excessively if unlimited and large data) if none of the +// selected separators are actually used in data but a field at start of line +// is quoted. +constexpr sal_uInt32 kMaxEmbeddedLinefeeds = 500; + +using namespace com::sun::star::uno; + +namespace { + +// Defines - CSV Import Preserve Options +// For usage of index order see lcl_CreatePropertiesNames() below. +enum CSVImportOptionsIndex +{ + CSVIO_MergeDelimiters = 0, + CSVIO_Separators, + CSVIO_TextSeparators, + CSVIO_FixedWidth, + CSVIO_RemoveSpace, + CSVIO_EvaluateFormulas, + // Settings for *all* dialog invocations above. + // Settings not for SC_TEXTTOCOLUMNS below. + CSVIO_FromRow, + CSVIO_Text2ColSkipEmptyCells = CSVIO_FromRow, + CSVIO_CharSet, + CSVIO_QuotedAsText, + CSVIO_DetectSpecialNum, + CSVIO_DetectScientificNum, + CSVIO_Language, + // Plus one not for SC_IMPORTFILE. + CSVIO_PasteSkipEmptyCells +}; + +} + +// Config items for all three paths are defined in +// officecfg/registry/schema/org/openoffice/Office/Calc.xcs +// If not, options are neither loaded nor saved. +const ::std::vector<OUString> CSVImportOptionNames = +{ + "MergeDelimiters", + "Separators", + "TextSeparators", + "FixedWidth", + "RemoveSpace", + "EvaluateFormulas", + "FromRow", + "CharSet", + "QuotedFieldAsText", + "DetectSpecialNumbers", + "DetectScientificNumbers", + "Language", + "SkipEmptyCells" +}; +constexpr OUStringLiteral aSep_Path = u"Office.Calc/Dialogs/CSVImport"; +constexpr OUStringLiteral aSep_Path_Clpbrd = u"Office.Calc/Dialogs/ClipboardTextImport"; +constexpr OUStringLiteral aSep_Path_Text2Col = u"Office.Calc/Dialogs/TextToColumnsImport"; + +namespace { +CSVImportOptionsIndex getSkipEmptyCellsIndex( ScImportAsciiCall eCall ) +{ + return eCall == SC_TEXTTOCOLUMNS ? CSVIO_Text2ColSkipEmptyCells : CSVIO_PasteSkipEmptyCells; +} +} + +static void lcl_FillCombo(weld::ComboBox& rCombo, std::u16string_view rList, sal_Unicode cSelect) +{ + OUString aStr; + if (!rList.empty()) + { + sal_Int32 nIdx {0}; + do + { + const OUString sEntry {o3tl::getToken(rList, 0, '\t', nIdx)}; + rCombo.append_text(sEntry); + if (nIdx>0 && static_cast<sal_Unicode>(o3tl::toInt32(o3tl::getToken(rList, 0, '\t', nIdx))) == cSelect) + aStr = sEntry; + } + while (nIdx>0); + } + + if ( cSelect ) + { + if (aStr.isEmpty()) + aStr = OUString(cSelect); // Ascii + + rCombo.set_entry_text(aStr); + } +} + +static sal_Unicode lcl_CharFromCombo(const weld::ComboBox& rCombo, std::u16string_view rList) +{ + sal_Unicode c = 0; + OUString aStr = rCombo.get_active_text(); + if ( !aStr.isEmpty() && !rList.empty() ) + { + sal_Int32 nIdx {0}; + OUString sToken {o3tl::getToken(rList, 0, '\t', nIdx)}; + while (nIdx>0) + { + if ( ScGlobal::GetTransliteration().isEqual( aStr, sToken ) ) + { + sal_Int32 nTmpIdx {nIdx}; + c = static_cast<sal_Unicode>(o3tl::toInt32(o3tl::getToken(rList, 0, '\t', nTmpIdx))); + } + // Skip to next token at even position + sToken = o3tl::getToken(rList, 1, '\t', nIdx); + } + if (!c) + { + sal_Unicode cFirst = aStr[0]; + // #i24235# first try the first character of the string directly + if( (aStr.getLength() == 1) || (cFirst < '0') || (cFirst > '9') ) + c = cFirst; + else // keep old behaviour for compatibility (i.e. "39" -> "'") + c = static_cast<sal_Unicode>(aStr.toInt32()); // Ascii + } + } + return c; +} + +static void lcl_CreatePropertiesNames ( OUString& rSepPath, Sequence<OUString>& rNames, ScImportAsciiCall eCall ) +{ + sal_Int32 nProperties = 0; + + switch(eCall) + { + case SC_IMPORTFILE: + rSepPath = aSep_Path; + nProperties = 12; + break; + case SC_PASTETEXT: + rSepPath = aSep_Path_Clpbrd; + nProperties = 13; + break; + case SC_TEXTTOCOLUMNS: + default: + rSepPath = aSep_Path_Text2Col; + nProperties = 7; + break; + } + rNames.realloc( nProperties ); + OUString* pNames = rNames.getArray(); + pNames[ CSVIO_MergeDelimiters ] = CSVImportOptionNames[ CSVIO_MergeDelimiters ]; + pNames[ CSVIO_Separators ] = CSVImportOptionNames[ CSVIO_Separators ]; + pNames[ CSVIO_TextSeparators ] = CSVImportOptionNames[ CSVIO_TextSeparators ]; + pNames[ CSVIO_FixedWidth ] = CSVImportOptionNames[ CSVIO_FixedWidth ]; + pNames[ CSVIO_RemoveSpace ] = CSVImportOptionNames[ CSVIO_RemoveSpace ]; + pNames[ CSVIO_EvaluateFormulas ] = CSVImportOptionNames[ CSVIO_EvaluateFormulas ]; + if (eCall != SC_TEXTTOCOLUMNS) + { + pNames[ CSVIO_FromRow ] = CSVImportOptionNames[ CSVIO_FromRow ]; + pNames[ CSVIO_CharSet ] = CSVImportOptionNames[ CSVIO_CharSet ]; + pNames[ CSVIO_QuotedAsText ] = CSVImportOptionNames[ CSVIO_QuotedAsText ]; + pNames[ CSVIO_DetectSpecialNum ] = CSVImportOptionNames[ CSVIO_DetectSpecialNum ]; + pNames[ CSVIO_DetectScientificNum ] = CSVImportOptionNames[ CSVIO_DetectScientificNum ]; + pNames[ CSVIO_Language ] = CSVImportOptionNames[ CSVIO_Language ]; + } + if (eCall != SC_IMPORTFILE) + { + const sal_Int32 nSkipEmptyCells = getSkipEmptyCellsIndex(eCall); + assert( nSkipEmptyCells < rNames.getLength()); + pNames[ nSkipEmptyCells ] = CSVImportOptionNames[ CSVIO_PasteSkipEmptyCells ]; + } +} + +static void lcl_LoadSeparators( OUString& rFieldSeparators, OUString& rTextSeparators, + bool& rMergeDelimiters, bool& rQuotedAsText, bool& rDetectSpecialNum, bool& rDetectScientificNum, + bool& rFixedWidth, sal_Int32& rFromRow, sal_Int32& rCharSet, + sal_Int32& rLanguage, bool& rSkipEmptyCells, bool& rRemoveSpace, + bool& rEvaluateFormulas, ScImportAsciiCall eCall ) +{ + Sequence<Any>aValues; + const Any *pProperties; + Sequence<OUString> aNames; + OUString aSepPath; + lcl_CreatePropertiesNames ( aSepPath, aNames, eCall); + ScLinkConfigItem aItem( aSepPath ); + aValues = aItem.GetProperties( aNames ); + pProperties = aValues.getConstArray(); + + if( pProperties[ CSVIO_MergeDelimiters ].hasValue() ) + rMergeDelimiters = ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_MergeDelimiters ] ); + + if( pProperties[ CSVIO_RemoveSpace ].hasValue() ) + rRemoveSpace = ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_RemoveSpace ] ); + + if( pProperties[ CSVIO_Separators ].hasValue() ) + pProperties[ CSVIO_Separators ] >>= rFieldSeparators; + + if( pProperties[ CSVIO_TextSeparators ].hasValue() ) + pProperties[ CSVIO_TextSeparators ] >>= rTextSeparators; + + if( pProperties[ CSVIO_FixedWidth ].hasValue() ) + rFixedWidth = ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_FixedWidth ] ); + + if( pProperties[ CSVIO_EvaluateFormulas ].hasValue() ) + rEvaluateFormulas = ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_EvaluateFormulas ] ); + + if (eCall != SC_TEXTTOCOLUMNS) + { + if( pProperties[ CSVIO_FromRow ].hasValue() ) + pProperties[ CSVIO_FromRow ] >>= rFromRow; + + if( pProperties[ CSVIO_CharSet ].hasValue() ) + pProperties[ CSVIO_CharSet ] >>= rCharSet; + + if ( pProperties[ CSVIO_QuotedAsText ].hasValue() ) + pProperties[ CSVIO_QuotedAsText ] >>= rQuotedAsText; + + if ( pProperties[ CSVIO_DetectSpecialNum ].hasValue() ) + pProperties[ CSVIO_DetectSpecialNum ] >>= rDetectSpecialNum; + + if ( pProperties[ CSVIO_DetectScientificNum ].hasValue() ) + pProperties[ CSVIO_DetectScientificNum ] >>= rDetectScientificNum; + + if ( pProperties[ CSVIO_Language ].hasValue() ) + pProperties[ CSVIO_Language ] >>= rLanguage; + } + if (eCall != SC_IMPORTFILE) + { + const sal_Int32 nSkipEmptyCells = getSkipEmptyCellsIndex(eCall); + assert( nSkipEmptyCells < aValues.getLength()); + if ( pProperties[nSkipEmptyCells].hasValue() ) + rSkipEmptyCells = ScUnoHelpFunctions::GetBoolFromAny( pProperties[nSkipEmptyCells] ); + } +} + +static void lcl_SaveSeparators( + const OUString& sFieldSeparators, const OUString& sTextSeparators, bool bMergeDelimiters, bool bQuotedAsText, + bool bDetectSpecialNum, bool bDetectScientificNum, bool bFixedWidth, sal_Int32 nFromRow, + sal_Int32 nCharSet, sal_Int32 nLanguage, bool bSkipEmptyCells, bool bRemoveSpace, bool bEvaluateFormulas, + ScImportAsciiCall eCall ) +{ + Sequence<Any> aValues; + Any *pProperties; + Sequence<OUString> aNames; + OUString aSepPath; + lcl_CreatePropertiesNames ( aSepPath, aNames, eCall ); + ScLinkConfigItem aItem( aSepPath ); + aValues = aItem.GetProperties( aNames ); + pProperties = aValues.getArray(); + + pProperties[ CSVIO_MergeDelimiters ] <<= bMergeDelimiters; + pProperties[ CSVIO_RemoveSpace ] <<= bRemoveSpace; + pProperties[ CSVIO_Separators ] <<= sFieldSeparators; + pProperties[ CSVIO_TextSeparators ] <<= sTextSeparators; + pProperties[ CSVIO_FixedWidth ] <<= bFixedWidth; + pProperties[ CSVIO_EvaluateFormulas ] <<= bEvaluateFormulas; + if (eCall != SC_TEXTTOCOLUMNS) + { + pProperties[ CSVIO_FromRow ] <<= nFromRow; + pProperties[ CSVIO_CharSet ] <<= nCharSet; + pProperties[ CSVIO_QuotedAsText ] <<= bQuotedAsText; + pProperties[ CSVIO_DetectSpecialNum ] <<= bDetectSpecialNum; + pProperties[ CSVIO_DetectScientificNum ] <<= bDetectScientificNum; + pProperties[ CSVIO_Language ] <<= nLanguage; + } + if (eCall != SC_IMPORTFILE) + { + const sal_Int32 nSkipEmptyCells = getSkipEmptyCellsIndex(eCall); + assert( nSkipEmptyCells < aValues.getLength()); + pProperties[ nSkipEmptyCells ] <<= bSkipEmptyCells; + } + + aItem.PutProperties(aNames, aValues); +} + +ScImportAsciiDlg::ScImportAsciiDlg(weld::Window* pParent, std::u16string_view aDatName, + SvStream* pInStream, ScImportAsciiCall eCall) + : GenericDialogController(pParent, "modules/scalc/ui/textimportcsv.ui", "TextImportCsvDialog") + , mpDatStream(pInStream) + , mnStreamPos(pInStream ? pInStream->Tell() : 0) + , mnRowPosCount(0) + , mcTextSep(ScAsciiOptions::cDefaultTextSep) + , meCall(eCall) + , mbDetectSep(eCall != SC_TEXTTOCOLUMNS) + , mxFtCharSet(m_xBuilder->weld_label("textcharset")) + , mxLbCharSet(new SvxTextEncodingBox(m_xBuilder->weld_combo_box("charset"))) + , mxFtCustomLang(m_xBuilder->weld_label("textlanguage")) + , mxLbCustomLang(new SvxLanguageBox(m_xBuilder->weld_combo_box("language"))) + , mxFtRow(m_xBuilder->weld_label("textfromrow")) + , mxNfRow(m_xBuilder->weld_spin_button("fromrow")) + , mxRbFixed(m_xBuilder->weld_radio_button("tofixedwidth")) + , mxRbSeparated(m_xBuilder->weld_radio_button("toseparatedby")) + , mxCkbTab(m_xBuilder->weld_check_button("tab")) + , mxCkbSemicolon(m_xBuilder->weld_check_button("semicolon")) + , mxCkbComma(m_xBuilder->weld_check_button("comma")) + , mxCkbRemoveSpace(m_xBuilder->weld_check_button("removespace")) + , mxCkbSpace(m_xBuilder->weld_check_button("space")) + , mxCkbOther(m_xBuilder->weld_check_button("other")) + , mxEdOther(m_xBuilder->weld_entry("inputother")) + , mxCkbAsOnce(m_xBuilder->weld_check_button("mergedelimiters")) + , mxFtTextSep(m_xBuilder->weld_label("texttextdelimiter")) + , mxCbTextSep(m_xBuilder->weld_combo_box("textdelimiter")) + , mxCkbQuotedAsText(m_xBuilder->weld_check_button("quotedfieldastext")) + , mxCkbDetectNumber(m_xBuilder->weld_check_button("detectspecialnumbers")) + , mxCkbDetectScientificNumber(m_xBuilder->weld_check_button("detectscientificnumbers")) + , mxCkbEvaluateFormulas(m_xBuilder->weld_check_button("evaluateformulas")) + , mxCkbSkipEmptyCells(m_xBuilder->weld_check_button("skipemptycells")) + , mxLbType(m_xBuilder->weld_combo_box("columntype")) + , mxAltTitle(m_xBuilder->weld_label("textalttitle")) + , mxTableBox(new ScCsvTableBox(*m_xBuilder)) +{ + OUString aName = m_xDialog->get_title(); + switch (meCall) + { + case SC_TEXTTOCOLUMNS: + m_xDialog->set_title(mxAltTitle->get_label()); + break; + case SC_IMPORTFILE: + if (!comphelper::LibreOfficeKit::isActive()) + { + aName += OUString::Concat(" - [") + aDatName + "]"; + m_xDialog->set_title(aName); + } + break; + default: + break; + } + + // To be able to prefill the correct values based on the file extension + bool bIsTSV = (o3tl::endsWithIgnoreAsciiCase(aDatName, ".tsv") || o3tl::endsWithIgnoreAsciiCase(aDatName, ".tab")); + + // Default options are set in officecfg/registry/schema/org/openoffice/Office/Calc.xcs + OUString sFieldSeparators(",;\t"); + OUString sTextSeparators(mcTextSep); + bool bMergeDelimiters = false; + bool bFixedWidth = false; + bool bQuotedFieldAsText = false; + bool bDetectSpecialNum = true; + bool bDetectScientificNum = true; + bool bEvaluateFormulas = (meCall != SC_IMPORTFILE); + bool bSkipEmptyCells = true; + bool bRemoveSpace = false; + sal_Int32 nFromRow = 1; + sal_Int32 nCharSet = -1; + sal_Int32 nLanguage = 0; + lcl_LoadSeparators (sFieldSeparators, sTextSeparators, bMergeDelimiters, + bQuotedFieldAsText, bDetectSpecialNum, bDetectScientificNum, bFixedWidth, nFromRow, + nCharSet, nLanguage, bSkipEmptyCells, bRemoveSpace, bEvaluateFormulas, meCall); + // load from saved settings + maFieldSeparators = sFieldSeparators; + + if( bMergeDelimiters && !bIsTSV ) + mxCkbAsOnce->set_active(true); + if (bQuotedFieldAsText) + mxCkbQuotedAsText->set_active(true); + if (bRemoveSpace) + mxCkbRemoveSpace->set_active(true); + if (bDetectSpecialNum) + { + mxCkbDetectNumber->set_active(true); + bDetectScientificNum = true; + mxCkbDetectScientificNumber->set_sensitive(false); + } + if (bDetectScientificNum) + mxCkbDetectScientificNumber->set_active(true); + if (bEvaluateFormulas) + mxCkbEvaluateFormulas->set_active(true); + if (bSkipEmptyCells) + mxCkbSkipEmptyCells->set_active(true); + if (bFixedWidth && !bIsTSV) + mxRbFixed->set_active(true); + if (nFromRow != 1) + mxNfRow->set_value(nFromRow); + + // Clipboard is always Unicode, else detect. + rtl_TextEncoding ePreselectUnicode = (meCall == SC_IMPORTFILE ? + RTL_TEXTENCODING_DONTKNOW : RTL_TEXTENCODING_UNICODE); + // Sniff for Unicode / not + if( ePreselectUnicode == RTL_TEXTENCODING_DONTKNOW && mpDatStream ) + { + mpDatStream->Seek( 0 ); + constexpr size_t buffsize = 4096; + sal_Int8 bytes[buffsize] = { 0 }; + sal_Int32 nRead = mpDatStream->ReadBytes( bytes, buffsize ); + mpDatStream->Seek( 0 ); + + if ( nRead > 0 ) + { + UErrorCode uerr = U_ZERO_ERROR; + UCharsetDetector* ucd = ucsdet_open( &uerr ); + ucsdet_setText( ucd, reinterpret_cast<const char*>(bytes), nRead, &uerr ); + + if ( const UCharsetMatch* match = ucsdet_detect(ucd, &uerr) ) + { + const char* pEncodingName = ucsdet_getName( match, &uerr ); + + if ( U_SUCCESS(uerr) && !strcmp("UTF-8", pEncodingName) ) + { + ePreselectUnicode = RTL_TEXTENCODING_UTF8; // UTF-8 + mpDatStream->StartReadingUnicodeText( RTL_TEXTENCODING_UTF8 ); + } + else if ( U_SUCCESS(uerr) && !strcmp("UTF-16LE", pEncodingName) ) + { + ePreselectUnicode = RTL_TEXTENCODING_UNICODE; // UTF-16LE + mpDatStream->SetEndian( SvStreamEndian::LITTLE ); + mpDatStream->StartReadingUnicodeText( RTL_TEXTENCODING_UNICODE ); + } + else if ( U_SUCCESS(uerr) && !strcmp("UTF-16BE", pEncodingName) ) + { + ePreselectUnicode = RTL_TEXTENCODING_UNICODE; // UTF-16BE + mpDatStream->SetEndian( SvStreamEndian::BIG ); + mpDatStream->StartReadingUnicodeText( RTL_TEXTENCODING_UNICODE ); + } + else // other + mpDatStream->StartReadingUnicodeText( RTL_TEXTENCODING_DONTKNOW ); + } + + ucsdet_close( ucd ); + } + + mnStreamPos = mpDatStream->Tell(); + } + + if (bIsTSV) + SetSeparators('\t'); + else + { + // Some MS-Excel convention is the first line containing the field + // separator as "sep=|" (without quotes and any field separator + // character). The second possibility seems to be it is present *with* + // quotes so it shows up as cell content *including* the separator and + // can be preserved during round trips. Check for an exact match of + // any such and set separator. + /* TODO: it is debatable whether the unquoted form should rather be + * treated special to actually include the separator in the field data. + * Currently it does not. */ + sal_Unicode cSep = 0; + OUString aLine; + // Try to read one more character, if more than 7 it can't be an exact + // match of any. + mpDatStream->ReadUniOrByteStringLine( aLine, mpDatStream->GetStreamCharSet(), 8); + mpDatStream->Seek(mnStreamPos); + if (aLine.getLength() == 8) + ; // nothing + else if (aLine.getLength() == 5 && aLine.startsWithIgnoreAsciiCase("sep=")) + cSep = aLine[4]; + else if (aLine.getLength() == 7 && aLine[6] == '"' && aLine.startsWithIgnoreAsciiCase("\"sep=")) + cSep = aLine[5]; + + // Set Separators in the dialog from maFieldSeparators (empty are not + // set) or an optionally defined by file content field separator. + SetSeparators(cSep); + } + + // Get Separators from the dialog (empty are set from default) + maFieldSeparators = GetSeparators(); + + mxNfRow->connect_value_changed( LINK( this, ScImportAsciiDlg, FirstRowHdl ) ); + + // *** Separator characters *** + lcl_FillCombo( *mxCbTextSep, SCSTR_TEXTSEP, mcTextSep ); + mxCbTextSep->set_entry_text(sTextSeparators); + // tdf#69207 - use selected text delimiter to parse the provided data + mcTextSep = lcl_CharFromCombo(*mxCbTextSep, SCSTR_TEXTSEP); + + Link<weld::Toggleable&,void> aSeparatorClickHdl =LINK( this, ScImportAsciiDlg, SeparatorClickHdl ); + Link<weld::Toggleable&,void> aOtherOptionsClickHdl =LINK( this, ScImportAsciiDlg, OtherOptionsClickHdl ); + mxCbTextSep->connect_changed( LINK( this, ScImportAsciiDlg, SeparatorComboBoxHdl ) ); + mxCkbTab->connect_toggled( aSeparatorClickHdl ); + mxCkbSemicolon->connect_toggled( aSeparatorClickHdl ); + mxCkbComma->connect_toggled( aSeparatorClickHdl ); + mxCkbAsOnce->connect_toggled( aSeparatorClickHdl ); + mxCkbSpace->connect_toggled( aSeparatorClickHdl ); + mxCkbRemoveSpace->connect_toggled( aSeparatorClickHdl ); + mxCkbOther->connect_toggled( aSeparatorClickHdl ); + mxEdOther->connect_changed(LINK(this, ScImportAsciiDlg, SeparatorEditHdl)); + mxCkbQuotedAsText->connect_toggled( aOtherOptionsClickHdl ); + mxCkbDetectNumber->connect_toggled( aOtherOptionsClickHdl ); + mxCkbDetectScientificNumber->connect_toggled( aOtherOptionsClickHdl ); + mxCkbEvaluateFormulas->connect_toggled( aOtherOptionsClickHdl ); + mxCkbSkipEmptyCells->connect_toggled( aOtherOptionsClickHdl ); + + // *** text encoding ListBox *** + // all encodings allowed, including Unicode, but subsets are excluded + mxLbCharSet->FillFromTextEncodingTable( true ); + // Insert one "SYSTEM" entry for compatibility in AsciiOptions and system + // independent document linkage. + mxLbCharSet->InsertTextEncoding( RTL_TEXTENCODING_DONTKNOW, ScResId( SCSTR_CHARSET_USER ) ); + if ( ePreselectUnicode == RTL_TEXTENCODING_DONTKNOW ) + { + rtl_TextEncoding eSystemEncoding = osl_getThreadTextEncoding(); + // Prefer UTF-8, as UTF-16 would have already been detected from the stream. + // This gives a better chance that the file is going to be opened correctly. + if ( ( eSystemEncoding == RTL_TEXTENCODING_UNICODE ) && mpDatStream ) + eSystemEncoding = RTL_TEXTENCODING_UTF8; + mxLbCharSet->SelectTextEncoding( eSystemEncoding ); + } + else + { + mxLbCharSet->SelectTextEncoding( ePreselectUnicode ); + } + + if (nCharSet >= 0 && ePreselectUnicode == RTL_TEXTENCODING_DONTKNOW) + mxLbCharSet->set_active(nCharSet); + + SetSelectedCharSet(); + mxLbCharSet->connect_changed( LINK( this, ScImportAsciiDlg, CharSetHdl ) ); + + mxLbCustomLang->SetLanguageList( + SvxLanguageListFlags::ALL | SvxLanguageListFlags::ONLY_KNOWN, false, false); + mxLbCustomLang->InsertLanguage(LANGUAGE_SYSTEM); + mxLbCustomLang->set_active_id(static_cast<LanguageType>(nLanguage)); + + // *** column type ListBox *** + OUString aColumnUser( ScResId( SCSTR_COLUMN_USER ) ); + for (sal_Int32 nIdx {0}; nIdx>=0; ) + { + mxLbType->append_text(aColumnUser.getToken(0, ';', nIdx)); + } + + mxLbType->connect_changed( LINK( this, ScImportAsciiDlg, LbColTypeHdl ) ); + mxLbType->set_sensitive(false); + + // *** table box preview *** + mxTableBox->Init(); + mxTableBox->SetUpdateTextHdl( LINK( this, ScImportAsciiDlg, UpdateTextHdl ) ); + mxTableBox->InitTypes( *mxLbType ); + mxTableBox->SetColTypeHdl( LINK( this, ScImportAsciiDlg, ColTypeHdl ) ); + + mxRbSeparated->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) ); + mxRbFixed->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) ); + + SetupSeparatorCtrls(); + RbSepFix(); + + UpdateVertical(); + + mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS ); + + if (meCall == SC_TEXTTOCOLUMNS) + { + mxFtCharSet->set_sensitive(false); + mxLbCharSet->set_sensitive(false); + mxFtCustomLang->set_sensitive(false); + mxLbCustomLang->set_active_id(LANGUAGE_SYSTEM); + mxLbCustomLang->set_sensitive(false); + mxFtRow->set_sensitive(false); + mxNfRow->set_sensitive(false); + + // Quoted field as text option is not used for text-to-columns mode. + mxCkbQuotedAsText->set_active(false); + mxCkbQuotedAsText->set_sensitive(false); + + // Always detect special numbers for text-to-columns mode. + mxCkbDetectNumber->set_active(true); + mxCkbDetectNumber->set_sensitive(false); + mxCkbDetectScientificNumber->set_active(true); + mxCkbDetectScientificNumber->set_sensitive(false); + } + if (meCall == SC_IMPORTFILE) + { + //Empty cells in imported file are empty + mxCkbSkipEmptyCells->set_active(false); + mxCkbSkipEmptyCells->hide(); + } + m_xDialog->SetInstallLOKNotifierHdl(LINK(this, ScImportAsciiDlg, InstallLOKNotifierHdl)); +} + +IMPL_STATIC_LINK_NOARG(ScImportAsciiDlg, InstallLOKNotifierHdl, void*, vcl::ILibreOfficeKitNotifier*) +{ + return GetpApp(); +} + +ScImportAsciiDlg::~ScImportAsciiDlg() +{ +} + +bool ScImportAsciiDlg::GetLine( sal_uLong nLine, OUString &rText, sal_Unicode& rcDetectSep ) +{ + if (nLine >= ASCIIDLG_MAXROWS || !mpDatStream) + return false; + + bool bRet = true; + bool bFixed = mxRbFixed->get_active(); + + if (!mpRowPosArray) + mpRowPosArray.reset( new sal_uLong[ASCIIDLG_MAXROWS + 2] ); + + if (!mnRowPosCount) // complete re-fresh + { + memset( mpRowPosArray.get(), 0, sizeof(mpRowPosArray[0]) * (ASCIIDLG_MAXROWS+2)); + + Seek(0); + mpDatStream->StartReadingUnicodeText( mpDatStream->GetStreamCharSet() ); + + mnStreamPos = mpDatStream->Tell(); + mpRowPosArray[mnRowPosCount] = mnStreamPos; + } + + if (nLine >= mnRowPosCount) + { + // need to work out some more line information + do + { + if (!Seek(mpRowPosArray[mnRowPosCount]) || !mpDatStream->good()) + { + bRet = false; + break; + } + rText = ReadCsvLine(*mpDatStream, !bFixed, maFieldSeparators, + mcTextSep, rcDetectSep, kMaxEmbeddedLinefeeds); + mnStreamPos = mpDatStream->Tell(); + mpRowPosArray[++mnRowPosCount] = mnStreamPos; + } while (nLine >= mnRowPosCount && mpDatStream->good()); + if (mpDatStream->eof() && + mnStreamPos == mpRowPosArray[mnRowPosCount-1]) + { + // the very end, not even an empty line read + bRet = false; + --mnRowPosCount; + } + } + else + { + Seek( mpRowPosArray[nLine]); + rText = ReadCsvLine(*mpDatStream, !bFixed, maFieldSeparators, mcTextSep, rcDetectSep, kMaxEmbeddedLinefeeds); + mnStreamPos = mpDatStream->Tell(); + } + + // If the file content isn't unicode, ReadUniStringLine + // may try to seek beyond the file's end and cause a CANTSEEK error + // (depending on the stream type). The error code has to be cleared, + // or further read operations (including non-unicode) will fail. + if ( mpDatStream->GetError() == ERRCODE_IO_CANTSEEK ) + mpDatStream->ResetError(); + + ScImportExport::EmbeddedNullTreatment( rText); + + return bRet; +} + +void ScImportAsciiDlg::GetOptions( ScAsciiOptions& rOpt ) +{ + rOpt.SetCharSet( meCharSet ); + rOpt.SetCharSetSystem( mbCharSetSystem ); + rOpt.SetLanguage(mxLbCustomLang->get_active_id()); + rOpt.SetFixedLen( mxRbFixed->get_active() ); + rOpt.SetStartRow( mxNfRow->get_value() ); + mxTableBox->FillColumnData( rOpt ); + if( mxRbSeparated->get_active() ) + { + rOpt.SetFieldSeps( GetSeparators() ); + rOpt.SetMergeSeps( mxCkbAsOnce->get_active() ); + rOpt.SetRemoveSpace( mxCkbRemoveSpace->get_active() ); + rOpt.SetTextSep( lcl_CharFromCombo( *mxCbTextSep, SCSTR_TEXTSEP ) ); + } + + rOpt.SetQuotedAsText(mxCkbQuotedAsText->get_active()); + rOpt.SetDetectSpecialNumber(mxCkbDetectNumber->get_active()); + rOpt.SetDetectScientificNumber(mxCkbDetectScientificNumber->get_active()); + rOpt.SetEvaluateFormulas(mxCkbEvaluateFormulas->get_active()); + rOpt.SetSkipEmptyCells(mxCkbSkipEmptyCells->get_active()); +} + +void ScImportAsciiDlg::SaveParameters() +{ + lcl_SaveSeparators( maFieldSeparators, mxCbTextSep->get_active_text(), mxCkbAsOnce->get_active(), + mxCkbQuotedAsText->get_active(), mxCkbDetectNumber->get_active(), mxCkbDetectScientificNumber->get_active(), + mxRbFixed->get_active(), + mxNfRow->get_value(), + mxLbCharSet->get_active(), + static_cast<sal_uInt16>(mxLbCustomLang->get_active_id()), + mxCkbSkipEmptyCells->get_active(), mxCkbRemoveSpace->get_active(), + mxCkbEvaluateFormulas->get_active(), meCall ); +} + +void ScImportAsciiDlg::SetSeparators( sal_Unicode cSep ) +{ + if (cSep) + { + // Exclusively set a separator, maFieldSeparators needs not be + // modified, it's obtained by GetSeparators() after this call. + constexpr sal_Unicode aSeps[] = { '\t', ';', ',', ' ' }; + for (const sal_Unicode c : aSeps) + { + const bool bSet = (c == cSep); + switch (c) + { + case '\t': mxCkbTab->set_active(bSet); break; + case ';': mxCkbSemicolon->set_active(bSet); break; + case ',': mxCkbComma->set_active(bSet); break; + case ' ': mxCkbSpace->set_active(bSet); break; + } + if (bSet) + cSep = 0; + } + if (cSep) + { + mxCkbOther->set_active(true); + mxEdOther->set_text(OUStringChar(cSep)); + } + } + else + { + for (sal_Int32 i = 0; i < maFieldSeparators.getLength(); ++i) + { + switch (maFieldSeparators[i]) + { + case '\t': mxCkbTab->set_active(true); break; + case ';': mxCkbSemicolon->set_active(true); break; + case ',': mxCkbComma->set_active(true); break; + case ' ': mxCkbSpace->set_active(true); break; + default: + mxCkbOther->set_active(true); + mxEdOther->set_text(mxEdOther->get_text() + OUStringChar(maFieldSeparators[i])); + } + } + } +} + +void ScImportAsciiDlg::SetSelectedCharSet() +{ + meCharSet = mxLbCharSet->GetSelectTextEncoding(); + mbCharSetSystem = (meCharSet == RTL_TEXTENCODING_DONTKNOW); + if( mbCharSetSystem ) + meCharSet = osl_getThreadTextEncoding(); +} + +OUString ScImportAsciiDlg::GetSeparators() const +{ + OUString aSepChars; + if( mxCkbTab->get_active() ) + aSepChars += "\t"; + if( mxCkbSemicolon->get_active() ) + aSepChars += ";"; + if( mxCkbComma->get_active() ) + aSepChars += ","; + if( mxCkbSpace->get_active() ) + aSepChars += " "; + if( mxCkbOther->get_active() ) + aSepChars += mxEdOther->get_text(); + return aSepChars; +} + +void ScImportAsciiDlg::SetupSeparatorCtrls() +{ + bool bEnable = mxRbSeparated->get_active(); + mxCkbTab->set_sensitive( bEnable ); + mxCkbSemicolon->set_sensitive( bEnable ); + mxCkbComma->set_sensitive( bEnable ); + mxCkbSpace->set_sensitive( bEnable ); + mxCkbRemoveSpace->set_sensitive( bEnable ); + mxCkbOther->set_sensitive( bEnable ); + mxEdOther->set_sensitive( bEnable ); + mxCkbAsOnce->set_sensitive( bEnable ); + mxFtTextSep->set_sensitive( bEnable ); + mxCbTextSep->set_sensitive( bEnable ); +} + +void ScImportAsciiDlg::UpdateVertical() +{ + mnRowPosCount = 0; + if (mpDatStream) + mpDatStream->SetStreamCharSet(meCharSet); +} + +void ScImportAsciiDlg::RbSepFix() +{ + weld::WaitObject aWaitObj(m_xDialog.get()); + if( mxRbFixed->get_active() ) + mxTableBox->SetFixedWidthMode(); + else + mxTableBox->SetSeparatorsMode(); + SetupSeparatorCtrls(); +} + +IMPL_LINK(ScImportAsciiDlg, RbSepFixHdl, weld::Toggleable&, rButton, void) +{ + if (!rButton.get_active()) + return; + RbSepFix(); +} + +IMPL_LINK(ScImportAsciiDlg, SeparatorClickHdl, weld::Toggleable&, rCtrl, void) +{ + SeparatorHdl(&rCtrl); +} + +IMPL_LINK( ScImportAsciiDlg, SeparatorComboBoxHdl, weld::ComboBox&, rCtrl, void ) +{ + SeparatorHdl(&rCtrl); +} + +IMPL_LINK( ScImportAsciiDlg, SeparatorEditHdl, weld::Entry&, rEdit, void ) +{ + SeparatorHdl(&rEdit); +} + +IMPL_LINK(ScImportAsciiDlg, OtherOptionsClickHdl, weld::Toggleable&, rCtrl, void) +{ + if (&rCtrl == mxCkbDetectNumber.get()) + { + if (mxCkbDetectNumber->get_active()) + { + mxCkbDetectScientificNumber->set_active(true); + mxCkbDetectScientificNumber->set_sensitive(false); + } + else + mxCkbDetectScientificNumber->set_sensitive(true); + return; + } +} + +void ScImportAsciiDlg::SeparatorHdl(const weld::Widget* pCtrl) +{ + OSL_ENSURE( pCtrl, "ScImportAsciiDlg::SeparatorHdl - missing sender" ); + OSL_ENSURE( !mxRbFixed->get_active(), "ScImportAsciiDlg::SeparatorHdl - not allowed in fixed width" ); + + /* #i41550# First update state of the controls. The GetSeparators() + function needs final state of the check boxes. */ + if (pCtrl == mxCkbOther.get() && mxCkbOther->get_active()) + mxEdOther->grab_focus(); + else if (pCtrl == mxEdOther.get()) + mxCkbOther->set_active(!mxEdOther->get_text().isEmpty()); + + OUString aOldFldSeps( maFieldSeparators); + maFieldSeparators = GetSeparators(); + sal_Unicode cOldSep = mcTextSep; + mcTextSep = lcl_CharFromCombo( *mxCbTextSep, SCSTR_TEXTSEP ); + // Any separator changed may result in completely different lines due to + // embedded line breaks. + if (cOldSep != mcTextSep || aOldFldSeps != maFieldSeparators) + UpdateVertical(); + + mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS ); +} + +IMPL_LINK_NOARG(ScImportAsciiDlg, CharSetHdl, weld::ComboBox&, void) +{ + if (mxLbCharSet->get_active() != -1) + { + weld::WaitObject aWaitObj(m_xDialog.get()); + rtl_TextEncoding eOldCharSet = meCharSet; + SetSelectedCharSet(); + // switching char-set invalidates 8bit -> String conversions + if (eOldCharSet != meCharSet) + UpdateVertical(); + + mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS ); + } +} + +IMPL_LINK(ScImportAsciiDlg, FirstRowHdl, weld::SpinButton&, rNumField, void) +{ + mxTableBox->GetGrid().Execute( CSVCMD_SETFIRSTIMPORTLINE, rNumField.get_value() - 1); +} + +IMPL_LINK(ScImportAsciiDlg, LbColTypeHdl, weld::ComboBox&, rListBox, void) +{ + if (&rListBox == mxLbType.get()) + mxTableBox->GetGrid().Execute(CSVCMD_SETCOLUMNTYPE, rListBox.get_active()); +} + +IMPL_LINK_NOARG(ScImportAsciiDlg, UpdateTextHdl, ScCsvTableBox&, void) +{ + // Checking the separator can only be done once for the very first time + // when the dialog wasn't already presented to the user. + // As a side effect this has the benefit that the check is only done on the + // first set of visible lines. + mbDetectSep = (mbDetectSep && !mxRbFixed->get_active() + && (!mxCkbTab->get_active() || !mxCkbSemicolon->get_active() + || !mxCkbComma->get_active() || !mxCkbSpace->get_active())); + sal_Unicode cDetectSep = (mbDetectSep ? 0 : 0xffff); + + sal_Int32 nBaseLine = mxTableBox->GetGrid().GetFirstVisLine(); + sal_Int32 nRead = mxTableBox->GetGrid().GetVisLineCount(); + // If mnRowPosCount==0, this is an initializing call, read ahead for row + // count and resulting scroll bar size and position to be able to scroll at + // all. When adding lines, read only the amount of next lines to be + // displayed. + if (!mnRowPosCount || nRead > CSV_PREVIEW_LINES) + nRead = CSV_PREVIEW_LINES; + + sal_Int32 i; + for (i = 0; i < nRead; i++) + { + if (!GetLine( nBaseLine + i, maPreviewLine[i], cDetectSep)) + break; + } + for (; i < CSV_PREVIEW_LINES; i++) + maPreviewLine[i].clear(); + + if (mbDetectSep) + { + mbDetectSep = false; + if (cDetectSep) + { + // Expect separator to be appended by now so all subsequent + // GetLine()/ReadCsvLine() actually used it. + assert(maFieldSeparators.endsWith(OUStringChar(cDetectSep))); + // Preselect separator in UI. + switch (cDetectSep) + { + case '\t': mxCkbTab->set_active(true); break; + case ';': mxCkbSemicolon->set_active(true); break; + case ',': mxCkbComma->set_active(true); break; + case ' ': mxCkbSpace->set_active(true); break; + } + } + } + + mxTableBox->GetGrid().Execute( CSVCMD_SETLINECOUNT, mnRowPosCount); + bool bMergeSep = mxCkbAsOnce->get_active(); + bool bRemoveSpace = mxCkbRemoveSpace->get_active(); + mxTableBox->SetUniStrings( maPreviewLine, maFieldSeparators, mcTextSep, bMergeSep, bRemoveSpace ); +} + +IMPL_LINK( ScImportAsciiDlg, ColTypeHdl, ScCsvTableBox&, rTableBox, void ) +{ + sal_Int32 nType = rTableBox.GetSelColumnType(); + sal_Int32 nTypeCount = mxLbType->get_count(); + bool bEmpty = (nType == CSV_TYPE_MULTI); + bool bEnable = ((0 <= nType) && (nType < nTypeCount)) || bEmpty; + + mxLbType->set_sensitive( bEnable ); + + if (bEmpty) + mxLbType->set_active(-1); + else if (bEnable) + mxLbType->set_active(nType); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/scuiimoptdlg.cxx b/sc/source/ui/dbgui/scuiimoptdlg.cxx new file mode 100644 index 0000000000..6e2bebd353 --- /dev/null +++ b/sc/source/ui/dbgui/scuiimoptdlg.cxx @@ -0,0 +1,340 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <scuiimoptdlg.hxx> +#include <scresid.hxx> +#include <strings.hrc> +#include <strings.hxx> +#include <officecfg/Office/Calc.hxx> +#include <osl/thread.h> +#include <rtl/tencinfo.h> +#include <imoptdlg.hxx> +#include <svx/txencbox.hxx> +#include <o3tl/string_view.hxx> +#include <utility> + +// ScDelimiterTable + +class ScDelimiterTable +{ +public: + explicit ScDelimiterTable( OUString aDelTab ) + : theDelTab (std::move( aDelTab )), + nDelIdx ( 0 ) + {} + + sal_uInt16 GetCode( std::u16string_view rDelimiter ) const; + OUString GetDelimiter( sal_Unicode nCode ) const; + + OUString FirstDel() { nDelIdx = 0; return theDelTab.getToken( 0, cSep, nDelIdx ); } + OUString NextDel() { return theDelTab.getToken( 1, cSep, nDelIdx ); } + +private: + const OUString theDelTab; + static constexpr sal_Unicode cSep {'\t'}; + sal_Int32 nDelIdx; +}; + +sal_uInt16 ScDelimiterTable::GetCode( std::u16string_view rDel ) const +{ + if (!theDelTab.isEmpty()) + { + sal_Int32 nIdx {0}; + + // Check even tokens: start from 0 and then skip 1 token at each iteration + if (rDel != o3tl::getToken(theDelTab, 0, cSep, nIdx )) + while (nIdx>0 && rDel != o3tl::getToken(theDelTab, 1, cSep, nIdx )); + + if (nIdx>0) + return static_cast<sal_Unicode>(o3tl::toInt32(o3tl::getToken(theDelTab, 0, cSep, nIdx ))); + } + + return 0; +} + +OUString ScDelimiterTable::GetDelimiter( sal_Unicode nCode ) const +{ + if (!theDelTab.isEmpty()) + { + sal_Int32 nIdx {0}; + // Check odd tokens: start from 1 and then skip 1 token at each iteration + do + { + sal_Int32 nPrevIdx {nIdx}; + if (nCode == static_cast<sal_Unicode>(o3tl::toInt32(o3tl::getToken(theDelTab, 1, cSep, nIdx )))) + return theDelTab.getToken( 0, cSep, nPrevIdx ); + } + while (nIdx>0); + } + + return OUString(); +} + +void ScImportOptionsDlg::FillFromTextEncodingTable(bool bExcludeImportSubsets, sal_uInt32 nExcludeInfoFlags) +{ + if (m_bIsAsciiImport) + m_xLbCharset->FillFromTextEncodingTable(bExcludeImportSubsets, nExcludeInfoFlags); + else + m_xTvCharset->FillFromTextEncodingTable(bExcludeImportSubsets, nExcludeInfoFlags); +} + +void ScImportOptionsDlg::FillFromDbTextEncodingMap(bool bExcludeImportSubsets, sal_uInt32 nExcludeInfoFlags) +{ + if (m_bIsAsciiImport) + m_xLbCharset->FillFromDbTextEncodingMap(bExcludeImportSubsets, nExcludeInfoFlags); + else + m_xTvCharset->FillFromDbTextEncodingMap(bExcludeImportSubsets, nExcludeInfoFlags); +} + +// ScImportOptionsDlg +ScImportOptionsDlg::ScImportOptionsDlg(weld::Window* pParent, bool bAscii, + const ScImportOptions* pOptions, + const OUString* pStrTitle, + bool bMultiByte, bool bOnlyDbtoolsEncodings, + bool bImport) + : GenericDialogController(pParent, "modules/scalc/ui/imoptdialog.ui", "ImOptDialog") + , m_bIsAsciiImport(bAscii) + , m_xFieldFrame(m_xBuilder->weld_frame("fieldframe")) + , m_xFtCharset(m_xBuilder->weld_label("charsetft")) + , m_xEncGrid(m_xBuilder->weld_widget("grid2")) + , m_xFtFieldSep(m_xBuilder->weld_label("fieldft")) + , m_xEdFieldSep(m_xBuilder->weld_combo_box("field")) + , m_xFtTextSep(m_xBuilder->weld_label("textft")) + , m_xEdTextSep(m_xBuilder->weld_combo_box("text")) + , m_xCbShown(m_xBuilder->weld_check_button("asshown")) + , m_xCbFormulas(m_xBuilder->weld_check_button("formulas")) + , m_xCbQuoteAll(m_xBuilder->weld_check_button("quoteall")) + , m_xCbFixed(m_xBuilder->weld_check_button("fixedwidth")) + , m_xLbCharset(new SvxTextEncodingBox(m_xBuilder->weld_combo_box("charsetdropdown"))) + , m_xTvCharset(new SvxTextEncodingTreeView(m_xBuilder->weld_tree_view("charsetlist"))) +{ + if (bAscii) + { + m_xDialog->set_help_id(m_xDialog->get_help_id() + "?config=NonTextImport"); + m_xLbCharset->show(); + m_xTvCharset->hide(); + } + else + { + m_xTvCharset->set_size_request(-1, m_xTvCharset->get_height_rows(6)); + m_xEncGrid->set_vexpand(true); + m_xLbCharset->hide(); + m_xTvCharset->show(); + } + + OUString sFieldSep(SCSTR_FIELDSEP); + sFieldSep = sFieldSep.replaceFirst( "%TAB", ScResId(SCSTR_FIELDSEP_TAB) ); + sFieldSep = sFieldSep.replaceFirst( "%SPACE", ScResId(SCSTR_FIELDSEP_SPACE) ); + + // not possible in the Ctor initializer (MSC cannot do that): + pFieldSepTab.reset( new ScDelimiterTable(sFieldSep) ); + pTextSepTab.reset( new ScDelimiterTable(SCSTR_TEXTSEP) ); + + OUString aStr = pFieldSepTab->FirstDel(); + + while (!aStr.isEmpty()) + { + m_xEdFieldSep->append_text(aStr); + aStr = pFieldSepTab->NextDel(); + } + + aStr = pTextSepTab->FirstDel(); + + while (!aStr.isEmpty()) + { + m_xEdTextSep->append_text(aStr); + aStr = pTextSepTab->NextDel(); + } + + m_xEdFieldSep->set_active(0); + m_xEdTextSep->set_active(0); + + if ( bOnlyDbtoolsEncodings ) + { + // Even dBase export allows multibyte now + if ( bMultiByte ) + FillFromDbTextEncodingMap( bImport ); + else + FillFromDbTextEncodingMap( bImport, RTL_TEXTENCODING_INFO_MULTIBYTE ); + } + else if ( !bAscii ) + { //!TODO: Unicode would need work in each filter + if ( bMultiByte ) + FillFromTextEncodingTable( bImport, RTL_TEXTENCODING_INFO_UNICODE ); + else + FillFromTextEncodingTable( bImport, RTL_TEXTENCODING_INFO_UNICODE | + RTL_TEXTENCODING_INFO_MULTIBYTE ); + } + else + { + if ( pOptions ) + { + sal_Unicode nCode = pOptions->nFieldSepCode; + aStr = pFieldSepTab->GetDelimiter( nCode ); + + if ( aStr.isEmpty() ) + m_xEdFieldSep->set_entry_text(OUString(nCode)); + else + m_xEdFieldSep->set_entry_text(aStr); + + nCode = pOptions->nTextSepCode; + aStr = pTextSepTab->GetDelimiter( nCode ); + + if ( aStr.isEmpty() ) + m_xEdTextSep->set_entry_text(OUString(nCode)); + else + m_xEdTextSep->set_entry_text(aStr); + } + // all encodings allowed, even Unicode + FillFromTextEncodingTable( bImport ); + } + + if( bAscii ) + { + sal_Int32 nCharSet = officecfg::Office::Calc::Dialogs::CSVExport::CharSet::get(); + OUString strFieldSeparator = officecfg::Office::Calc::Dialogs::CSVExport::FieldSeparator::get(); + OUString strTextSeparator = officecfg::Office::Calc::Dialogs::CSVExport::TextSeparator::get(); + bool bSaveTrueCellContent = officecfg::Office::Calc::Dialogs::CSVExport::SaveTrueCellContent::get(); + bool bSaveCellFormulas = officecfg::Office::Calc::Dialogs::CSVExport::SaveCellFormulas::get(); + bool bQuoteAllTextCells = officecfg::Office::Calc::Dialogs::CSVExport::QuoteAllTextCells::get(); + bool bFixedWidth = officecfg::Office::Calc::Dialogs::CSVExport::FixedWidth::get(); + + m_xCbFixed->show(); + m_xCbFixed->connect_toggled(LINK(this, ScImportOptionsDlg, FixedWidthHdl)); + m_xCbFixed->set_active( bFixedWidth ); + FixedWidthHdl(*m_xCbFixed); + m_xCbShown->show(); + m_xCbShown->set_active( bSaveTrueCellContent ); + m_xCbQuoteAll->show(); + m_xCbQuoteAll->set_active( bQuoteAllTextCells ); + m_xCbFormulas->show(); + // default option for "save formulas" no longer taken from view shell but from persisted dialog settings + m_xCbFormulas->set_active( bSaveCellFormulas ); + // if no charset, text separator or field separator exist, keep the values from dialog initialization + if (strFieldSeparator.getLength() > 0) + m_xEdFieldSep->set_entry_text(strFieldSeparator); + if (strTextSeparator.getLength() > 0) + m_xEdTextSep->set_entry_text(strTextSeparator); + if (nCharSet < 0 || nCharSet == RTL_TEXTENCODING_DONTKNOW ) + m_xLbCharset->SelectTextEncoding(pOptions ? pOptions->eCharSet : osl_getThreadTextEncoding()); + else + m_xLbCharset->SelectTextEncoding(nCharSet); + } + else + { + m_xFieldFrame->set_label(m_xFtCharset->get_label()); + m_xFtFieldSep->hide(); + m_xFtTextSep->hide(); + m_xFtCharset->hide(); + m_xEdFieldSep->hide(); + m_xEdTextSep->hide(); + m_xCbFixed->hide(); + m_xCbShown->hide(); + m_xCbQuoteAll->hide(); + m_xCbFormulas->hide(); + m_xTvCharset->grab_focus(); + m_xTvCharset->connect_row_activated(LINK(this, ScImportOptionsDlg, DoubleClickHdl)); + m_xTvCharset->SelectTextEncoding(pOptions ? pOptions->eCharSet : osl_getThreadTextEncoding()); + } + + // optional title: + if (pStrTitle) + m_xDialog->set_title(*pStrTitle); +} + +ScImportOptionsDlg::~ScImportOptionsDlg() +{ +} + +void ScImportOptionsDlg::GetImportOptions( ScImportOptions& rOptions ) const +{ + auto nEncoding = m_bIsAsciiImport ? m_xLbCharset->GetSelectTextEncoding() : m_xTvCharset->GetSelectTextEncoding(); + rOptions.SetTextEncoding(nEncoding); + + if (m_xCbFixed->get_visible()) + { + rOptions.nFieldSepCode = GetCodeFromCombo( *m_xEdFieldSep ); + rOptions.nTextSepCode = GetCodeFromCombo( *m_xEdTextSep ); + rOptions.bFixedWidth = m_xCbFixed->get_active(); + rOptions.bSaveAsShown = m_xCbShown->get_active(); + rOptions.bQuoteAllText = m_xCbQuoteAll->get_active(); + rOptions.bSaveFormulas = m_xCbFormulas->get_active(); + } +} + +sal_uInt16 ScImportOptionsDlg::GetCodeFromCombo(const weld::ComboBox& rEd) const +{ + ScDelimiterTable* pTab; + OUString aStr( rEd.get_active_text() ); + sal_uInt16 nCode; + + if (&rEd == m_xEdTextSep.get()) + pTab = pTextSepTab.get(); + else + pTab = pFieldSepTab.get(); + + if ( aStr.isEmpty() ) + { + nCode = 0; // no separator + } + else + { + nCode = pTab->GetCode( aStr ); + + if ( nCode == 0 ) + nCode = static_cast<sal_uInt16>(aStr[0]); + } + + return nCode; +} + +IMPL_LINK_NOARG(ScImportOptionsDlg, FixedWidthHdl, weld::Toggleable&, void) +{ + bool bEnable = !m_xCbFixed->get_active(); + m_xFtFieldSep->set_sensitive( bEnable ); + m_xEdFieldSep->set_sensitive( bEnable ); + m_xFtTextSep->set_sensitive( bEnable ); + m_xEdTextSep->set_sensitive( bEnable ); + m_xCbShown->set_sensitive( bEnable ); + m_xCbQuoteAll->set_sensitive( bEnable ); +} + +IMPL_LINK_NOARG(ScImportOptionsDlg, DoubleClickHdl, weld::TreeView&, bool) +{ + m_xDialog->response(RET_OK); + return true; +} + +void ScImportOptionsDlg::SaveImportOptions() const +{ + std::shared_ptr < comphelper::ConfigurationChanges > batch(comphelper::ConfigurationChanges::create()); + auto nEncoding = m_bIsAsciiImport ? m_xLbCharset->GetSelectTextEncoding() : m_xTvCharset->GetSelectTextEncoding(); + officecfg::Office::Calc::Dialogs::CSVExport::CharSet::set(nEncoding, batch); + officecfg::Office::Calc::Dialogs::CSVExport::FieldSeparator::set(m_xEdFieldSep->get_active_text(), batch); + officecfg::Office::Calc::Dialogs::CSVExport::TextSeparator::set(m_xEdTextSep->get_active_text(), batch); + officecfg::Office::Calc::Dialogs::CSVExport::FixedWidth::set(m_xCbFixed->get_active(), batch); + officecfg::Office::Calc::Dialogs::CSVExport::SaveCellFormulas::set(m_xCbFormulas->get_active(), batch); + officecfg::Office::Calc::Dialogs::CSVExport::SaveTrueCellContent::set(m_xCbShown->get_active(), batch); + officecfg::Office::Calc::Dialogs::CSVExport::QuoteAllTextCells::set(m_xCbQuoteAll->get_active(), batch); + batch->commit(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/sfiltdlg.cxx b/sc/source/ui/dbgui/sfiltdlg.cxx new file mode 100644 index 0000000000..3aeb319237 --- /dev/null +++ b/sc/source/ui/dbgui/sfiltdlg.cxx @@ -0,0 +1,435 @@ +/* -*- 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 <sfx2/dispatch.hxx> + +#include <uiitems.hxx> +#include <rangenam.hxx> +#include <reffact.hxx> +#include <viewdata.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <scresid.hxx> + +#include <foptmgr.hxx> + +#include <globstr.hrc> +#include <strings.hrc> + +#include <filtdlg.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +// DEFINE -------------------------------------------------------------------- + +namespace +{ + void ERRORBOX(weld::Window* pParent, TranslateId rid) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, + VclMessageType::Warning, VclButtonsType::Ok, + ScResId(rid))); + xBox->run(); + } +} + + +ScSpecialFilterDlg::ScSpecialFilterDlg( SfxBindings* pB, SfxChildWindow* pCW, weld::Window* pParent, + const SfxItemSet& rArgSet ) + + : ScAnyRefDlgController(pB, pCW, pParent, "modules/scalc/ui/advancedfilterdialog.ui", "AdvancedFilterDialog") + , aStrUndefined ( ScResId(SCSTR_UNDEFINED) ) + , nWhichQuery ( rArgSet.GetPool()->GetWhich( SID_QUERY ) ) + , theQueryData ( static_cast<const ScQueryItem&>( + rArgSet.Get( nWhichQuery )).GetQueryData() ) + , pViewData(nullptr) + , pDoc(nullptr) + , bRefInputMode(false) + , m_pRefInputEdit(nullptr) + , m_xLbFilterArea(m_xBuilder->weld_combo_box("lbfilterarea")) + , m_xEdFilterArea(new formula::RefEdit(m_xBuilder->weld_entry("edfilterarea"))) + , m_xRbFilterArea(new formula::RefButton(m_xBuilder->weld_button("rbfilterarea"))) + , m_xExpander(m_xBuilder->weld_expander("more")) + , m_xBtnCase(m_xBuilder->weld_check_button("case")) + , m_xBtnRegExp(m_xBuilder->weld_check_button("regexp")) + , m_xBtnHeader(m_xBuilder->weld_check_button("header")) + , m_xBtnUnique(m_xBuilder->weld_check_button("unique")) + , m_xBtnCopyResult(m_xBuilder->weld_check_button("copyresult")) + , m_xLbCopyArea(m_xBuilder->weld_combo_box("lbcopyarea")) + , m_xEdCopyArea(new formula::RefEdit(m_xBuilder->weld_entry("edcopyarea"))) + , m_xRbCopyArea(new formula::RefButton(m_xBuilder->weld_button("rbcopyarea"))) + , m_xBtnDestPers(m_xBuilder->weld_check_button("destpers")) + , m_xFtDbAreaLabel(m_xBuilder->weld_label("dbarealabel")) + , m_xFtDbArea(m_xBuilder->weld_label("dbarea")) + , m_xBtnOk(m_xBuilder->weld_button("ok")) + , m_xBtnCancel(m_xBuilder->weld_button("cancel")) + , m_xFilterFrame(m_xBuilder->weld_frame("filterframe")) + , m_xFilterLabel(m_xFilterFrame->weld_label_widget()) +{ + m_xEdFilterArea->SetReferences(this, m_xFilterLabel.get()); + m_xRbFilterArea->SetReferences(this, m_xEdFilterArea.get()); + m_xEdCopyArea->SetReferences(this, m_xFtDbAreaLabel.get()); + m_xRbCopyArea->SetReferences(this, m_xEdCopyArea.get()); + + Init( rArgSet ); + + Link<formula::RefEdit&, void> aLinkEdit = LINK(this, ScSpecialFilterDlg, RefInputEditHdl); + Link<formula::RefButton&, void> aLinkButton = LINK(this, ScSpecialFilterDlg, RefInputButtonHdl); + m_xEdCopyArea->SetGetFocusHdl(aLinkEdit); + m_xRbCopyArea->SetGetFocusHdl(aLinkButton); + m_xEdFilterArea->SetGetFocusHdl(aLinkEdit); + m_xRbFilterArea->SetGetFocusHdl(aLinkButton); + m_xEdCopyArea->SetLoseFocusHdl(aLinkEdit); + m_xRbCopyArea->SetLoseFocusHdl(aLinkButton); + m_xEdFilterArea->SetLoseFocusHdl(aLinkEdit); + m_xRbFilterArea->SetLoseFocusHdl(aLinkButton); + + m_xEdFilterArea->GrabFocus(); +} + +ScSpecialFilterDlg::~ScSpecialFilterDlg() +{ + pOptionsMgr.reset(); + + pOutItem.reset(); +} + +void ScSpecialFilterDlg::Init( const SfxItemSet& rArgSet ) +{ + const ScQueryItem& rQueryItem = static_cast<const ScQueryItem&>( + rArgSet.Get( nWhichQuery )); + + m_xBtnOk->connect_clicked( LINK( this, ScSpecialFilterDlg, EndDlgHdl ) ); + m_xBtnCancel->connect_clicked( LINK( this, ScSpecialFilterDlg, EndDlgHdl ) ); + m_xLbFilterArea->connect_changed( LINK( this, ScSpecialFilterDlg, FilterAreaSelHdl ) ); + m_xEdFilterArea->SetModifyHdl ( LINK( this, ScSpecialFilterDlg, FilterAreaModHdl ) ); + + pViewData = rQueryItem.GetViewData(); + pDoc = pViewData ? &pViewData->GetDocument() : nullptr; + + m_xEdFilterArea->SetText( OUString() ); // may be overwritten below + + if ( pViewData && pDoc ) + { + if(pDoc->GetChangeTrack()!=nullptr) m_xBtnCopyResult->set_sensitive(false); + + ScRangeName* pRangeNames = pDoc->GetRangeName(); + m_xLbFilterArea->clear(); + m_xLbFilterArea->append_text(aStrUndefined); + + for (const auto& rEntry : *pRangeNames) + { + if (!rEntry.second->HasType(ScRangeData::Type::Criteria)) + continue; + + OUString aSymbol = rEntry.second->GetSymbol(); + m_xLbFilterArea->append(aSymbol, rEntry.second->GetName()); + } + + // is there a stored source range? + + ScRange aAdvSource; + if (rQueryItem.GetAdvancedQuerySource(aAdvSource)) + { + OUString aRefStr(aAdvSource.Format(*pDoc, ScRefFlags::RANGE_ABS_3D, pDoc->GetAddressConvention())); + m_xEdFilterArea->SetRefString( aRefStr ); + } + } + + m_xLbFilterArea->set_active( 0 ); + + // let options be initialized: + + pOptionsMgr.reset( new ScFilterOptionsMgr( + pViewData, + theQueryData, + m_xBtnCase.get(), + m_xBtnRegExp.get(), + m_xBtnHeader.get(), + m_xBtnUnique.get(), + m_xBtnCopyResult.get(), + m_xBtnDestPers.get(), + m_xLbCopyArea.get(), + m_xEdCopyArea.get(), + m_xRbCopyArea.get(), + m_xFtDbAreaLabel.get(), + m_xFtDbArea.get(), + aStrUndefined ) ); + + // special filter always needs column headers + m_xBtnHeader->set_active(true); + m_xBtnHeader->set_sensitive(false); + + // turn on modal mode + // SetDispatcherLock( true ); + //@BugID 54702 enable/disable in base class only + //SFX_APPWINDOW->Disable(false); //! general method in ScAnyRefDlg +} + +void ScSpecialFilterDlg::Close() +{ + if (pViewData) + pViewData->GetDocShell()->CancelAutoDBRange(); + + DoClose( ScSpecialFilterDlgWrapper::GetChildWindowId() ); +} + +// Transfer of a table area selected with the mouse, which is then displayed +// as a new selection in the reference edit. + +void ScSpecialFilterDlg::SetReference( const ScRange& rRef, ScDocument& rDocP ) +{ + if ( !(bRefInputMode && m_pRefInputEdit) ) // only possible if in the reference edit mode + return; + + if ( rRef.aStart != rRef.aEnd ) + RefInputStart( m_pRefInputEdit ); + + OUString aRefStr; + const formula::FormulaGrammar::AddressConvention eConv = rDocP.GetAddressConvention(); + + if (m_pRefInputEdit == m_xEdCopyArea.get()) + aRefStr = rRef.aStart.Format(ScRefFlags::ADDR_ABS_3D, &rDocP, eConv); + else if (m_pRefInputEdit == m_xEdFilterArea.get()) + aRefStr = rRef.Format(rDocP, ScRefFlags::RANGE_ABS_3D, eConv); + + m_pRefInputEdit->SetRefString( aRefStr ); +} + +void ScSpecialFilterDlg::SetActive() +{ + if ( bRefInputMode ) + { + if (m_pRefInputEdit == m_xEdCopyArea.get()) + { + m_xEdCopyArea->GrabFocus(); + m_xEdCopyArea->GetModifyHdl().Call( *m_xEdCopyArea ); + } + else if (m_pRefInputEdit == m_xEdFilterArea.get()) + { + m_xEdFilterArea->GrabFocus(); + FilterAreaModHdl( *m_xEdFilterArea ); + } + } + else + m_xDialog->grab_focus(); + + RefInputDone(); +} + +ScQueryItem* ScSpecialFilterDlg::GetOutputItem( const ScQueryParam& rParam, + const ScRange& rSource ) +{ + pOutItem.reset(new ScQueryItem( nWhichQuery, &rParam )); + pOutItem->SetAdvancedQuerySource( &rSource ); + return pOutItem.get(); +} + +bool ScSpecialFilterDlg::IsRefInputMode() const +{ + return bRefInputMode; +} + +// Handler: + +IMPL_LINK(ScSpecialFilterDlg, EndDlgHdl, weld::Button&, rBtn, void) +{ + OSL_ENSURE( pDoc && pViewData, "Document or ViewData not found. :-/" ); + + if (&rBtn == m_xBtnOk.get() && pDoc && pViewData) + { + OUString theCopyStr( m_xEdCopyArea->GetText() ); + OUString theAreaStr( m_xEdFilterArea->GetText() ); + ScQueryParam theOutParam( theQueryData ); + ScAddress theAdrCopy; + bool bEditInputOk = true; + bool bQueryOk = false; + ScRange theFilterArea; + const formula::FormulaGrammar::AddressConvention eConv = pDoc->GetAddressConvention(); + + if ( m_xBtnCopyResult->get_active() ) + { + sal_Int32 nColonPos = theCopyStr.indexOf( ':' ); + + if ( -1 != nColonPos ) + theCopyStr = theCopyStr.copy( 0, nColonPos ); + + ScRefFlags nResult = theAdrCopy.Parse( theCopyStr, *pDoc, eConv ); + + if ( (nResult & ScRefFlags::VALID) == ScRefFlags::ZERO ) + { + if (!m_xExpander->get_expanded()) + m_xExpander->set_expanded(true); + + ERRORBOX(m_xDialog.get(), STR_INVALID_TABREF); + m_xEdCopyArea->GrabFocus(); + bEditInputOk = false; + } + } + + if ( bEditInputOk ) + { + ScRefFlags nResult = ScRange().Parse( theAreaStr, *pDoc, eConv ); + + if ( (nResult & ScRefFlags::VALID) == ScRefFlags::ZERO ) + { + ERRORBOX(m_xDialog.get(), STR_INVALID_TABREF); + m_xEdFilterArea->GrabFocus(); + bEditInputOk = false; + } + } + + if ( bEditInputOk ) + { + /* + * All edit fields contain valid areas. Now try to create + * a ScQueryParam from the filter area: + */ + + ScRefFlags nResult = theFilterArea.Parse( theAreaStr, *pDoc, eConv ); + + if ( (nResult & ScRefFlags::VALID) == ScRefFlags::VALID ) + { + ScAddress& rStart = theFilterArea.aStart; + ScAddress& rEnd = theFilterArea.aEnd; + + if ( m_xBtnCopyResult->get_active() ) + { + theOutParam.bInplace = false; + theOutParam.nDestTab = theAdrCopy.Tab(); + theOutParam.nDestCol = theAdrCopy.Col(); + theOutParam.nDestRow = theAdrCopy.Row(); + } + else + { + theOutParam.bInplace = true; + theOutParam.nDestTab = 0; + theOutParam.nDestCol = 0; + theOutParam.nDestRow = 0; + } + + theOutParam.bHasHeader = m_xBtnHeader->get_active(); + theOutParam.bByRow = true; + theOutParam.bCaseSens = m_xBtnCase->get_active(); + theOutParam.eSearchType = m_xBtnRegExp->get_active() ? utl::SearchParam::SearchType::Regexp : + utl::SearchParam::SearchType::Normal; + theOutParam.bDuplicate = !m_xBtnUnique->get_active(); + theOutParam.bDestPers = m_xBtnDestPers->get_active(); + + bQueryOk = pDoc->CreateQueryParam(ScRange(rStart,rEnd), theOutParam); + } + } + + if ( bQueryOk ) + { + SetDispatcherLock( false ); + SwitchToDocument(); + GetBindings().GetDispatcher()->ExecuteList(FID_FILTER_OK, + SfxCallMode::SLOT | SfxCallMode::RECORD, + { GetOutputItem(theOutParam, theFilterArea) }); + response(RET_OK); + } + else + { + ERRORBOX(m_xDialog.get(), STR_INVALID_QUERYAREA); + m_xEdFilterArea->GrabFocus(); + } + } + else if (&rBtn == m_xBtnCancel.get()) + { + response(RET_CANCEL); + } +} + +IMPL_LINK_NOARG(ScSpecialFilterDlg, RefInputEditHdl, formula::RefEdit&, void) +{ + RefInputHdl(); +} + +IMPL_LINK_NOARG(ScSpecialFilterDlg, RefInputButtonHdl, formula::RefButton&, void) +{ + RefInputHdl(); +} + +void ScSpecialFilterDlg::RefInputHdl() +{ + if (!m_xDialog->has_toplevel_focus()) + return; + + if( m_xEdCopyArea->GetWidget()->has_focus() || m_xRbCopyArea->GetWidget()->has_focus() ) + { + m_pRefInputEdit = m_xEdCopyArea.get(); + bRefInputMode = true; + } + else if( m_xEdFilterArea->GetWidget()->has_focus() || m_xRbFilterArea->GetWidget()->has_focus() ) + { + m_pRefInputEdit = m_xEdFilterArea.get(); + bRefInputMode = true; + } + else if( bRefInputMode ) + { + m_pRefInputEdit = nullptr; + bRefInputMode = false; + } +} + +IMPL_LINK(ScSpecialFilterDlg, FilterAreaSelHdl, weld::ComboBox&, rLb, void) +{ + if (&rLb == m_xLbFilterArea.get()) + { + OUString aString; + const sal_Int32 nSelPos = m_xLbFilterArea->get_active(); + + if ( nSelPos > 0 ) + aString = m_xLbFilterArea->get_id(nSelPos); + + m_xEdFilterArea->SetText( aString ); + } +} + +IMPL_LINK( ScSpecialFilterDlg, FilterAreaModHdl, formula::RefEdit&, rEd, void ) +{ + if (&rEd != m_xEdFilterArea.get()) + return; + + if ( pDoc && pViewData ) + { + OUString theCurAreaStr = rEd.GetText(); + ScRefFlags nResult = ScRange().Parse( theCurAreaStr, *pDoc ); + + if ( (nResult & ScRefFlags::VALID) == ScRefFlags::VALID ) + { + const sal_Int32 nCount = m_xLbFilterArea->get_count(); + for (sal_Int32 i = 1; i < nCount; ++i) + { + OUString aStr = m_xLbFilterArea->get_id(i); + if (theCurAreaStr == aStr) + { + m_xLbFilterArea->set_active( i ); + return; + } + } + m_xLbFilterArea->set_active( 0 ); + } + } + else + m_xLbFilterArea->set_active( 0 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/sortdlg.cxx b/sc/source/ui/dbgui/sortdlg.cxx new file mode 100644 index 0000000000..43978d1b85 --- /dev/null +++ b/sc/source/ui/dbgui/sortdlg.cxx @@ -0,0 +1,79 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <scui_def.hxx> +#include <tpsort.hxx> +#include <sortdlg.hxx> +#include <unotools/viewoptions.hxx> + +ScSortDlg::ScSortDlg(weld::Window* pParent, const SfxItemSet* pArgSet) + : SfxTabDialogController(pParent, "modules/scalc/ui/sortdialog.ui", "SortDialog", pArgSet) +{ + AddTabPage("criteria", ScTabPageSortFields::Create, nullptr); + AddTabPage("options", ScTabPageSortOptions::Create, nullptr); + + // restore dialog size + SvtViewOptions aDlgOpt(EViewType::Dialog, "SortDialog"); + if (aDlgOpt.Exists()) + m_xDialog->set_window_state(aDlgOpt.GetWindowState()); +} + +ScSortDlg::~ScSortDlg() +{ + // tdf#153852 - Make of sort dialog resizable (and remember size) + SvtViewOptions aDlgOpt(EViewType::Dialog, "SortDialog"); + OUString sWindowState = m_xDialog->get_window_state(vcl::WindowDataMask::PosSize); + aDlgOpt.SetWindowState(sWindowState); +} + +ScSortWarningDlg::ScSortWarningDlg(weld::Window* pParent, + std::u16string_view rExtendText, std::u16string_view rCurrentText) + : GenericDialogController(pParent, "modules/scalc/ui/sortwarning.ui", "SortWarning") + , m_xFtText(m_xBuilder->weld_label("sorttext")) + , m_xBtnExtSort(m_xBuilder->weld_button("extend")) + , m_xBtnCurSort(m_xBuilder->weld_button("current")) +{ + OUString sTextName = m_xFtText->get_label(); + sTextName = sTextName.replaceFirst("%1", rExtendText); + sTextName = sTextName.replaceFirst("%2", rCurrentText); + m_xFtText->set_label(sTextName); + + m_xBtnExtSort->connect_clicked( LINK( this, ScSortWarningDlg, BtnHdl ) ); + m_xBtnCurSort->connect_clicked( LINK( this, ScSortWarningDlg, BtnHdl ) ); +} + +ScSortWarningDlg::~ScSortWarningDlg() +{ +} + +IMPL_LINK(ScSortWarningDlg, BtnHdl, weld::Button&, rBtn, void) +{ + if (&rBtn == m_xBtnExtSort.get()) + { + m_xDialog->response(BTN_EXTEND_RANGE); + } + else if(&rBtn == m_xBtnCurSort.get()) + { + m_xDialog->response(BTN_CURRENT_SELECTION); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/sortkeydlg.cxx b/sc/source/ui/dbgui/sortkeydlg.cxx new file mode 100644 index 0000000000..0e9f05870a --- /dev/null +++ b/sc/source/ui/dbgui/sortkeydlg.cxx @@ -0,0 +1,81 @@ +/* -*- 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 <memory> +#include <sortkeydlg.hxx> +#include <comphelper/lok.hxx> +#include <vcl/svapp.hxx> + +#include <scresid.hxx> +#include <strings.hrc> + +ScSortKeyItem::ScSortKeyItem(weld::Container* pParent) + : m_xBuilder(Application::CreateBuilder(pParent, "modules/scalc/ui/sortkey.ui")) + , m_xFrame(m_xBuilder->weld_frame("SortKeyFrame")) + , m_xLbSort(m_xBuilder->weld_combo_box("sortlb")) + , m_xBtnUp(m_xBuilder->weld_radio_button("up")) + , m_xBtnDown(m_xBuilder->weld_radio_button("down")) + , m_xLabel(m_xBuilder->weld_label("lbColRow")) + , m_pParent(pParent) +{ + // tdf#136155 let the other elements in the dialog determine the width of the + // combobox + m_xLbSort->set_size_request(m_xLbSort->get_approximate_digit_width() * 12, -1); + // keep the UI static when switching the labels + const sal_Int32 nChars = std::max( ScResId(SCSTR_COLUMN).getLength(), ScResId(SCSTR_ROW).getLength() ) + 2; // +2 to avoid cut-off labels on kf5/gen + m_xLabel->set_size_request( m_xLabel->get_approximate_digit_width() * nChars, -1); +} + +ScSortKeyItem::~ScSortKeyItem() +{ + m_pParent->move(m_xFrame.get(), nullptr); +} + +void ScSortKeyItem::DisableField() +{ + m_xFrame->set_sensitive(false); +} + +void ScSortKeyItem::EnableField() +{ + m_xFrame->set_sensitive(true); +} + +ScSortKeyWindow::ScSortKeyWindow(weld::Container* pBox) + : m_pBox(pBox) +{ +} + +ScSortKeyWindow::~ScSortKeyWindow() +{ +} + +void ScSortKeyWindow::AddSortKey( sal_uInt16 nItemNumber ) +{ + ScSortKeyItem* pSortKeyItem = new ScSortKeyItem(m_pBox); + + // Set Sort key number + OUString aLine = pSortKeyItem->m_xFrame->get_label() + + OUString::number( nItemNumber ); + pSortKeyItem->m_xFrame->set_label(aLine); + + // for ui-testing. Distinguish the sort keys + if (!comphelper::LibreOfficeKit::isActive()) + { + if ( m_aSortKeyItems.size() > 0 ) + { + pSortKeyItem->m_xLbSort->set_buildable_name( + pSortKeyItem->m_xLbSort->get_buildable_name() + OUString::number(m_aSortKeyItems.size() + 1)); + } + } + + m_aSortKeyItems.push_back(std::unique_ptr<ScSortKeyItem>(pSortKeyItem)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/subtdlg.cxx b/sc/source/ui/dbgui/subtdlg.cxx new file mode 100644 index 0000000000..924716a6ff --- /dev/null +++ b/sc/source/ui/dbgui/subtdlg.cxx @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <tpsubt.hxx> +#include <subtdlg.hxx> +#include <scui_def.hxx> + +ScSubTotalDlg::ScSubTotalDlg(weld::Window* pParent, const SfxItemSet& rArgSet) + : SfxTabDialogController(pParent, "modules/scalc/ui/subtotaldialog.ui", "SubTotalDialog", &rArgSet) + , m_xBtnRemove(m_xBuilder->weld_button("remove")) +{ + AddTabPage("1stgroup", ScTpSubTotalGroup1::Create, nullptr); + AddTabPage("2ndgroup", ScTpSubTotalGroup2::Create, nullptr); + AddTabPage("3rdgroup", ScTpSubTotalGroup3::Create, nullptr); + AddTabPage("options", ScTpSubTotalOptions::Create, nullptr); + m_xBtnRemove->connect_clicked( LINK( this, ScSubTotalDlg, RemoveHdl ) ); +} + +ScSubTotalDlg::~ScSubTotalDlg() +{ +} + +IMPL_LINK_NOARG(ScSubTotalDlg, RemoveHdl, weld::Button&, void) +{ + m_xDialog->response(SCRET_REMOVE); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/textimportoptions.cxx b/sc/source/ui/dbgui/textimportoptions.cxx new file mode 100644 index 0000000000..e13bdfbd4a --- /dev/null +++ b/sc/source/ui/dbgui/textimportoptions.cxx @@ -0,0 +1,120 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <textimportoptions.hxx> +#include <svx/langbox.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <i18nlangtag/languagetag.hxx> + +ScTextImportOptionsDlg::ScTextImportOptionsDlg(weld::Window* pParent) + : GenericDialogController(pParent, "modules/scalc/ui/textimportoptions.ui", "TextImportOptionsDialog") + , m_xBtnOk(m_xBuilder->weld_button("ok")) + , m_xRbAutomatic(m_xBuilder->weld_radio_button("automatic")) + , m_xRbCustom(m_xBuilder->weld_radio_button("custom")) + , m_xCkbConvertDate(m_xBuilder->weld_check_button("convertdata")) + , m_xCkbConvertScientific(m_xBuilder->weld_check_button("convertscientificnotation")) + , m_xCkbKeepAsking(m_xBuilder->weld_check_button("keepasking")) + , m_xLbCustomLang(new SvxLanguageBox(m_xBuilder->weld_combo_box("lang"))) +{ + init(); +} + +ScTextImportOptionsDlg::~ScTextImportOptionsDlg() +{ +} + +LanguageType ScTextImportOptionsDlg::getLanguageType() const +{ + if (m_xRbAutomatic->get_active()) + return LANGUAGE_SYSTEM; + + return m_xLbCustomLang->get_active_id(); +} + +bool ScTextImportOptionsDlg::isDateConversionSet() const +{ + return m_xCkbConvertDate->get_active(); +} + +bool ScTextImportOptionsDlg::isScientificConversionSet() const +{ + return m_xCkbConvertScientific->get_active(); +} + +bool ScTextImportOptionsDlg::isKeepAskingSet() const +{ + return m_xCkbKeepAsking->get_active(); +} + +void ScTextImportOptionsDlg::init() +{ + m_xBtnOk->connect_clicked(LINK(this, ScTextImportOptionsDlg, OKHdl)); + Link<weld::Toggleable&,void> aLink = LINK(this, ScTextImportOptionsDlg, RadioCheckHdl); + m_xRbAutomatic->connect_toggled(aLink); + m_xRbCustom->connect_toggled(aLink); + m_xCkbConvertDate->connect_toggled(aLink); + m_xCkbConvertScientific->connect_toggled(aLink); + + m_xRbAutomatic->set_active(true); + + m_xLbCustomLang->SetLanguageList( + SvxLanguageListFlags::ALL | SvxLanguageListFlags::ONLY_KNOWN, false); + + LanguageType eLang = Application::GetSettings().GetLanguageTag().getLanguageType(); + m_xLbCustomLang->set_active_id(eLang); + m_xLbCustomLang->set_sensitive(false); +} + +IMPL_LINK_NOARG(ScTextImportOptionsDlg, OKHdl, weld::Button&, void) +{ + m_xDialog->response(RET_OK); +} + +IMPL_LINK(ScTextImportOptionsDlg, RadioCheckHdl, weld::Toggleable&, rBtn, void) +{ + if (&rBtn == m_xRbAutomatic.get()) + { + m_xLbCustomLang->set_sensitive(false); + } + else if (&rBtn == m_xRbCustom.get()) + { + m_xLbCustomLang->set_sensitive(true); + } + else if (&rBtn == m_xCkbConvertDate.get()) + { + if (m_xCkbConvertDate->get_active()) + { + m_xCkbConvertScientific->set_active(true); + m_xCkbConvertScientific->set_sensitive(false); + } + else + { + m_xCkbConvertScientific->set_sensitive(true); + } + } + else if (&rBtn == m_xCkbConvertScientific.get()) + { + assert( !m_xCkbConvertDate->get_active() && "ScTextImportOptionsDlg::RadioCheckHdl - scientific option disabled if Detect numbers active" ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/tpsort.cxx b/sc/source/ui/dbgui/tpsort.cxx new file mode 100644 index 0000000000..49390a404f --- /dev/null +++ b/sc/source/ui/dbgui/tpsort.cxx @@ -0,0 +1,873 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <svtools/collatorres.hxx> +#include <unotools/collatorwrapper.hxx> +#include <comphelper/processfactory.hxx> +#include <osl/diagnose.h> + +#include <scitems.hxx> +#include <uiitems.hxx> +#include <viewdata.hxx> +#include <document.hxx> +#include <global.hxx> +#include <dbdata.hxx> +#include <userlist.hxx> +#include <rangeutl.hxx> +#include <scresid.hxx> +#include <sc.hrc> +#include <strings.hrc> +#include <globstr.hrc> + +#include <sortkeydlg.hxx> + +#include <sortdlg.hxx> + +#include <tpsort.hxx> + +using namespace com::sun::star; + +/* + * Since the settings on the second Tab Page (Options) effects + * the first Tab Page, there must be a way for it to communicate with the + * other Page. + * + * At the moment this problem is solved through using two data members of the + * Tab Pages. If a page is enabled / disabled, it compares this data member + * with its own state (-> Activate() / Deactivate()). + * + * In the meantime the class SfxTabPage offers the following method: + * + * virtual sal_Bool HasExchangeSupport() const; -> return sal_True; + * virtual void ActivatePage(const SfxItemSet &); + * virtual int DeactivatePage(SfxItemSet * = 0); + * + * This still needs to be changed! + */ + +// Sort Criteria Tab page + +ScTabPageSortFields::ScTabPageSortFields(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet) + : SfxTabPage(pPage, pController, "modules/scalc/ui/sortcriteriapage.ui", "SortCriteriaPage", &rArgSet) + , + + m_aIdle("ScTabPageSortFields Scroll To End Idle"), + aStrUndefined ( ScResId( SCSTR_UNDEFINED ) ), + aStrColumn ( ScResId( SCSTR_COLUMN ) ), + aStrRow ( ScResId( SCSTR_ROW ) ), + aStrRowLabel ( ScResId( SCSTR_ROW_LABEL ) ), + aStrColLabel ( ScResId( SCSTR_COL_LABEL ) ), + + nWhichSort ( rArgSet.GetPool()->GetWhich( SID_SORT ) ), + pViewData ( nullptr ), + aSortData ( rArgSet.Get( nWhichSort ).GetSortData() ), + nFieldCount ( 0 ), + // show actual size of the sorting keys without limiting them to the default size + nSortKeyCount(std::max(aSortData.GetSortKeyCount(), static_cast<sal_uInt16>(DEFSORT))) + + , m_xTop(m_xBuilder->weld_container("TopWindow")) + , m_xBtnHeader(m_xBuilder->weld_check_button("cbHeader")) + , m_xBtnTopDown(m_xBuilder->weld_radio_button("rbTopDown")) + , m_xBtnLeftRight(m_xBuilder->weld_radio_button("rbLeftRight")) + , m_xScrolledWindow(m_xBuilder->weld_scrolled_window("SortCriteriaPage")) + , m_xBox(m_xBuilder->weld_container("SortKeyWindow")) + , m_aSortWin(m_xBox.get()) +{ + // tdf#147722 set some nominal small default height so the height adapts + // to all the other contents and the natural height of this widget isn't + // an input into the overall size + m_xScrolledWindow->set_size_request(-1, 42); + + Init(); + + m_aIdle.SetInvokeHandler(LINK(this, ScTabPageSortFields, ScrollToEndHdl)); + + SetExchangeSupport(); +} + +ScTabPageSortFields::~ScTabPageSortFields() +{ + m_aSortWin.m_aSortKeyItems.clear(); + m_xBox.reset(); + m_xScrolledWindow.reset(); +} + +void ScTabPageSortFields::Init() +{ + // Check whether the field that is passed on is a database field: + ScDocument* pDoc = pViewData ? &pViewData->GetDocument() : nullptr; + if ( pDoc ) + { + ScDBCollection* pDBColl = pDoc->GetDBCollection(); + const SCTAB nCurTab = pViewData->GetTabNo(); + if ( pDBColl ) + { + ScDBData* pDBData + = pDBColl->GetDBAtArea( nCurTab, + aSortData.nCol1, aSortData.nRow1, + aSortData.nCol2, aSortData.nRow2 ); + if ( pDBData ) + { + m_xBtnHeader->set_active(pDBData->HasHeader()); + } + } + } + m_xBtnHeader->set_label(aStrColLabel); + + Link<weld::Toggleable&,void> aLink = LINK(this, ScTabPageSortFields, SortDirHdl ); + m_xBtnTopDown->connect_toggled( aLink ); + m_xBtnLeftRight->connect_toggled( aLink ); + m_xBtnHeader->connect_toggled( aLink ); + + const ScSortItem& rSortItem = GetItemSet().Get( nWhichSort ); + + pViewData = rSortItem.GetViewData(); + OSL_ENSURE( pViewData, "ViewData not found!" ); + + nFieldArr.push_back( 0 ); + + // Create three sort key dialogs by default + for ( sal_uInt16 i=0; i<nSortKeyCount; i++ ) + { + AddSortKey(i+1); + m_aSortWin.m_aSortKeyItems[i]->m_xLbSort->connect_changed(LINK(this, ScTabPageSortFields, SelectHdl)); + } +} + +std::unique_ptr<SfxTabPage> ScTabPageSortFields::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* pArgSet) +{ + return std::make_unique<ScTabPageSortFields>(pPage, pController, *pArgSet); +} + +void ScTabPageSortFields::Reset( const SfxItemSet* /* rArgSet */ ) +{ + m_xBtnHeader->set_active( aSortData.bHasHeader ); + m_xBtnTopDown->set_active( aSortData.bByRow ); + m_xBtnLeftRight->set_active( !aSortData.bByRow ); + + if (m_aSortWin.m_aSortKeyItems[0]->m_xLbSort->get_count() == 0) + FillFieldLists(0); + + // ListBox selection: + if (!aSortData.maKeyState.empty() && aSortData.maKeyState[0].bDoSort) + { + // Make sure that the all sort keys are reset + for ( sal_uInt16 i=nSortKeyCount; i<aSortData.GetSortKeyCount(); i++ ) + { + AddSortKey(i+1); + m_aSortWin.m_aSortKeyItems[i]->m_xLbSort->connect_changed( LINK( this, + ScTabPageSortFields, SelectHdl ) ); + } + nSortKeyCount = aSortData.GetSortKeyCount(); + FillFieldLists(0); + + for ( sal_uInt16 i=0; i<nSortKeyCount; i++ ) + { + if (aSortData.maKeyState[i].bDoSort ) + { + m_aSortWin.m_aSortKeyItems[i]->m_xLbSort->set_active( GetFieldSelPos( + aSortData.maKeyState[i].nField ) ); + (aSortData.maKeyState[i].bAscending) + ? m_aSortWin.m_aSortKeyItems[i]->m_xBtnUp->set_active(true) + : m_aSortWin.m_aSortKeyItems[i]->m_xBtnDown->set_active(true); + } + else + { + m_aSortWin.m_aSortKeyItems[i]->m_xLbSort->set_active(0); // Select none + m_aSortWin.m_aSortKeyItems[i]->m_xBtnUp->set_active(true); + } + } + + // Enable or disable field depending on preceding Listbox selection + m_aSortWin.m_aSortKeyItems[0]->EnableField(); + for ( sal_uInt16 i=1; i<nSortKeyCount; i++ ) + if ( m_aSortWin.m_aSortKeyItems[i - 1]->m_xLbSort->get_active() == 0 ) + m_aSortWin.m_aSortKeyItems[i]->DisableField(); + else + m_aSortWin.m_aSortKeyItems[i]->EnableField(); + } + else + { + SCCOL nCol = pViewData->GetCurX(); + + if( nCol < aSortData.nCol1 ) + nCol = aSortData.nCol1; + else if( nCol > aSortData.nCol2 ) + nCol = aSortData.nCol2; + + sal_uInt16 nSort1Pos = nCol - aSortData.nCol1+1; + + m_aSortWin.m_aSortKeyItems[0]->m_xLbSort->set_active(nSort1Pos); + for ( sal_uInt16 i=1; i<nSortKeyCount; i++ ) + m_aSortWin.m_aSortKeyItems[i]->m_xLbSort->set_active(0); + + for ( sal_uInt16 i=0; i<nSortKeyCount; i++ ) + m_aSortWin.m_aSortKeyItems[i]->m_xBtnUp->set_active(true); + + m_aSortWin.m_aSortKeyItems[0]->EnableField(); + m_aSortWin.m_aSortKeyItems[1]->EnableField(); + for ( sal_uInt16 i=2; i<nSortKeyCount; i++ ) + m_aSortWin.m_aSortKeyItems[i]->DisableField(); + } + + // Make sure that there is always a last undefined sort key + if (m_aSortWin.m_aSortKeyItems[nSortKeyCount - 1]->m_xLbSort->get_active() > 0) + SetLastSortKey( nSortKeyCount ); +} + +bool ScTabPageSortFields::FillItemSet( SfxItemSet* rArgSet ) +{ + ScSortParam aNewSortData = aSortData; + + const SfxItemSet* pExample = GetDialogExampleSet(); + if (pExample) + { + if (const ScSortItem* pItem = pExample->GetItemIfSet(nWhichSort)) + { + ScSortParam aTempData = pItem->GetSortData(); + aTempData.maKeyState = aNewSortData.maKeyState; + aNewSortData = aTempData; + } + } + aNewSortData.bByRow = m_xBtnTopDown->get_active(); + aNewSortData.bHasHeader = m_xBtnHeader->get_active(); + + std::vector<sal_Int32> nSortPos; + + for ( sal_uInt16 i=0; i<nSortKeyCount; i++ ) + { + nSortPos.push_back(m_aSortWin.m_aSortKeyItems[i]->m_xLbSort->get_active()); + if (nSortPos[i] == -1) nSortPos[i] = 0; + } + + if( nSortKeyCount >= aNewSortData.GetSortKeyCount() ) + aNewSortData.maKeyState.resize(nSortKeyCount); + + if ( nSortPos[0] > 0 ) + { + for ( sal_uInt16 i=0; i<nSortKeyCount; i++ ) + { + aNewSortData.maKeyState[i].bDoSort = (nSortPos[i] > 0); + aNewSortData.maKeyState[i].nField = nFieldArr[nSortPos[i]]; + aNewSortData.maKeyState[i].bAscending = m_aSortWin.m_aSortKeyItems[i]->m_xBtnUp->get_active(); + } + } + else + { + for ( sal_uInt16 i=0; i<nSortKeyCount; i++ ) + aNewSortData.maKeyState[i].bDoSort = false; + } + + rArgSet->Put( ScSortItem( SCITEM_SORTDATA, nullptr, &aNewSortData ) ); + + return true; +} + +// for data exchange without dialogue detour: +void ScTabPageSortFields::ActivatePage( const SfxItemSet& rSet ) +{ + // Refresh local copy with shared data + aSortData = rSet.Get( SCITEM_SORTDATA ).GetSortData(); + + m_xBtnHeader->set_active( aSortData.bHasHeader ); + m_xBtnTopDown->set_active( aSortData.bByRow ); + m_xBtnLeftRight->set_active( !aSortData.bByRow ); +} + +DeactivateRC ScTabPageSortFields::DeactivatePage( SfxItemSet* pSetP ) +{ + if ( pSetP ) + FillItemSet( pSetP ); + + return DeactivateRC::LeavePage; +} + +void ScTabPageSortFields::FillFieldLists( sal_uInt16 nStartField ) +{ + if ( !pViewData ) + return; + + ScDocument& rDoc = pViewData->GetDocument(); + + for (sal_uInt16 j = nStartField; j < nSortKeyCount; ++j) + { + m_aSortWin.m_aSortKeyItems[j]->m_xLabel->set_label(aSortData.bByRow ? aStrColumn : aStrRow); + m_aSortWin.m_aSortKeyItems[j]->m_xLbSort->clear(); + m_aSortWin.m_aSortKeyItems[j]->m_xLbSort->freeze(); + m_aSortWin.m_aSortKeyItems[j]->m_xLbSort->append_text(aStrUndefined); + } + + SCCOL nFirstSortCol = aSortData.nCol1; + SCROW nFirstSortRow = aSortData.nRow1; + SCTAB nTab = pViewData->GetTabNo(); + sal_uInt16 i = 1; + nFieldArr.clear(); + nFieldArr.push_back(0); + + if ( aSortData.bByRow ) + { + OUString aFieldName; + SCCOL nMaxCol = rDoc.ClampToAllocatedColumns(nTab, aSortData.nCol2); + SCCOL col; + + for ( col=nFirstSortCol; col<=nMaxCol && i<SC_MAXFIELDS(rDoc.GetSheetLimits()); col++ ) + { + aFieldName = rDoc.GetString(col, nFirstSortRow, nTab); + if ( !aSortData.bHasHeader || aFieldName.isEmpty() ) + aFieldName = ScColToAlpha( col ); + nFieldArr.push_back( col ); + + for ( sal_uInt16 j=nStartField; j<nSortKeyCount; j++ ) + m_aSortWin.m_aSortKeyItems[j]->m_xLbSort->insert_text(i, aFieldName); + + i++; + } + } + else + { + OUString aFieldName; + SCROW nMaxRow = aSortData.nRow2; + SCROW row; + + for ( row=nFirstSortRow; row<=nMaxRow && i<SC_MAXFIELDS(rDoc.GetSheetLimits()); row++ ) + { + aFieldName = rDoc.GetString(nFirstSortCol, row, nTab); + if ( !aSortData.bHasHeader || aFieldName.isEmpty() ) + aFieldName = OUString::number( row+1); + nFieldArr.push_back( row ); + + for ( sal_uInt16 j=nStartField; j<nSortKeyCount; j++ ) + m_aSortWin.m_aSortKeyItems[j]->m_xLbSort->insert_text(i, aFieldName); + + i++; + } + } + + for (sal_uInt16 j=nStartField; j < nSortKeyCount; ++j) + { + m_aSortWin.m_aSortKeyItems[j]->m_xLbSort->thaw(); + } + + nFieldCount = i; +} + +sal_uInt16 ScTabPageSortFields::GetFieldSelPos( SCCOLROW nField ) +{ + sal_uInt16 nFieldPos = 0; + bool bFound = false; + + for ( sal_uInt16 n=1; n<nFieldCount && !bFound; n++ ) + { + if ( nFieldArr[n] == nField ) + { + nFieldPos = n; + bFound = true; + } + } + + return nFieldPos; +} + +void ScTabPageSortFields::SetLastSortKey( sal_uInt16 nItem ) +{ + // Extend local SortParam copy + const ScSortKeyState atempKeyState = { 0, false, true, ScColorSortMode::None, Color() }; + aSortData.maKeyState.push_back( atempKeyState ); + + // Add Sort Key Item + ++nSortKeyCount; + AddSortKey( nSortKeyCount ); + m_aSortWin.m_aSortKeyItems[nItem]->m_xLbSort->connect_changed( + LINK( this, ScTabPageSortFields, SelectHdl ) ); + + FillFieldLists( nItem ); + + // Set Status + m_aSortWin.m_aSortKeyItems[nItem]->m_xBtnUp->set_active(true); + m_aSortWin.m_aSortKeyItems[nItem]->m_xLbSort->set_active(0); +} + +// Handler: + +IMPL_LINK_NOARG(ScTabPageSortFields, SortDirHdl, weld::Toggleable&, void) +{ + if ( (m_xBtnTopDown->get_active() != aSortData.bByRow) || (m_xBtnHeader->get_active() != aSortData.bHasHeader)) + { + if (m_xBtnTopDown->get_active()) + m_xBtnHeader->set_label(aStrColLabel); + else + m_xBtnHeader->set_label(aStrRowLabel); + + aSortData.bByRow = m_xBtnTopDown->get_active(); + aSortData.bHasHeader = m_xBtnHeader->get_active(); + + // remember selection + std::vector<sal_uInt16> nCurSel; + for ( sal_uInt16 i=0; i<nSortKeyCount; i++ ) + nCurSel.push_back( m_aSortWin.m_aSortKeyItems[i]->m_xLbSort->get_active() ); + + FillFieldLists(0); + + for ( sal_uInt16 i=0; i<nSortKeyCount; i++ ) + m_aSortWin.m_aSortKeyItems[i]->m_xLbSort->set_active(nCurSel[i]); + } +} + +IMPL_LINK( ScTabPageSortFields, SelectHdl, weld::ComboBox&, rLb, void ) +{ + OUString aSelEntry = rLb.get_active_text(); + ScSortKeyItems::iterator pIter; + + // If last listbox is enabled add one item + if (m_aSortWin.m_aSortKeyItems.back()->m_xLbSort.get() == &rLb) + { + if ( aSelEntry != aStrUndefined ) + { + SetLastSortKey( nSortKeyCount ); + return; + } + } + + // Find selected listbox + pIter = std::find_if(m_aSortWin.m_aSortKeyItems.begin(), m_aSortWin.m_aSortKeyItems.end(), + [&rLb](const ScSortKeyItems::value_type& rItem) { return rItem->m_xLbSort.get() == &rLb; }); + + if (pIter == m_aSortWin.m_aSortKeyItems.end()) + return; + + // If not selecting the last Listbox, modify the succeeding ones + ++pIter; + if ( std::distance(m_aSortWin.m_aSortKeyItems.begin(), pIter) >= nSortKeyCount ) + return; + + if ( aSelEntry == aStrUndefined ) + { + for ( ; pIter != m_aSortWin.m_aSortKeyItems.end(); ++pIter ) + { + (*pIter)->m_xLbSort->set_active(0); + + (*pIter)->DisableField(); + } + } + else + { + (*pIter)->EnableField(); + } +} + +IMPL_LINK_NOARG(ScTabPageSortFields, ScrollToEndHdl, Timer*, void) +{ + m_xScrolledWindow->vadjustment_set_value(m_xScrolledWindow->vadjustment_get_upper()); +} + +void ScTabPageSortFields::AddSortKey( sal_uInt16 nItem ) +{ + m_aSortWin.AddSortKey(nItem); + m_aIdle.Start(); +} + +// Sort option Tab Page: + +ScTabPageSortOptions::ScTabPageSortOptions(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet) + : SfxTabPage(pPage, pController, "modules/scalc/ui/sortoptionspage.ui", "SortOptionsPage", &rArgSet) + , aStrUndefined(ScResId(SCSTR_UNDEFINED)) + , aStrCommentsRowLabel(ScResId(SCSTR_NOTES_ROW_LABEL)) + , aStrCommentsColLabel(ScResId(SCSTR_NOTES_COL_LABEL)) + , aStrImgRowLabel(ScResId(SCSTR_IMAGES_ROW_LABEL)) + , aStrImgColLabel(ScResId(SCSTR_IMAGES_COL_LABEL)) + , nWhichSort(rArgSet.GetPool()->GetWhich(SID_SORT)) + , aSortData(rArgSet.Get(nWhichSort).GetSortData()) + , pViewData(nullptr) + , pDoc(nullptr) + , m_xBtnCase(m_xBuilder->weld_check_button("case")) + , m_xBtnFormats(m_xBuilder->weld_check_button("formats")) + , m_xBtnNaturalSort(m_xBuilder->weld_check_button("naturalsort")) + , m_xBtnCopyResult(m_xBuilder->weld_check_button("copyresult")) + , m_xLbOutPos(m_xBuilder->weld_combo_box("outarealb")) + , m_xEdOutPos(m_xBuilder->weld_entry("outareaed")) + , m_xBtnSortUser(m_xBuilder->weld_check_button("sortuser")) + , m_xLbSortUser(m_xBuilder->weld_combo_box("sortuserlb")) + , m_xLbLanguage(new SvxLanguageBox(m_xBuilder->weld_combo_box("language"))) + , m_xFtAlgorithm(m_xBuilder->weld_label("algorithmft")) + , m_xLbAlgorithm(m_xBuilder->weld_combo_box("algorithmlb")) + , m_xBtnIncComments(m_xBuilder->weld_check_button("includenotes")) + , m_xBtnIncImages(m_xBuilder->weld_check_button("includeimages")) +{ + m_xLbSortUser->set_size_request(m_xLbSortUser->get_approximate_digit_width() * 50, -1); + m_xLbSortUser->set_accessible_description(ScResId(STR_A11Y_DESC_SORTUSER)); + Init(); + SetExchangeSupport(); +} + +void ScTabPageSortOptions::Init() +{ + // CollatorResource has user-visible names for sort algorithms + m_xColRes.reset(new CollatorResource); + + //! use CollatorWrapper from document? + m_oColWrap.emplace(comphelper::getProcessComponentContext()); + + const ScSortItem& rSortItem = GetItemSet().Get( nWhichSort ); + + m_xLbOutPos->connect_changed( LINK( this, ScTabPageSortOptions, SelOutPosHdl ) ); + m_xBtnCopyResult->connect_toggled( LINK( this, ScTabPageSortOptions, EnableHdl ) ); + m_xBtnSortUser->connect_toggled( LINK( this, ScTabPageSortOptions, EnableHdl ) ); + m_xLbLanguage->connect_changed( LINK( this, ScTabPageSortOptions, FillAlgorHdl ) ); + + pViewData = rSortItem.GetViewData(); + pDoc = pViewData ? &pViewData->GetDocument() : nullptr; + + OSL_ENSURE( pViewData, "ViewData not found! :-/" ); + + if ( pViewData && pDoc ) + { + const formula::FormulaGrammar::AddressConvention eConv = pDoc->GetAddressConvention(); + + m_xLbOutPos->clear(); + m_xLbOutPos->append_text(aStrUndefined); + m_xLbOutPos->set_sensitive(false); + + ScAreaNameIterator aIter( *pDoc ); + OUString aName; + ScRange aRange; + while ( aIter.Next( aName, aRange ) ) + { + OUString aRefStr(aRange.aStart.Format(ScRefFlags::ADDR_ABS_3D, pDoc, eConv)); + m_xLbOutPos->append(aRefStr, aName); + } + + m_xLbOutPos->set_active(0); + m_xEdOutPos->set_text(OUString()); + } + + m_xBtnIncComments->set_label(aStrCommentsColLabel); + m_xBtnIncImages->set_label(aStrImgColLabel); + + FillUserSortListBox(); + + // get available languages + + m_xLbLanguage->SetLanguageList( SvxLanguageListFlags::ALL | SvxLanguageListFlags::ONLY_KNOWN, false ); + m_xLbLanguage->InsertLanguage( LANGUAGE_SYSTEM ); +} + +std::unique_ptr<SfxTabPage> ScTabPageSortOptions::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rArgSet) +{ + return std::make_unique<ScTabPageSortOptions>(pPage, pController, *rArgSet); +} + +void ScTabPageSortOptions::Reset( const SfxItemSet* /* rArgSet */ ) +{ + if ( aSortData.bUserDef ) + { + m_xBtnSortUser->set_active(true); + m_xLbSortUser->set_sensitive(true); + m_xLbSortUser->set_active(aSortData.nUserIndex); + } + else + { + m_xBtnSortUser->set_active(false); + m_xLbSortUser->set_sensitive(false); + m_xLbSortUser->set_active(0); + } + + m_xBtnCase->set_active( aSortData.bCaseSens ); + m_xBtnFormats->set_active( aSortData.aDataAreaExtras.mbCellFormats ); + m_xBtnNaturalSort->set_active( aSortData.bNaturalSort ); + m_xBtnIncComments->set_active( aSortData.aDataAreaExtras.mbCellNotes ); + m_xBtnIncImages->set_active( aSortData.aDataAreaExtras.mbCellDrawObjects ); + + LanguageType eLang = LanguageTag::convertToLanguageType( aSortData.aCollatorLocale, false); + if ( eLang == LANGUAGE_DONTKNOW ) + eLang = LANGUAGE_SYSTEM; + m_xLbLanguage->set_active_id(eLang); + FillAlgor(); // get algorithms, select default + if ( !aSortData.aCollatorAlgorithm.isEmpty() ) + m_xLbAlgorithm->set_active_text(m_xColRes->GetTranslation(aSortData.aCollatorAlgorithm)); + + if ( pDoc && !aSortData.bInplace ) + { + ScRefFlags nFormat = (aSortData.nDestTab != pViewData->GetTabNo()) + ? ScRefFlags::RANGE_ABS_3D + : ScRefFlags::RANGE_ABS; + + theOutPos.Set( aSortData.nDestCol, + aSortData.nDestRow, + aSortData.nDestTab ); + + OUString aStr(theOutPos.Format(nFormat, pDoc, pDoc->GetAddressConvention())); + m_xBtnCopyResult->set_active(true); + m_xLbOutPos->set_sensitive(true); + m_xEdOutPos->set_sensitive(true); + m_xEdOutPos->set_text( aStr ); + EdOutPosModHdl(); + m_xEdOutPos->grab_focus(); + m_xEdOutPos->select_region(0, -1); + } + else + { + m_xBtnCopyResult->set_active( false ); + m_xLbOutPos->set_sensitive(false); + m_xEdOutPos->set_sensitive(false); + m_xEdOutPos->set_text( OUString() ); + } +} + +bool ScTabPageSortOptions::FillItemSet( SfxItemSet* rArgSet ) +{ + // Create local copy of ScParam + ScSortParam aNewSortData = aSortData; + + const SfxItemSet* pExample = GetDialogExampleSet(); + if (pExample) + { + if (const ScSortItem* pSortItem = pExample->GetItemIfSet(nWhichSort)) + aNewSortData = pSortItem->GetSortData(); + } + aNewSortData.bCaseSens = m_xBtnCase->get_active(); + aNewSortData.bNaturalSort = m_xBtnNaturalSort->get_active(); + aNewSortData.aDataAreaExtras.mbCellNotes = m_xBtnIncComments->get_active(); + aNewSortData.aDataAreaExtras.mbCellDrawObjects = m_xBtnIncImages->get_active(); + aNewSortData.aDataAreaExtras.mbCellFormats = m_xBtnFormats->get_active(); + aNewSortData.bInplace = !m_xBtnCopyResult->get_active(); + aNewSortData.nDestCol = theOutPos.Col(); + aNewSortData.nDestRow = theOutPos.Row(); + aNewSortData.nDestTab = theOutPos.Tab(); + aNewSortData.bUserDef = m_xBtnSortUser->get_active(); + aNewSortData.nUserIndex = (m_xBtnSortUser->get_active()) + ? m_xLbSortUser->get_active() + : 0; + + // get locale + LanguageType eLang = m_xLbLanguage->get_active_id(); + aNewSortData.aCollatorLocale = LanguageTag::convertToLocale( eLang, false); + + // get algorithm + OUString sAlg; + if ( eLang != LANGUAGE_SYSTEM ) + { + uno::Sequence<OUString> aAlgos = m_oColWrap->listCollatorAlgorithms( + aNewSortData.aCollatorLocale ); + const int nSel = m_xLbAlgorithm->get_active(); + if ( nSel < aAlgos.getLength() ) + sAlg = aAlgos[nSel]; + } + aNewSortData.aCollatorAlgorithm = sAlg; + + rArgSet->Put( ScSortItem( SCITEM_SORTDATA, &aNewSortData ) ); + + return true; +} + +// for data exchange without dialogue detour: +void ScTabPageSortOptions::ActivatePage( const SfxItemSet& rSet ) +{ + // Refresh local copy with shared data + aSortData = rSet.Get( SCITEM_SORTDATA ).GetSortData(); + ScSortDlg* pDlg = static_cast<ScSortDlg*>(GetDialogController()); + if (!pDlg) + return; + + if (aSortData.bByRow) + { + m_xBtnIncComments->set_label(aStrCommentsRowLabel); + m_xBtnIncImages->set_label(aStrImgRowLabel); + } + else + { + m_xBtnIncComments->set_label(aStrCommentsColLabel); + m_xBtnIncImages->set_label(aStrImgColLabel); + } +} + +DeactivateRC ScTabPageSortOptions::DeactivatePage( SfxItemSet* pSetP ) +{ + bool bPosInputOk = true; + + if ( m_xBtnCopyResult->get_active() ) + { + OUString thePosStr = m_xEdOutPos->get_text(); + ScAddress thePos; + sal_Int32 nColonPos = thePosStr.indexOf( ':' ); + + if ( -1 != nColonPos ) + thePosStr = thePosStr.copy( 0, nColonPos ); + + if ( pViewData ) + { + // visible table is default for input without table + // must be changed to GetRefTabNo when sorting has RefInput! + thePos.SetTab( pViewData->GetTabNo() ); + } + + ScRefFlags nResult = thePos.Parse( thePosStr, *pDoc, pDoc->GetAddressConvention() ); + + bPosInputOk = (nResult & ScRefFlags::VALID) == ScRefFlags::VALID; + + if ( !bPosInputOk ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Warning, VclButtonsType::Ok, + ScResId(STR_INVALID_TABREF))); + xBox->run(); + m_xEdOutPos->grab_focus(); + m_xEdOutPos->select_region(0, -1); + theOutPos.Set(0,0,0); + } + else + { + m_xEdOutPos->set_text(thePosStr); + theOutPos = thePos; + } + } + + if ( pSetP && bPosInputOk ) + FillItemSet( pSetP ); + + return bPosInputOk ? DeactivateRC::LeavePage : DeactivateRC::KeepPage; +} + +void ScTabPageSortOptions::FillUserSortListBox() +{ + ScUserList* pUserLists = ScGlobal::GetUserList(); + + m_xLbSortUser->clear(); + if ( pUserLists ) + { + size_t nCount = pUserLists->size(); + for (size_t i=0; i<nCount; ++i) + m_xLbSortUser->append_text((*pUserLists)[i].GetString()); + } +} + +// Handler: + +IMPL_LINK( ScTabPageSortOptions, EnableHdl, weld::Toggleable&, rButton, void ) +{ + if (&rButton == m_xBtnCopyResult.get()) + { + if (rButton.get_active()) + { + m_xLbOutPos->set_sensitive(true); + m_xEdOutPos->set_sensitive(true); + m_xEdOutPos->grab_focus(); + } + else + { + m_xLbOutPos->set_sensitive(false); + m_xEdOutPos->set_sensitive(false); + } + } + else if (&rButton == m_xBtnSortUser.get()) + { + if (rButton.get_active()) + { + m_xLbSortUser->set_sensitive(true); + m_xLbSortUser->grab_focus(); + } + else + m_xLbSortUser->set_sensitive(false); + } +} + +IMPL_LINK(ScTabPageSortOptions, SelOutPosHdl, weld::ComboBox&, rLb, void) +{ + if (&rLb == m_xLbOutPos.get()) + { + OUString aString; + const int nSelPos = m_xLbOutPos->get_active(); + + if (nSelPos > 0) + aString = m_xLbOutPos->get_id(nSelPos); + + m_xEdOutPos->set_text(aString); + } +} + +void ScTabPageSortOptions::EdOutPosModHdl() +{ + OUString theCurPosStr = m_xEdOutPos->get_text(); + ScRefFlags nResult = ScAddress().Parse( theCurPosStr, *pDoc, pDoc->GetAddressConvention() ); + + if ( (nResult & ScRefFlags::VALID) != ScRefFlags::VALID ) + return; + + bool bFound = false; + sal_Int32 i = 0; + const int nCount = m_xLbOutPos->get_count(); + + for ( i=2; i<nCount && !bFound; i++ ) + { + OUString aStr = m_xLbOutPos->get_id(i); + bFound = (theCurPosStr == aStr); + } + + if ( bFound ) + m_xLbOutPos->set_active(--i); + else + m_xLbOutPos->set_active(0); +} + +void ScTabPageSortOptions::FillAlgor() +{ + tools::Long nCount = 0; + + m_xLbAlgorithm->freeze(); + m_xLbAlgorithm->clear(); + + LanguageType eLang = m_xLbLanguage->get_active_id(); + if ( eLang == LANGUAGE_SYSTEM ) + { + // for LANGUAGE_SYSTEM no algorithm can be selected because + // it wouldn't necessarily exist for other languages + // -> leave list box empty if LANGUAGE_SYSTEM is selected + m_xFtAlgorithm->set_sensitive( false ); // nothing to select + m_xLbAlgorithm->set_sensitive( false ); // nothing to select + } + else + { + lang::Locale aLocale( LanguageTag::convertToLocale( eLang )); + const uno::Sequence<OUString> aAlgos = m_oColWrap->listCollatorAlgorithms( aLocale ); + + nCount = aAlgos.getLength(); + for (const OUString& sAlg : aAlgos) + { + OUString sUser = m_xColRes->GetTranslation( sAlg ); + m_xLbAlgorithm->append_text(sUser); + } + } + + m_xLbAlgorithm->thaw(); + + m_xLbAlgorithm->set_active(nCount ? 0 : -1); // first entry is default + m_xFtAlgorithm->set_sensitive(nCount > 1); // enable only if there is a choice + m_xLbAlgorithm->set_sensitive(nCount > 1); // enable only if there is a choice +} + +IMPL_LINK_NOARG(ScTabPageSortOptions, FillAlgorHdl, weld::ComboBox&, void) +{ + FillAlgor(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/tpsubt.cxx b/sc/source/ui/dbgui/tpsubt.cxx new file mode 100644 index 0000000000..b7e5b9bc4a --- /dev/null +++ b/sc/source/ui/dbgui/tpsubt.cxx @@ -0,0 +1,621 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <scitems.hxx> +#include <uiitems.hxx> +#include <global.hxx> +#include <userlist.hxx> +#include <viewdata.hxx> +#include <document.hxx> +#include <scresid.hxx> +#include <sc.hrc> +#include <strings.hrc> +#include <subtotals.hrc> + +#include <tpsubt.hxx> +#include <tpsort.hxx> +#include <memory> + +#include <osl/diagnose.h> + +// Subtotals group tabpage: + +ScTpSubTotalGroup::ScTpSubTotalGroup(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet, const sal_uInt16& rTabNumber) + : SfxTabPage(pPage, pController, "modules/scalc/ui/subtotalgrppage.ui", "SubTotalGrpPage", &rArgSet) + , aStrNone(ScResId(SCSTR_NONE)) + , aStrColumn(ScResId(SCSTR_COLUMN_LETTER)) + , pViewData(nullptr) + , pDoc(nullptr) + , nWhichSubTotals(rArgSet.GetPool()->GetWhich(SID_SUBTOTALS)) + , rSubTotalData(rArgSet.Get(nWhichSubTotals).GetSubTotalData()) + , nFieldCount(0) + , mxLbGroup(m_xBuilder->weld_combo_box("group_by")) + , mxLbColumns(m_xBuilder->weld_tree_view("columns")) + , mxLbFunctions(m_xBuilder->weld_tree_view("functions")) + , mxLbSelectAllColumns(m_xBuilder->weld_check_button("select_all_columns_button")) +{ + for (size_t i = 0; i < SAL_N_ELEMENTS(SCSTR_SUBTOTALS); ++i) + mxLbFunctions->append_text(ScResId(SCSTR_SUBTOTALS[i])); + + auto nHeight = mxLbColumns->get_height_rows(14); + mxLbColumns->set_size_request(-1, nHeight); + mxLbFunctions->set_size_request(-1, nHeight); + + mxLbColumns->enable_toggle_buttons(weld::ColumnToggleType::Check); + + Init(); + + // UI tests + mxLbGroup->set_buildable_name(mxLbGroup->get_buildable_name() + OUString::number(rTabNumber)); + mxLbColumns->set_buildable_name(mxLbColumns->get_buildable_name() + OUString::number(rTabNumber)); +} + +ScTpSubTotalGroup::~ScTpSubTotalGroup() +{ +} + +void ScTpSubTotalGroup::Init() +{ + const ScSubTotalItem& rSubTotalItem = GetItemSet().Get( nWhichSubTotals ); + + pViewData = rSubTotalItem.GetViewData(); + assert(pViewData && "CreateScSubTotalDlg aArgSet must contain a ScSubTotalItem with ViewData set"); + pDoc = &pViewData->GetDocument(); + assert(pDoc && "Document not found :-("); + + mxLbGroup->connect_changed( LINK( this, ScTpSubTotalGroup, SelectListBoxHdl ) ); + mxLbColumns->connect_changed( LINK( this, ScTpSubTotalGroup, SelectTreeListBoxHdl ) ); + mxLbColumns->connect_toggled( LINK( this, ScTpSubTotalGroup, CheckHdl ) ); + mxLbFunctions->connect_changed( LINK( this, ScTpSubTotalGroup, SelectTreeListBoxHdl) ); + mxLbSelectAllColumns->connect_toggled( LINK( this, ScTpSubTotalGroup, CheckBoxHdl ) ); + + mnFieldArr.resize(SC_MAXFIELDS(pDoc->GetSheetLimits())); + mnFieldArr[0] = 0; + FillListBoxes(); +} + +namespace +{ + int GetCheckedEntryCount(weld::TreeView& rTreeView) + { + int nRet = 0; + + rTreeView.all_foreach([&](const weld::TreeIter& rEntry) { + if ( rTreeView.get_toggle(rEntry) == TRISTATE_TRUE ) + ++nRet; + return false; + }); + + return nRet; + } +} + +bool ScTpSubTotalGroup::DoReset( sal_uInt16 nGroupNo, + const SfxItemSet& rArgSet ) +{ + sal_uInt16 nGroupIdx = 0; + + OSL_ENSURE( (nGroupNo<=3) && (nGroupNo>0), "Invalid group" ); + + if ( (nGroupNo > 3) || (nGroupNo == 0) ) + return false; + else + nGroupIdx = nGroupNo-1; + + // first we have to clear the listboxes... + for (int nLbEntry = 0, nCount = mxLbColumns->n_children(); nLbEntry < nCount; ++nLbEntry) + { + mxLbColumns->set_toggle(nLbEntry, TRISTATE_FALSE); + mxLbColumns->set_id(nLbEntry, "0"); + } + mxLbFunctions->select(0); + + const ScSubTotalParam & theSubTotalData( rArgSet.Get( nWhichSubTotals ).GetSubTotalData() ); + + if ( theSubTotalData.bGroupActive[nGroupIdx] ) + { + SCCOL nField = theSubTotalData.nField[nGroupIdx]; + SCCOL nSubTotals = theSubTotalData.nSubTotals[nGroupIdx]; + SCCOL* pSubTotals = theSubTotalData.pSubTotals[nGroupIdx].get(); + ScSubTotalFunc* pFunctions = theSubTotalData.pFunctions[nGroupIdx].get(); + + mxLbGroup->set_active( GetFieldSelPos( nField )+1 ); + + sal_uInt16 nFirstChecked = 0; + for ( sal_uInt16 i=0; i<nSubTotals; i++ ) + { + sal_uInt16 nCheckPos = GetFieldSelPos( pSubTotals[i] ); + + mxLbColumns->set_toggle(nCheckPos, TRISTATE_TRUE); + mxLbColumns->set_id(nCheckPos, OUString::number(FuncToLbPos(pFunctions[i]))); + + if (i == 0 || nCheckPos < nFirstChecked) + nFirstChecked = nCheckPos; + } + // Select the first checked field from the top. + mxLbColumns->select(nFirstChecked); + } + else + { + mxLbGroup->set_active( (nGroupNo == 1) ? 1 : 0 ); + mxLbColumns->select( 0 ); + mxLbFunctions->select( 0 ); + } + + if ( mxLbColumns->n_children() == GetCheckedEntryCount(*mxLbColumns) ) + mxLbSelectAllColumns->set_active( true ); + else + mxLbSelectAllColumns->set_active( false ); + + return true; +} + +bool ScTpSubTotalGroup::DoFillItemSet( sal_uInt16 nGroupNo, + SfxItemSet& rArgSet ) +{ + sal_uInt16 nGroupIdx = 0; + + OSL_ENSURE( (nGroupNo<=3) && (nGroupNo>0), "Invalid group" ); + OSL_ENSURE( (mxLbGroup->get_count() > 0) + && (mxLbColumns->n_children() > 0) + && (mxLbFunctions->n_children() > 0), + "Non-initialized Lists" ); + + if ( (nGroupNo > 3) || (nGroupNo == 0) + || (mxLbGroup->get_count() == 0) + || (mxLbColumns->n_children() == 0) + || (mxLbFunctions->n_children() == 0) + ) + return false; + else + nGroupIdx = nGroupNo-1; + + ScSubTotalParam theSubTotalData; // read out, if already partly filled + const SfxItemSet* pExample = GetDialogExampleSet(); + if (pExample) + { + if (const ScSubTotalItem* pItem = pExample->GetItemIfSet(nWhichSubTotals)) + theSubTotalData = pItem->GetSubTotalData(); + } + + std::unique_ptr<ScSubTotalFunc[]> pFunctions; + std::unique_ptr<SCCOL[]> pSubTotals; + const sal_Int32 nGroup = mxLbGroup->get_active(); + const sal_Int32 nEntryCount = mxLbColumns->n_children(); + const sal_Int32 nCheckCount = GetCheckedEntryCount(*mxLbColumns); + + theSubTotalData.nCol1 = rSubTotalData.nCol1; + theSubTotalData.nRow1 = rSubTotalData.nRow1; + theSubTotalData.nCol2 = rSubTotalData.nCol2; + theSubTotalData.nRow2 = rSubTotalData.nRow2; + theSubTotalData.bGroupActive[nGroupIdx] = (nGroup != 0); + theSubTotalData.nField[nGroupIdx] = (nGroup != 0) + ? mnFieldArr[nGroup-1] + : static_cast<SCCOL>(0); + + if ( nEntryCount>0 && nCheckCount>0 && nGroup!=0 ) + { + sal_uInt16 nFunction = 0; + + pSubTotals.reset(new SCCOL [nCheckCount]); + pFunctions.reset(new ScSubTotalFunc [nCheckCount]); + + for ( sal_Int32 i=0, nCheck=0; i<nEntryCount; i++ ) + { + if (mxLbColumns->get_toggle(i) == TRISTATE_TRUE) + { + OSL_ENSURE( nCheck <= nCheckCount, + "Range error :-(" ); + nFunction = mxLbColumns->get_id(i).toUInt32(); + pSubTotals[nCheck] = mnFieldArr[i]; + pFunctions[nCheck] = LbPosToFunc( nFunction ); + nCheck++; + } + } + theSubTotalData.SetSubTotals( nGroupNo, // group number + pSubTotals.get(), + pFunctions.get(), + nCheckCount ); // number of array elements + + } + + rArgSet.Put( ScSubTotalItem( SCITEM_SUBTDATA, nullptr, &theSubTotalData ) ); + + return true; +} + +void ScTpSubTotalGroup::FillListBoxes() +{ + assert(pViewData && pDoc && "CreateScSubTotalDlg aArgSet must contain a ScSubTotalItem with ViewData set"); + + SCCOL nFirstCol = rSubTotalData.nCol1; + SCROW nFirstRow = rSubTotalData.nRow1; + SCTAB nTab = pViewData->GetTabNo(); + SCCOL nMaxCol = rSubTotalData.nCol2; + SCCOL col; + OUString aFieldName; + + mxLbGroup->clear(); + mxLbColumns->clear(); + mxLbGroup->insert_text(0, aStrNone ); + + mxLbColumns->freeze(); + sal_uInt16 i=0; + for ( col=nFirstCol; col<=nMaxCol && i<SC_MAXFIELDS(pDoc->GetSheetLimits()); col++ ) + { + aFieldName = pDoc->GetString(col, nFirstRow, nTab); + if ( aFieldName.isEmpty() ) + { + aFieldName = ScGlobal::ReplaceOrAppend( aStrColumn, u"%1", ScColToAlpha( col )); + } + mnFieldArr[i] = col; + mxLbGroup->insert_text(i+1, aFieldName); + mxLbColumns->insert(i); + mxLbColumns->set_toggle(i, TRISTATE_FALSE); + mxLbColumns->set_text(i, aFieldName, 0); + mxLbColumns->set_id(i, "0"); + i++; + } + mxLbColumns->thaw(); + + // subsequent initialization of the constant: + nFieldCount = i; +} + +sal_uInt16 ScTpSubTotalGroup::GetFieldSelPos( SCCOL nField ) +{ + sal_uInt16 nFieldPos = 0; + bool bFound = false; + + for ( sal_uInt16 n=0; n<nFieldCount && !bFound; n++ ) + { + if ( mnFieldArr[n] == nField ) + { + nFieldPos = n; + bFound = true; + } + } + + return nFieldPos; +} + +ScSubTotalFunc ScTpSubTotalGroup::LbPosToFunc( sal_uInt16 nPos ) +{ + switch ( nPos ) + { +// case 0: return SUBTOTAL_FUNC_NONE; + case 2: return SUBTOTAL_FUNC_AVE; + case 6: return SUBTOTAL_FUNC_CNT; + case 1: return SUBTOTAL_FUNC_CNT2; + case 3: return SUBTOTAL_FUNC_MAX; + case 4: return SUBTOTAL_FUNC_MIN; + case 5: return SUBTOTAL_FUNC_PROD; + case 7: return SUBTOTAL_FUNC_STD; + case 8: return SUBTOTAL_FUNC_STDP; + case 0: return SUBTOTAL_FUNC_SUM; + case 9: return SUBTOTAL_FUNC_VAR; + case 10: return SUBTOTAL_FUNC_VARP; + default: + OSL_FAIL( "ScTpSubTotalGroup::LbPosToFunc" ); + return SUBTOTAL_FUNC_NONE; + } +} + +sal_uInt16 ScTpSubTotalGroup::FuncToLbPos( ScSubTotalFunc eFunc ) +{ + switch ( eFunc ) + { +// case SUBTOTAL_FUNC_NONE: return 0; + case SUBTOTAL_FUNC_AVE: return 2; + case SUBTOTAL_FUNC_CNT: return 6; + case SUBTOTAL_FUNC_CNT2: return 1; + case SUBTOTAL_FUNC_MAX: return 3; + case SUBTOTAL_FUNC_MIN: return 4; + case SUBTOTAL_FUNC_PROD: return 5; + case SUBTOTAL_FUNC_STD: return 7; + case SUBTOTAL_FUNC_STDP: return 8; + case SUBTOTAL_FUNC_SUM: return 0; + case SUBTOTAL_FUNC_VAR: return 9; + case SUBTOTAL_FUNC_VARP: return 10; + default: + OSL_FAIL( "ScTpSubTotalGroup::FuncToLbPos" ); + return 0; + } +} + +// Handler: + +IMPL_LINK(ScTpSubTotalGroup, SelectTreeListBoxHdl, weld::TreeView&, rLb, void) +{ + SelectHdl(&rLb); + + if ( mxLbColumns->n_children() == GetCheckedEntryCount(*mxLbColumns) ) + mxLbSelectAllColumns->set_active( true ); + else + mxLbSelectAllColumns->set_active( false ); +} + +IMPL_LINK(ScTpSubTotalGroup, SelectListBoxHdl, weld::ComboBox&, rLb, void) +{ + SelectHdl(&rLb); +} + +void ScTpSubTotalGroup::SelectHdl(const weld::Widget *pLb) +{ + const sal_Int32 nColumn = mxLbColumns->get_selected_index(); + if (nColumn == -1) + return; + + const sal_Int32 nFunction = mxLbFunctions->get_selected_index(); + sal_uInt16 nOldFunction = mxLbColumns->get_id(nColumn).toUInt32(); + + if ( pLb == mxLbColumns.get() ) + { + mxLbFunctions->select(nOldFunction); + } + else if ( pLb == mxLbFunctions.get() ) + { + mxLbColumns->set_id(nColumn, OUString::number(nFunction)); + mxLbColumns->set_toggle(nColumn, TRISTATE_TRUE); + } +} + +IMPL_LINK( ScTpSubTotalGroup, CheckHdl, const weld::TreeView::iter_col&, rRowCol, void ) +{ + mxLbColumns->select(rRowCol.first); + SelectHdl(mxLbColumns.get()); + + if ( mxLbColumns->n_children() == GetCheckedEntryCount(*mxLbColumns) ) + mxLbSelectAllColumns->set_active( true ); + else + mxLbSelectAllColumns->set_active( false ); +} + +// Derived Group TabPages: + +std::unique_ptr<SfxTabPage> ScTpSubTotalGroup1::Create( weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet* rArgSet ) +{ + return std::make_unique<ScTpSubTotalGroup1>( pPage, pController, *rArgSet ); +} + +std::unique_ptr<SfxTabPage> ScTpSubTotalGroup2::Create( weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet* rArgSet ) +{ + return std::make_unique<ScTpSubTotalGroup2>( pPage, pController, *rArgSet ); +} + +std::unique_ptr<SfxTabPage> ScTpSubTotalGroup3::Create( weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet* rArgSet ) +{ + return std::make_unique<ScTpSubTotalGroup3>( pPage, pController, *rArgSet ); +} + +ScTpSubTotalGroup1::ScTpSubTotalGroup1( weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet ) : + ScTpSubTotalGroup( pPage, pController, rArgSet, 1 ) +{} + +ScTpSubTotalGroup2::ScTpSubTotalGroup2( weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet ) : + ScTpSubTotalGroup( pPage, pController, rArgSet, 2 ) +{} + +ScTpSubTotalGroup3::ScTpSubTotalGroup3( weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet ) : + ScTpSubTotalGroup( pPage, pController, rArgSet, 3 ) +{} + +#define RESET(i) (ScTpSubTotalGroup::DoReset( (i), *rArgSet )) +void ScTpSubTotalGroup1::Reset( const SfxItemSet* rArgSet ) { RESET(1); } +void ScTpSubTotalGroup2::Reset( const SfxItemSet* rArgSet ) { RESET(2); } +void ScTpSubTotalGroup3::Reset( const SfxItemSet* rArgSet ) { RESET(3); } +#undef RESET + +#define FILLSET(i) (ScTpSubTotalGroup::DoFillItemSet( (i), *rArgSet )) +bool ScTpSubTotalGroup1::FillItemSet( SfxItemSet* rArgSet ) { return FILLSET(1); } +bool ScTpSubTotalGroup2::FillItemSet( SfxItemSet* rArgSet ) { return FILLSET(2); } +bool ScTpSubTotalGroup3::FillItemSet( SfxItemSet* rArgSet ) { return FILLSET(3); } +#undef FILL + +// options tab page: + +ScTpSubTotalOptions::ScTpSubTotalOptions(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet) + + : SfxTabPage ( pPage, pController, + "modules/scalc/ui/subtotaloptionspage.ui", "SubTotalOptionsPage", + &rArgSet ), + pViewData ( nullptr ), + pDoc ( nullptr ), + nWhichSubTotals ( rArgSet.GetPool()->GetWhich( SID_SUBTOTALS ) ), + rSubTotalData ( rArgSet.Get( nWhichSubTotals ).GetSubTotalData() ) + , m_xBtnPagebreak(m_xBuilder->weld_check_button("pagebreak")) + , m_xBtnCase(m_xBuilder->weld_check_button("case")) + , m_xBtnSort(m_xBuilder->weld_check_button("sort")) + , m_xFlSort(m_xBuilder->weld_label("label2")) + , m_xBtnAscending(m_xBuilder->weld_radio_button("ascending")) + , m_xBtnDescending(m_xBuilder->weld_radio_button("descending")) + , m_xBtnFormats(m_xBuilder->weld_check_button("formats")) + , m_xBtnUserDef(m_xBuilder->weld_check_button("btnuserdef")) + , m_xLbUserDef(m_xBuilder->weld_combo_box("lbuserdef")) +{ + m_xLbUserDef->set_accessible_description(ScResId(STR_A11Y_DESC_USERDEF)); + m_xBtnUserDef->set_accessible_description(ScResId(STR_A11Y_DESC_USERDEF)); + Init(); +} + +ScTpSubTotalOptions::~ScTpSubTotalOptions() +{ +} + +void ScTpSubTotalOptions::Init() +{ + const ScSubTotalItem& rSubTotalItem = GetItemSet().Get( nWhichSubTotals ); + + pViewData = rSubTotalItem.GetViewData(); + assert(pViewData && "CreateScSubTotalDlg aArgSet must contain a ScSubTotalItem with ViewData set"); + pDoc = &pViewData->GetDocument(); + assert(pDoc && "Document not found!"); + + m_xBtnSort->connect_toggled( LINK( this, ScTpSubTotalOptions, CheckHdl ) ); + m_xBtnUserDef->connect_toggled( LINK( this, ScTpSubTotalOptions, CheckHdl ) ); + + FillUserSortListBox(); +} + +std::unique_ptr<SfxTabPage> ScTpSubTotalOptions::Create(weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet* rArgSet) +{ + return std::make_unique<ScTpSubTotalOptions>(pPage, pController, *rArgSet); +} + +void ScTpSubTotalOptions::Reset( const SfxItemSet* /* rArgSet */ ) +{ + m_xBtnPagebreak->set_active( rSubTotalData.bPagebreak ); + m_xBtnCase->set_active( rSubTotalData.bCaseSens ); + m_xBtnFormats->set_active( rSubTotalData.bIncludePattern ); + m_xBtnSort->set_active( rSubTotalData.bDoSort ); + m_xBtnAscending->set_active( rSubTotalData.bAscending ); + m_xBtnDescending->set_active( !rSubTotalData.bAscending ); + + if ( rSubTotalData.bUserDef ) + { + m_xBtnUserDef->set_active(true); + m_xLbUserDef->set_sensitive(true); + m_xLbUserDef->set_active(rSubTotalData.nUserIndex); + } + else + { + m_xBtnUserDef->set_active( false ); + m_xLbUserDef->set_sensitive(false); + m_xLbUserDef->set_active(0); + } + + CheckHdl(*m_xBtnSort); +} + +bool ScTpSubTotalOptions::FillItemSet( SfxItemSet* rArgSet ) +{ + ScSubTotalParam theSubTotalData; // read out, if already partly filled + const SfxItemSet* pExample = GetDialogExampleSet(); + if (pExample) + { + if (const ScSubTotalItem* pItem = pExample->GetItemIfSet(nWhichSubTotals)) + theSubTotalData = pItem->GetSubTotalData(); + } + + theSubTotalData.bPagebreak = m_xBtnPagebreak->get_active(); + theSubTotalData.bReplace = true; + theSubTotalData.bCaseSens = m_xBtnCase->get_active(); + theSubTotalData.bIncludePattern = m_xBtnFormats->get_active(); + theSubTotalData.bDoSort = m_xBtnSort->get_active(); + theSubTotalData.bAscending = m_xBtnAscending->get_active(); + theSubTotalData.bUserDef = m_xBtnUserDef->get_active(); + theSubTotalData.nUserIndex = (m_xBtnUserDef->get_active()) + ? m_xLbUserDef->get_active() + : 0; + + rArgSet->Put( ScSubTotalItem( nWhichSubTotals, nullptr, &theSubTotalData ) ); + + return true; +} + +void ScTpSubTotalOptions::FillUserSortListBox() +{ + ScUserList* pUserLists = ScGlobal::GetUserList(); + + m_xLbUserDef->freeze(); + m_xLbUserDef->clear(); + if ( pUserLists ) + { + size_t nCount = pUserLists->size(); + for ( size_t i=0; i<nCount; ++i ) + m_xLbUserDef->append_text((*pUserLists)[i].GetString() ); + } + m_xLbUserDef->thaw(); +} + +// Handler: + +IMPL_LINK(ScTpSubTotalOptions, CheckHdl, weld::Toggleable&, rBox, void) +{ + if (&rBox == m_xBtnSort.get()) + { + if ( m_xBtnSort->get_active() ) + { + m_xFlSort->set_sensitive(true); + m_xBtnFormats->set_sensitive(true); + m_xBtnUserDef->set_sensitive(true); + m_xBtnAscending->set_sensitive(true); + m_xBtnDescending->set_sensitive(true); + + if ( m_xBtnUserDef->get_active() ) + m_xLbUserDef->set_sensitive(true); + } + else + { + m_xFlSort->set_sensitive(false); + m_xBtnFormats->set_sensitive(false); + m_xBtnUserDef->set_sensitive(false); + m_xBtnAscending->set_sensitive(false); + m_xBtnDescending->set_sensitive(false); + m_xLbUserDef->set_sensitive(false); + } + } + else if (&rBox == m_xBtnUserDef.get()) + { + if ( m_xBtnUserDef->get_active() ) + { + m_xLbUserDef->set_sensitive(true); + m_xLbUserDef->grab_focus(); + } + else + m_xLbUserDef->set_sensitive(false); + } +} + +IMPL_LINK(ScTpSubTotalGroup, CheckBoxHdl, weld::Toggleable&, rBox, void) +{ + if (&rBox != mxLbSelectAllColumns.get()) + return; + + bool bChecked = mxLbSelectAllColumns->get_active(); + + mxLbColumns->all_foreach([&](const weld::TreeIter& rEntry) { + if ( bChecked ) + mxLbColumns->set_toggle(rEntry, TRISTATE_TRUE); + else + mxLbColumns->set_toggle(rEntry, TRISTATE_FALSE); + + return false; + }); +} + +ScTpSubTotalGroup1::~ScTpSubTotalGroup1() +{ +} + +ScTpSubTotalGroup2::~ScTpSubTotalGroup2() +{ +} + +ScTpSubTotalGroup3::~ScTpSubTotalGroup3() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/dbgui/validate.cxx b/sc/source/ui/dbgui/validate.cxx new file mode 100644 index 0000000000..f8565e9643 --- /dev/null +++ b/sc/source/ui/dbgui/validate.cxx @@ -0,0 +1,931 @@ +/* -*- 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 . + */ + +#ifdef SC_DLLIMPLEMENTATION +#undef SC_DLLIMPLEMENTATION +#endif + +#include <com/sun/star/sheet/TableValidationVisibility.hpp> +#include <comphelper/string.hxx> +#include <svl/stritem.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <sfx2/app.hxx> +#include <o3tl/string_view.hxx> + +#include <scresid.hxx> +#include <strings.hrc> + +#include <stringutil.hxx> +#include <validat.hxx> +#include <validate.hxx> +#include <compiler.hxx> +#include <formula/opcode.hxx> + +// cell range picker +#include <tabvwsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/childwin.hxx> +#include <reffact.hxx> +#include <comphelper/lok.hxx> + + +#define IS_MOBILE (comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current() && SfxViewShell::Current()->isLOKMobilePhone()) + +/* Position indexes for "Allow" list box. + They do not map directly to ScValidationMode and can safely be modified to + change the order of the list box entries. */ +#define SC_VALIDDLG_ALLOW_ANY 0 +#define SC_VALIDDLG_ALLOW_WHOLE 1 +#define SC_VALIDDLG_ALLOW_DECIMAL 2 +#define SC_VALIDDLG_ALLOW_DATE 3 +#define SC_VALIDDLG_ALLOW_TIME 4 +#define SC_VALIDDLG_ALLOW_RANGE 5 +#define SC_VALIDDLG_ALLOW_LIST 6 +#define SC_VALIDDLG_ALLOW_TEXTLEN 7 +#define SC_VALIDDLG_ALLOW_CUSTOM 8 + +/* Position indexes for "Data" list box. + They do not map directly to ScConditionMode and can safely be modified to + change the order of the list box entries. */ +#define SC_VALIDDLG_DATA_EQUAL 0 +#define SC_VALIDDLG_DATA_LESS 1 +#define SC_VALIDDLG_DATA_GREATER 2 +#define SC_VALIDDLG_DATA_EQLESS 3 +#define SC_VALIDDLG_DATA_EQGREATER 4 +#define SC_VALIDDLG_DATA_NOTEQUAL 5 +#define SC_VALIDDLG_DATA_VALIDRANGE 6 +#define SC_VALIDDLG_DATA_INVALIDRANGE 7 +#define SC_VALIDDLG_DATA_DIRECT 8 + +namespace ValidListType = css::sheet::TableValidationVisibility; + +const WhichRangesContainer ScTPValidationValue::pValueRanges(svl::Items< + FID_VALID_LISTTYPE, FID_VALID_LISTTYPE, + FID_VALID_MODE, FID_VALID_ERRTEXT +>); + +ScValidationDlg::ScValidationDlg(weld::Window* pParent, const SfxItemSet* pArgSet, + ScTabViewShell *pTabViewSh) + : ScValidationDlgBase(pParent, + "modules/scalc/ui/validationdialog.ui", "ValidationDialog", pArgSet, nullptr) + , m_pTabVwSh(pTabViewSh) + , m_sValuePageId("criteria") + , m_bOwnRefHdlr(false) + , m_bRefInputting(false) + , m_xHBox(m_xBuilder->weld_container("refinputbox")) +{ + AddTabPage(m_sValuePageId, ScTPValidationValue::Create, nullptr); + AddTabPage("inputhelp", ScTPValidationHelp::Create, nullptr); + AddTabPage("erroralert", ScTPValidationError::Create, nullptr); + + if (IS_MOBILE) + { + m_xBuilder->weld_button("cancel")->hide(); + m_xBuilder->weld_button("help")->hide(); + } +} + +void ScValidationDlg::EndDialog(int nResponse) +{ + // tdf#155708 - do not close, just hide validation window if we click in another sheet + if (nResponse == nCloseResponseToJustHide && getDialog()->get_visible()) + { + getDialog()->hide(); + return; + } + // tdf#137215 ensure original modality of true is restored before dialog loop ends + if (m_bOwnRefHdlr) + RemoveRefDlg(true); + ScValidationDlgBase::EndDialog(nResponse); +} + +ScValidationDlg::~ScValidationDlg() +{ + if (m_bOwnRefHdlr) + RemoveRefDlg(false); +} + +void ScTPValidationValue::SetReferenceHdl( const ScRange&rRange , const ScDocument& rDoc ) +{ + if ( rRange.aStart != rRange.aEnd ) + if ( ScValidationDlg *pValidationDlg = GetValidationDlg() ) + if( m_pRefEdit ) + pValidationDlg->RefInputStart( m_pRefEdit ); + + if ( m_pRefEdit ) + { + OUString aStr(rRange.Format(rDoc, ScRefFlags::RANGE_ABS_3D, rDoc.GetAddressConvention())); + m_pRefEdit->SetRefString( aStr ); + } +} + +void ScTPValidationValue:: SetActiveHdl() +{ + if ( m_pRefEdit ) m_pRefEdit->GrabFocus(); + + if ( ScValidationDlg *pValidationDlg = GetValidationDlg() ) + if( m_pRefEdit ) + { + pValidationDlg->RefInputDone(); + } +} + +void ScTPValidationValue::RefInputStartPreHdl( formula::RefEdit* pEdit, const formula::RefButton* pButton ) +{ + ScValidationDlg *pValidationDlg = GetValidationDlg(); + if (!pValidationDlg) + return; + + weld::Container* pNewParent = pValidationDlg->get_refinput_shrink_parent(); + if (pEdit == m_pRefEdit && pNewParent != m_pRefEditParent) + { + m_xRefGrid->move(m_pRefEdit->GetWidget(), pNewParent); + m_pRefEditParent = pNewParent; + } + + if (pNewParent != m_pBtnRefParent) + { + // if Edit SetParent but button not, the tab order will be + // incorrect, so move button anyway, and restore + // parent later in order to restore the tab order. But + // hide it if it's moved but unwanted. + m_xRefGrid->move(m_xBtnRef->GetWidget(), pNewParent); + m_xBtnRef->GetWidget()->set_visible(pButton == m_xBtnRef.get()); + m_pBtnRefParent = pNewParent; + } + + pNewParent->show(); +} + +void ScTPValidationValue::RefInputDonePostHdl() +{ + if (ScValidationDlg *pValidationDlg = GetValidationDlg()) + { + weld::Container* pOldParent = pValidationDlg->get_refinput_shrink_parent(); + + if (m_pRefEdit && m_pRefEditParent != m_xRefGrid.get()) + { + pOldParent->move(m_pRefEdit->GetWidget(), m_xRefGrid.get()); + m_pRefEditParent = m_xRefGrid.get(); + } + + if (m_pBtnRefParent != m_xRefGrid.get()) + { + pOldParent->move(m_xBtnRef->GetWidget(), m_xRefGrid.get()); + m_xBtnRef->GetWidget()->show(); + m_pBtnRefParent = m_xRefGrid.get(); + } + + pOldParent->hide(); + ScViewData& rViewData = pValidationDlg->GetTabViewShell()->GetViewData(); + SCTAB nCurTab = rViewData.GetTabNo(); + SCTAB nRefTab = rViewData.GetRefTabNo(); + // If RefInput switched to a different sheet from the data sheet, + // switch back: fdo#53920 + if ( nCurTab != nRefTab ) + { + rViewData.GetViewShell()->SetTabNo( nRefTab ); + } + } + + if (m_pRefEdit && !m_pRefEdit->GetWidget()->has_focus()) + m_pRefEdit->GrabFocus(); +} + +namespace { + +/** Converts the passed ScValidationMode to the position in the list box. */ +sal_uInt16 lclGetPosFromValMode( ScValidationMode eValMode ) +{ + sal_uInt16 nLbPos = SC_VALIDDLG_ALLOW_ANY; + switch( eValMode ) + { + case SC_VALID_ANY: nLbPos = SC_VALIDDLG_ALLOW_ANY; break; + case SC_VALID_WHOLE: nLbPos = SC_VALIDDLG_ALLOW_WHOLE; break; + case SC_VALID_DECIMAL: nLbPos = SC_VALIDDLG_ALLOW_DECIMAL; break; + case SC_VALID_DATE: nLbPos = SC_VALIDDLG_ALLOW_DATE; break; + case SC_VALID_TIME: nLbPos = SC_VALIDDLG_ALLOW_TIME; break; + case SC_VALID_TEXTLEN: nLbPos = SC_VALIDDLG_ALLOW_TEXTLEN; break; + case SC_VALID_LIST: nLbPos = SC_VALIDDLG_ALLOW_RANGE; break; + case SC_VALID_CUSTOM: nLbPos = SC_VALIDDLG_ALLOW_CUSTOM; break; + default: OSL_FAIL( "lclGetPosFromValMode - unknown validity mode" ); + } + return nLbPos; +} + +/** Converts the passed list box position to an ScValidationMode. */ +ScValidationMode lclGetValModeFromPos( sal_uInt16 nLbPos ) +{ + ScValidationMode eValMode = SC_VALID_ANY; + switch( nLbPos ) + { + case SC_VALIDDLG_ALLOW_ANY: eValMode = SC_VALID_ANY; break; + case SC_VALIDDLG_ALLOW_WHOLE: eValMode = SC_VALID_WHOLE; break; + case SC_VALIDDLG_ALLOW_DECIMAL: eValMode = SC_VALID_DECIMAL; break; + case SC_VALIDDLG_ALLOW_DATE: eValMode = SC_VALID_DATE; break; + case SC_VALIDDLG_ALLOW_TIME: eValMode = SC_VALID_TIME; break; + case SC_VALIDDLG_ALLOW_RANGE: eValMode = SC_VALID_LIST; break; + case SC_VALIDDLG_ALLOW_LIST: eValMode = SC_VALID_LIST; break; + case SC_VALIDDLG_ALLOW_TEXTLEN: eValMode = SC_VALID_TEXTLEN; break; + case SC_VALIDDLG_ALLOW_CUSTOM: eValMode = SC_VALID_CUSTOM; break; + default: OSL_FAIL( "lclGetValModeFromPos - invalid list box position" ); + } + return eValMode; +} + +/** Converts the passed ScConditionMode to the position in the list box. */ +sal_uInt16 lclGetPosFromCondMode( ScConditionMode eCondMode ) +{ + sal_uInt16 nLbPos = SC_VALIDDLG_DATA_EQUAL; + switch( eCondMode ) + { + case ScConditionMode::NONE: // may occur in old XML files after Excel import + case ScConditionMode::Equal: nLbPos = SC_VALIDDLG_DATA_EQUAL; break; + case ScConditionMode::Less: nLbPos = SC_VALIDDLG_DATA_LESS; break; + case ScConditionMode::Greater: nLbPos = SC_VALIDDLG_DATA_GREATER; break; + case ScConditionMode::EqLess: nLbPos = SC_VALIDDLG_DATA_EQLESS; break; + case ScConditionMode::EqGreater: nLbPos = SC_VALIDDLG_DATA_EQGREATER; break; + case ScConditionMode::NotEqual: nLbPos = SC_VALIDDLG_DATA_NOTEQUAL; break; + case ScConditionMode::Between: nLbPos = SC_VALIDDLG_DATA_VALIDRANGE; break; + case ScConditionMode::NotBetween: nLbPos = SC_VALIDDLG_DATA_INVALIDRANGE; break; + case ScConditionMode::Direct: nLbPos = SC_VALIDDLG_DATA_DIRECT; break; + default: OSL_FAIL( "lclGetPosFromCondMode - unknown condition mode" ); + } + return nLbPos; +} + +/** Converts the passed list box position to an ScConditionMode. */ +ScConditionMode lclGetCondModeFromPos( sal_uInt16 nLbPos ) +{ + ScConditionMode eCondMode = ScConditionMode::Equal; + switch( nLbPos ) + { + case SC_VALIDDLG_DATA_EQUAL: eCondMode = ScConditionMode::Equal; break; + case SC_VALIDDLG_DATA_LESS: eCondMode = ScConditionMode::Less; break; + case SC_VALIDDLG_DATA_GREATER: eCondMode = ScConditionMode::Greater; break; + case SC_VALIDDLG_DATA_EQLESS: eCondMode = ScConditionMode::EqLess; break; + case SC_VALIDDLG_DATA_EQGREATER: eCondMode = ScConditionMode::EqGreater; break; + case SC_VALIDDLG_DATA_NOTEQUAL: eCondMode = ScConditionMode::NotEqual; break; + case SC_VALIDDLG_DATA_VALIDRANGE: eCondMode = ScConditionMode::Between; break; + case SC_VALIDDLG_DATA_INVALIDRANGE: eCondMode = ScConditionMode::NotBetween; break; + case SC_VALIDDLG_DATA_DIRECT: eCondMode = ScConditionMode::Direct; break; + default: OSL_FAIL( "lclGetCondModeFromPos - invalid list box position" ); + } + return eCondMode; +} + +/** Converts line feed separated string to a formula with strings separated by semicolons. + @descr Keeps all empty strings. + Example: abc\ndef\n\nghi -> "abc";"def";"";"ghi". + @param rFmlaStr (out-param) The converted formula string. */ +void lclGetFormulaFromStringList( OUString& rFmlaStr, std::u16string_view rStringList, sal_Unicode cFmlaSep ) +{ + rFmlaStr.clear(); + if (!rStringList.empty()) + { + sal_Int32 nIdx {0}; + do + { + OUString aToken {o3tl::getToken(rStringList, 0, '\n', nIdx )}; + ScGlobal::AddQuotes( aToken, '"' ); + rFmlaStr = ScGlobal::addToken(rFmlaStr, aToken, cFmlaSep); + } + while (nIdx>0); + } + if( rFmlaStr.isEmpty() ) + rFmlaStr = "\"\""; +} + +/** Converts formula with strings separated by semicolons to line feed separated string. + @descr Keeps all empty strings. Ignores all empty tokens (multiple semicolons). + Example: "abc";;;"def";"";"ghi" -> abc\ndef\n\nghi. + @param rStringList (out-param) The converted line feed separated string list. + @return true = Conversion successful. */ +bool lclGetStringListFromFormula( OUString& rStringList, const OUString& rFmlaStr, sal_Unicode cFmlaSep ) +{ + static constexpr OUStringLiteral aQuotes( u"\"\"" ); + + rStringList.clear(); + bool bIsStringList = !rFmlaStr.isEmpty(); + bool bTokenAdded = false; + + for ( sal_Int32 nStringIx = 0; bIsStringList && nStringIx>=0; ) + { + OUString aToken( ScStringUtil::GetQuotedToken(rFmlaStr, 0, aQuotes, cFmlaSep, nStringIx ) ); + aToken = comphelper::string::strip(aToken, ' '); + if( !aToken.isEmpty() ) // ignore empty tokens, i.e. "a";;"b" + { + bIsStringList = ScGlobal::IsQuoted( aToken, '"' ); + if( bIsStringList ) + { + ScGlobal::EraseQuotes( aToken, '"' ); + rStringList = ScGlobal::addToken(rStringList, aToken, '\n', 1, bTokenAdded); + bTokenAdded = true; + } + } + } + + return bIsStringList; +} + +} // namespace + +ScTPValidationValue::ScTPValidationValue(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet) + : SfxTabPage(pPage, pController, "modules/scalc/ui/validationcriteriapage.ui", + "ValidationCriteriaPage", &rArgSet) + , maStrMin(ScResId(SCSTR_VALID_MINIMUM)) + , maStrMax(ScResId(SCSTR_VALID_MAXIMUM)) + , maStrValue(ScResId(SCSTR_VALID_VALUE)) + , maStrFormula(ScResId(SCSTR_VALID_FORMULA)) + , maStrRange(ScResId(SCSTR_VALID_RANGE)) + , maStrList(ScResId(SCSTR_VALID_LIST)) + , m_pRefEdit(nullptr) + , m_xLbAllow(m_xBuilder->weld_combo_box("allow")) + , m_xCbAllow(m_xBuilder->weld_check_button("allowempty")) + , m_xCbShow(m_xBuilder->weld_check_button("showlist")) + , m_xCbSort(m_xBuilder->weld_check_button("sortascend")) + , m_xFtValue(m_xBuilder->weld_label("valueft")) + , m_xLbValue(m_xBuilder->weld_combo_box("data")) + , m_xFtMin(m_xBuilder->weld_label("minft")) + , m_xMinGrid(m_xBuilder->weld_widget("mingrid")) + , m_xEdMin(new formula::RefEdit(m_xBuilder->weld_entry("min"))) + , m_xEdList(m_xBuilder->weld_text_view("minlist")) + , m_xFtMax(m_xBuilder->weld_label("maxft")) + , m_xEdMax(new formula::RefEdit(m_xBuilder->weld_entry("max"))) + , m_xFtHint(m_xBuilder->weld_label("hintft")) + , m_xBtnRef(new formula::RefButton(m_xBuilder->weld_button("validref"))) + , m_xRefGrid(m_xBuilder->weld_container("refgrid")) + , m_pRefEditParent(m_xRefGrid.get()) + , m_pBtnRefParent(m_xRefGrid.get()) +{ + m_xEdMin->SetReferences(nullptr, m_xFtMin.get()); + + Size aSize(m_xEdList->get_approximate_digit_width() * 40, + m_xEdList->get_height_rows(10)); + m_xEdList->set_size_request(aSize.Width(), aSize.Height()); + m_xEdMax->SetReferences(nullptr, m_xFtMax.get()); + + m_xBtnRef->SetClickHdl(LINK(this, ScTPValidationValue, ClickHdl)); + + //lock in the max size initial config + aSize = m_xContainer->get_preferred_size(); + m_xContainer->set_size_request(aSize.Width(), aSize.Height()); + + Init(); + + // list separator in formulas + OUString aListSep = ::ScCompiler::GetNativeSymbol( ocSep ); + OSL_ENSURE( aListSep.getLength() == 1, "ScTPValidationValue::ScTPValidationValue - list separator error" ); + mcFmlaSep = aListSep.getLength() ? aListSep[0] : ';'; + m_xBtnRef->GetWidget()->hide(); // cell range picker +} + +ScTPValidationValue::~ScTPValidationValue() +{ + m_xEdMin.reset(); + m_xEdMax.reset(); + m_xBtnRef.reset(); +} + +void ScTPValidationValue::Init() +{ + m_xLbAllow->connect_changed( LINK( this, ScTPValidationValue, SelectHdl ) ); + m_xLbValue->connect_changed( LINK( this, ScTPValidationValue, SelectHdl ) ); + m_xCbShow->connect_toggled( LINK( this, ScTPValidationValue, CheckHdl ) ); + + // cell range picker + m_xEdMin->SetGetFocusHdl( LINK( this, ScTPValidationValue, EditSetFocusHdl ) ); + m_xEdMin->SetLoseFocusHdl( LINK( this, ScTPValidationValue, KillEditFocusHdl ) ); + m_xEdMax->SetGetFocusHdl( LINK( this, ScTPValidationValue, EditSetFocusHdl ) ); + m_xEdMax->SetLoseFocusHdl( LINK( this, ScTPValidationValue, KillEditFocusHdl ) ); + m_xBtnRef->SetLoseFocusHdl( LINK( this, ScTPValidationValue, KillButtonFocusHdl ) ); + + m_xLbAllow->set_active( SC_VALIDDLG_ALLOW_ANY ); + m_xLbValue->set_active( SC_VALIDDLG_DATA_EQUAL ); + + SelectHdl( *m_xLbAllow ); + CheckHdl( *m_xCbShow ); +} + +std::unique_ptr<SfxTabPage> ScTPValidationValue::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rArgSet) +{ + return std::make_unique<ScTPValidationValue>(pPage, pController, *rArgSet); +} + +void ScTPValidationValue::Reset( const SfxItemSet* rArgSet ) +{ + sal_uInt16 nLbPos = SC_VALIDDLG_ALLOW_ANY; + if( const SfxUInt16Item* pItem = rArgSet->GetItemIfSet( FID_VALID_MODE ) ) + nLbPos = lclGetPosFromValMode( static_cast< ScValidationMode >( pItem->GetValue() ) ); + m_xLbAllow->set_active( nLbPos ); + + nLbPos = SC_VALIDDLG_DATA_EQUAL; + if( const SfxUInt16Item* pItem = rArgSet->GetItemIfSet( FID_VALID_CONDMODE ) ) + nLbPos = lclGetPosFromCondMode( static_cast< ScConditionMode >( pItem->GetValue() ) ); + m_xLbValue->set_active( nLbPos ); + + // *** check boxes *** + bool bCheck = true; + if( const SfxBoolItem* pItem = rArgSet->GetItemIfSet( FID_VALID_BLANK ) ) + bCheck = pItem->GetValue(); + m_xCbAllow->set_active( bCheck ); + + sal_Int32 nListType = ValidListType::UNSORTED; + if( const SfxInt16Item* pItem = rArgSet->GetItemIfSet( FID_VALID_LISTTYPE ) ) + nListType = pItem->GetValue(); + m_xCbShow->set_active( nListType != ValidListType::INVISIBLE ); + m_xCbSort->set_active( nListType == ValidListType::SORTEDASCENDING ); + + // *** formulas *** + OUString aFmlaStr; + if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_VALUE1 ) ) + aFmlaStr = pItem->GetValue(); + SetFirstFormula( aFmlaStr ); + + aFmlaStr.clear(); + if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_VALUE2 ) ) + aFmlaStr = pItem->GetValue(); + SetSecondFormula( aFmlaStr ); + + SelectHdl( *m_xLbAllow ); + CheckHdl( *m_xCbShow ); +} + +bool ScTPValidationValue::FillItemSet( SfxItemSet* rArgSet ) +{ + sal_Int16 nListType = m_xCbShow->get_active() ? + (m_xCbSort->get_active() ? ValidListType::SORTEDASCENDING : ValidListType::UNSORTED) : + ValidListType::INVISIBLE; + + const sal_Int32 nLbPos = m_xLbAllow->get_active(); + bool bCustom = (nLbPos == SC_VALIDDLG_ALLOW_CUSTOM); + ScConditionMode eCondMode = bCustom ? + ScConditionMode::Direct : lclGetCondModeFromPos( m_xLbValue->get_active() ); + + rArgSet->Put( SfxUInt16Item( FID_VALID_MODE, sal::static_int_cast<sal_uInt16>( + lclGetValModeFromPos( nLbPos ) ) ) ); + rArgSet->Put( SfxUInt16Item( FID_VALID_CONDMODE, sal::static_int_cast<sal_uInt16>( eCondMode ) ) ); + rArgSet->Put( SfxStringItem( FID_VALID_VALUE1, GetFirstFormula() ) ); + rArgSet->Put( SfxStringItem( FID_VALID_VALUE2, GetSecondFormula() ) ); + rArgSet->Put( SfxBoolItem( FID_VALID_BLANK, m_xCbAllow->get_active() ) ); + rArgSet->Put( SfxInt16Item( FID_VALID_LISTTYPE, nListType ) ); + return true; +} + +OUString ScTPValidationValue::GetFirstFormula() const +{ + OUString aFmlaStr; + if( m_xLbAllow->get_active() == SC_VALIDDLG_ALLOW_LIST ) + lclGetFormulaFromStringList( aFmlaStr, m_xEdList->get_text(), mcFmlaSep ); + else + aFmlaStr = m_xEdMin->GetText(); + return aFmlaStr; +} + +OUString ScTPValidationValue::GetSecondFormula() const +{ + return m_xEdMax->GetText(); +} + +void ScTPValidationValue::SetFirstFormula( const OUString& rFmlaStr ) +{ + // try if formula is a string list, validation mode must already be set + OUString aStringList; + if( (m_xLbAllow->get_active() == SC_VALIDDLG_ALLOW_RANGE) && + lclGetStringListFromFormula( aStringList, rFmlaStr, mcFmlaSep ) ) + { + m_xEdList->set_text( aStringList ); + m_xEdMin->SetText( OUString() ); + // change validation mode to string list + m_xLbAllow->set_active( SC_VALIDDLG_ALLOW_LIST ); + } + else + { + m_xEdMin->SetText( rFmlaStr ); + m_xEdList->set_text( OUString() ); + } +} + +void ScTPValidationValue::SetSecondFormula( const OUString& rFmlaStr ) +{ + m_xEdMax->SetText( rFmlaStr ); +} + +ScValidationDlg * ScTPValidationValue::GetValidationDlg() +{ + return dynamic_cast<ScValidationDlg*>(GetDialogController()); +} + +void ScTPValidationValue::SetupRefDlg() +{ + ScValidationDlg *pValidationDlg = GetValidationDlg(); + if( !pValidationDlg ) + return; + + if( !pValidationDlg->SetupRefDlg() ) + return; + + pValidationDlg->SetHandler( this ); + pValidationDlg->SetSetRefHdl( static_cast<ScRefHandlerHelper::PFUNCSETREFHDLTYPE>( &ScTPValidationValue::SetReferenceHdl ) ); + pValidationDlg->SetSetActHdl( static_cast<ScRefHandlerHelper::PCOMMONHDLTYPE>( &ScTPValidationValue::SetActiveHdl ) ); + pValidationDlg->SetRefInputStartPreHdl( static_cast<ScRefHandlerHelper::PINPUTSTARTDLTYPE>( &ScTPValidationValue::RefInputStartPreHdl ) ); + pValidationDlg->SetRefInputDonePostHdl( static_cast<ScRefHandlerHelper::PCOMMONHDLTYPE>( &ScTPValidationValue::RefInputDonePostHdl ) ); + + weld::Label* pLabel = nullptr; + + if (m_xEdMax->GetWidget()->get_visible()) + { + m_pRefEdit = m_xEdMax.get(); + pLabel = m_xFtMax.get(); + } + else if (m_xEdMin->GetWidget()->get_visible()) + { + m_pRefEdit = m_xEdMin.get(); + pLabel = m_xFtMin.get(); + } + + if (m_pRefEdit && !m_pRefEdit->GetWidget()->has_focus()) + m_pRefEdit->GrabFocus(); + + if( m_pRefEdit ) + m_pRefEdit->SetReferences( pValidationDlg, pLabel ); + + m_xBtnRef->SetReferences( pValidationDlg, m_pRefEdit ); +} + +void ScTPValidationValue::RemoveRefDlg(bool bRestoreModal) +{ + ScValidationDlg *pValidationDlg = GetValidationDlg(); + if( !pValidationDlg ) + return; + + if( !pValidationDlg->RemoveRefDlg(bRestoreModal) ) + return; + + pValidationDlg->SetHandler( nullptr ); + pValidationDlg->SetSetRefHdl( nullptr ); + pValidationDlg->SetSetActHdl( nullptr ); + pValidationDlg->SetRefInputStartPreHdl( nullptr ); + pValidationDlg->SetRefInputDonePostHdl( nullptr ); + + if( m_pRefEdit ) + m_pRefEdit->SetReferences( nullptr, nullptr ); + m_pRefEdit = nullptr; + + m_xBtnRef->SetReferences( nullptr, nullptr ); +} + +IMPL_LINK_NOARG(ScTPValidationValue, EditSetFocusHdl, formula::RefEdit&, void) +{ + const sal_Int32 nPos = m_xLbAllow->get_active(); + + if ( nPos == SC_VALIDDLG_ALLOW_RANGE ) + { + SetupRefDlg(); + } +} + +IMPL_LINK( ScTPValidationValue, KillEditFocusHdl, formula::RefEdit&, rWnd, void ) +{ + if (&rWnd != m_pRefEdit) + return; + if( ScValidationDlg *pValidationDlg = GetValidationDlg() ) + { + if (pValidationDlg->IsChildFocus() && !pValidationDlg->IsRefInputting()) + { + if( ( !m_pRefEdit || !m_pRefEdit->GetWidget()->has_focus()) && !m_xBtnRef->GetWidget()->has_focus() ) + { + RemoveRefDlg(true); + } + } + } +} + +IMPL_LINK( ScTPValidationValue, KillButtonFocusHdl, formula::RefButton&, rWnd, void ) +{ + if( &rWnd != m_xBtnRef.get()) + return; + if( ScValidationDlg *pValidationDlg = GetValidationDlg() ) + if (pValidationDlg->IsChildFocus() && !pValidationDlg->IsRefInputting()) + if( ( !m_pRefEdit || !m_pRefEdit->GetWidget()->has_focus()) && !m_xBtnRef->GetWidget()->has_focus() ) + { + RemoveRefDlg(true); + } +} + +IMPL_LINK_NOARG(ScTPValidationValue, SelectHdl, weld::ComboBox&, void) +{ + const sal_Int32 nLbPos = m_xLbAllow->get_active(); + bool bEnable = (nLbPos != SC_VALIDDLG_ALLOW_ANY); + bool bRange = (nLbPos == SC_VALIDDLG_ALLOW_RANGE); + bool bList = (nLbPos == SC_VALIDDLG_ALLOW_LIST); + bool bCustom = (nLbPos == SC_VALIDDLG_ALLOW_CUSTOM); + + m_xCbAllow->set_sensitive( bEnable ); // Empty cell + m_xFtValue->set_sensitive( bEnable ); + m_xLbValue->set_sensitive( bEnable ); + m_xFtMin->set_sensitive( bEnable ); + m_xEdMin->GetWidget()->set_sensitive( bEnable ); + m_xEdList->set_sensitive( bEnable ); + m_xFtMax->set_sensitive( bEnable ); + m_xEdMax->GetWidget()->set_sensitive( bEnable ); + + bool bShowMax = false; + + if( bRange ) + m_xFtMin->set_label( maStrRange ); + else if( bList ) + m_xFtMin->set_label( maStrList ); + else if( bCustom ) + m_xFtMin->set_label( maStrFormula ); + else + { + switch( m_xLbValue->get_active() ) + { + case SC_VALIDDLG_DATA_EQUAL: + case SC_VALIDDLG_DATA_NOTEQUAL: m_xFtMin->set_label( maStrValue ); break; + + case SC_VALIDDLG_DATA_LESS: + case SC_VALIDDLG_DATA_EQLESS: m_xFtMin->set_label( maStrMax ); break; + + case SC_VALIDDLG_DATA_VALIDRANGE: + case SC_VALIDDLG_DATA_INVALIDRANGE: bShowMax = true; + [[fallthrough]]; + case SC_VALIDDLG_DATA_GREATER: + case SC_VALIDDLG_DATA_EQGREATER: m_xFtMin->set_label( maStrMin ); break; + + default: + OSL_FAIL( "ScTPValidationValue::SelectHdl - unknown condition mode" ); + } + } + + m_xCbShow->set_visible( bRange || bList ); + m_xCbSort->set_visible( bRange || bList ); + m_xFtValue->set_visible( !bRange && !bList && !bCustom); + m_xLbValue->set_visible( !bRange && !bList && !bCustom ); + m_xEdMin->GetWidget()->set_visible( !bList ); + m_xEdList->set_visible( bList ); + m_xMinGrid->set_vexpand( bList ); + m_xFtMax->set_visible( bShowMax ); + m_xEdMax->GetWidget()->set_visible( bShowMax ); + m_xFtHint->set_visible( bRange ); + m_xBtnRef->GetWidget()->set_visible( bRange ); // cell range picker +} + +IMPL_LINK_NOARG(ScTPValidationValue, CheckHdl, weld::Toggleable&, void) +{ + m_xCbSort->set_sensitive( m_xCbShow->get_active() ); +} + +// Input Help Page + +ScTPValidationHelp::ScTPValidationHelp(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet) + : SfxTabPage(pPage, pController, "modules/scalc/ui/validationhelptabpage.ui", "ValidationHelpTabPage", &rArgSet) + , m_xTsbHelp(m_xBuilder->weld_check_button("tsbhelp")) + , m_xEdtTitle(m_xBuilder->weld_entry("title")) + , m_xEdInputHelp(m_xBuilder->weld_text_view("inputhelp_text")) +{ + m_xEdInputHelp->set_size_request(m_xEdInputHelp->get_approximate_digit_width() * 40, m_xEdInputHelp->get_height_rows(13)); +} + +ScTPValidationHelp::~ScTPValidationHelp() +{ +} + +std::unique_ptr<SfxTabPage> ScTPValidationHelp::Create(weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet* rArgSet) +{ + return std::make_unique<ScTPValidationHelp>(pPage, pController, *rArgSet); +} + +void ScTPValidationHelp::Reset( const SfxItemSet* rArgSet ) +{ + if ( const SfxBoolItem* pItem = rArgSet->GetItemIfSet( FID_VALID_SHOWHELP ) ) + m_xTsbHelp->set_state( pItem->GetValue() ? TRISTATE_TRUE : TRISTATE_FALSE ); + else + m_xTsbHelp->set_state( TRISTATE_FALSE ); + + if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_HELPTITLE ) ) + m_xEdtTitle->set_text( pItem->GetValue() ); + else + m_xEdtTitle->set_text( OUString() ); + + if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_HELPTEXT ) ) + m_xEdInputHelp->set_text( pItem->GetValue() ); + else + m_xEdInputHelp->set_text( OUString() ); +} + +bool ScTPValidationHelp::FillItemSet( SfxItemSet* rArgSet ) +{ + rArgSet->Put( SfxBoolItem( FID_VALID_SHOWHELP, m_xTsbHelp->get_state() == TRISTATE_TRUE ) ); + rArgSet->Put( SfxStringItem( FID_VALID_HELPTITLE, m_xEdtTitle->get_text() ) ); + rArgSet->Put( SfxStringItem( FID_VALID_HELPTEXT, m_xEdInputHelp->get_text() ) ); + + return true; +} + +// Error Alert Page + +ScTPValidationError::ScTPValidationError(weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet& rArgSet) + + : SfxTabPage ( pPage, pController, + "modules/scalc/ui/erroralerttabpage.ui", "ErrorAlertTabPage", + &rArgSet ) + , m_xTsbShow(m_xBuilder->weld_check_button("tsbshow")) + , m_xLbAction(m_xBuilder->weld_combo_box("actionCB")) + , m_xBtnSearch(m_xBuilder->weld_button("browseBtn")) + , m_xEdtTitle(m_xBuilder->weld_entry("erroralert_title")) + , m_xFtError(m_xBuilder->weld_label("errormsg_label")) + , m_xEdError(m_xBuilder->weld_text_view("errorMsg")) +{ + m_xEdError->set_size_request(m_xEdError->get_approximate_digit_width() * 40, m_xEdError->get_height_rows(12)); + Init(); +} + +ScTPValidationError::~ScTPValidationError() +{ +} + +void ScTPValidationError::Init() +{ + m_xLbAction->connect_changed(LINK(this, ScTPValidationError, SelectActionHdl)); + m_xBtnSearch->connect_clicked(LINK( this, ScTPValidationError, ClickSearchHdl)); + + m_xLbAction->set_active(0); + + SelectActionHdl(*m_xLbAction); +} + +std::unique_ptr<SfxTabPage> ScTPValidationError::Create(weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet* rArgSet) +{ + return std::make_unique<ScTPValidationError>(pPage, pController, *rArgSet); +} + +void ScTPValidationError::Reset( const SfxItemSet* rArgSet ) +{ + if ( const SfxBoolItem* pItem = rArgSet->GetItemIfSet( FID_VALID_SHOWERR ) ) + m_xTsbShow->set_state( pItem->GetValue() ? TRISTATE_TRUE : TRISTATE_FALSE ); + else + m_xTsbShow->set_state( TRISTATE_TRUE ); // check by default + + if ( const SfxUInt16Item* pItem = rArgSet->GetItemIfSet( FID_VALID_ERRSTYLE ) ) + m_xLbAction->set_active( pItem->GetValue() ); + else + m_xLbAction->set_active( 0 ); + + if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_ERRTITLE ) ) + m_xEdtTitle->set_text( pItem->GetValue() ); + else + m_xEdtTitle->set_text( OUString() ); + + if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_ERRTEXT ) ) + m_xEdError->set_text( pItem->GetValue() ); + else + m_xEdError->set_text( OUString() ); + + SelectActionHdl(*m_xLbAction); +} + +bool ScTPValidationError::FillItemSet( SfxItemSet* rArgSet ) +{ + rArgSet->Put( SfxBoolItem( FID_VALID_SHOWERR, m_xTsbShow->get_state() == TRISTATE_TRUE ) ); + rArgSet->Put( SfxUInt16Item( FID_VALID_ERRSTYLE, m_xLbAction->get_active() ) ); + rArgSet->Put( SfxStringItem( FID_VALID_ERRTITLE, m_xEdtTitle->get_text() ) ); + rArgSet->Put( SfxStringItem( FID_VALID_ERRTEXT, m_xEdError->get_text() ) ); + + return true; +} + +IMPL_LINK_NOARG(ScTPValidationError, SelectActionHdl, weld::ComboBox&, void) +{ + ScValidErrorStyle eStyle = static_cast<ScValidErrorStyle>(m_xLbAction->get_active()); + bool bMacro = ( eStyle == SC_VALERR_MACRO ); + + m_xBtnSearch->set_sensitive( bMacro ); + m_xFtError->set_sensitive( !bMacro ); + m_xEdError->set_sensitive( !bMacro ); +} + +IMPL_LINK_NOARG(ScTPValidationError, ClickSearchHdl, weld::Button&, void) +{ + // Use static SfxApplication method to bring up selector dialog for + // choosing a script + OUString aScriptURL = SfxApplication::ChooseScript(GetFrameWeld()); + + if ( !aScriptURL.isEmpty() ) + { + m_xEdtTitle->set_text( aScriptURL ); + } +} + +bool ScValidationDlg::EnterRefStatus() +{ + ScTabViewShell *pTabViewShell = GetTabViewShell(); + + if( !pTabViewShell ) return false; + + sal_uInt16 nId = SLOTID; + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + if (pWnd && pWnd->GetController().get() != this) pWnd = nullptr; + + SC_MOD()->SetRefDialog( nId, pWnd == nullptr ); + + return true; +} + +bool ScValidationDlg::LeaveRefStatus() +{ + ScTabViewShell *pTabViewShell = GetTabViewShell(); + + if( !pTabViewShell ) return false; + + sal_uInt16 nId = SLOTID; + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + if (rViewFrm.GetChildWindow(nId)) + { + DoClose( nId ); + } + return true; +} + +bool ScValidationDlg::SetupRefDlg() +{ + if ( m_bOwnRefHdlr ) return false; + if( EnterRefMode() ) + { + SetModal( false ); + m_bOwnRefHdlr = true; + return EnterRefStatus(); + } + + return false; +} + +bool ScValidationDlg::RemoveRefDlg( bool bRestoreModal /* = true */ ) +{ + bool bVisLock = false; + bool bFreeWindowLock = false; + + ScTabViewShell *pTabVwSh = GetTabViewShell(); + + if( !pTabVwSh ) return false; + + if ( SfxChildWindow* pWnd = pTabVwSh->GetViewFrame().GetChildWindow( SID_VALIDITY_REFERENCE ) ) + { + bVisLock = static_cast<ScValidityRefChildWin*>(pWnd)->LockVisible( true ); + bFreeWindowLock = static_cast<ScValidityRefChildWin*>(pWnd)->LockFreeWindow( true ); + } + + if ( !m_bOwnRefHdlr ) return false; + if( LeaveRefStatus() && LeaveRefMode() ) + { + m_bOwnRefHdlr = false; + + if( bRestoreModal ) + { + SetModal( true ); + } + } + + if ( SfxChildWindow* pWnd = pTabVwSh->GetViewFrame().GetChildWindow( SID_VALIDITY_REFERENCE ) ) + { + static_cast<ScValidityRefChildWin*>(pWnd)->LockVisible( bVisLock ); + static_cast<ScValidityRefChildWin*>(pWnd)->LockFreeWindow( bFreeWindowLock ); + } + + return true; +} + +IMPL_LINK_NOARG(ScTPValidationValue, ClickHdl, formula::RefButton&, void) +{ + SetupRefDlg(); +} + +bool ScValidationDlg::IsChildFocus() const +{ + return m_xDialog->has_toplevel_focus(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |