tdf#85976 [RFE] Add a "Remove Duplicate Records" command

Add a "Remove Duplicate Records" entry under Calc > menu Data
to remove duplicate records from a rectangular selection
of cells in Calc.

Change-Id: Ic8340d7f1e19461ef3666fd2ef65294b73577d5c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160685
Reviewed-by: Heiko Tietze <heiko.tietze@documentfoundation.org>
Tested-by: Jenkins
This commit is contained in:
Sahil Gautam 2024-09-24 23:57:22 +05:30 committed by Heiko Tietze
parent b8368ad96e
commit 29fd68bb68
16 changed files with 1062 additions and 1 deletions

View file

@ -684,7 +684,8 @@ class SvxZoomItem;
#define FN_PARAM_4 (FN_PARAM+63)
#define FN_PARAM_5 (FN_PARAM+64)
#define FN_PARAM_6 (FN_PARAM+65)
#define FN_NOUPDATE TypedWhichId<SfxBoolItem>(FN_PARAM+66)
#define FN_PARAM_7 (FN_PARAM+66)
#define FN_NOUPDATE TypedWhichId<SfxBoolItem>(FN_PARAM+67)
#define FN_FAX (SID_SW_START + 28) /* Fax */
#define SID_KEYFUNC_START (SID_SC_START + 521)

View file

@ -1378,6 +1378,14 @@
<value>1</value>
</prop>
</node>
<node oor:name=".uno:HandleDuplicateRecords" oor:op="replace">
<prop oor:name="Label" oor:type="xs:string">
<value xml:lang="en-US">Duplicate Records...</value>
</prop>
<prop oor:name="Properties" oor:type="xs:int">
<value>1</value>
</prop>
</node>
<node oor:name=".uno:ViewColumnRowHighlighting" oor:op="replace">
<prop oor:name="Label" oor:type="xs:string">
<value xml:lang="en-US">Column/Row Highlighting</value>

View file

@ -1791,6 +1791,36 @@
<info>
<desc>Contains miscellaneous settings.</desc>
</info>
<group oor:name="HandleDuplicateRecords">
<info>
<desc>Defines the settings for 'handle duplicate records' dialog.</desc>
<label>Handle duplicate records settings</label>
</info>
<prop oor:name="DataIncludesHeaders" oor:type="xs:boolean" oor:nillable="false">
<!-- UIHints: Data - Duplicate Records... -->
<info>
<desc>Indicates whether selected data includes headers in the 'handle duplicate records' dialog.</desc>
<label>Data includes row/column headers</label>
</info>
<value>false</value>
</prop>
<prop oor:name="RemoveDuplicateRows" oor:type="xs:boolean" oor:nillable="false">
<!-- UIHints: Data - Duplicate Records... -->
<info>
<desc>Indicates whether to remove rows, or columns in the 'handle duplicate records' dialog.</desc>
<label>Data includes row/column headers</label>
</info>
<value>false</value>
</prop>
<prop oor:name="RemoveRecords" oor:type="xs:boolean" oor:nillable="false">
<!-- UIHints: Data - Duplicate Records... -->
<info>
<desc>Indicates whether to select or remove records in the 'handle duplicate records' dialog.</desc>
<label>Select or remove</label>
</info>
<value>true</value>
</prop>
</group>
<group oor:name="DefaultObjectSize">
<info>
<desc>Defines the default size of newly created objects using CTRL-Return or CTRL-Click at an object creating Button.</desc>

View file

@ -499,6 +499,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\
sc/source/ui/miscdlgs/onlyactivesheetsaveddlg \
sc/source/ui/miscdlgs/optsolver \
sc/source/ui/miscdlgs/protectiondlg \
sc/source/ui/miscdlgs/duplicaterecordsdlg \
sc/source/ui/miscdlgs/redcom \
sc/source/ui/miscdlgs/retypepassdlg \
sc/source/ui/miscdlgs/sharedocdlg \

View file

@ -95,6 +95,7 @@ $(eval $(call gb_UIConfig_add_uifiles,modules/scalc,\
sc/uiconfig/scalc/ui/autoformattable \
sc/uiconfig/scalc/ui/autosum \
sc/uiconfig/scalc/ui/cellprotectionpage \
sc/uiconfig/scalc/ui/duplicaterecordsdlg \
sc/uiconfig/scalc/ui/changesourcedialog \
sc/uiconfig/scalc/ui/chardialog \
sc/uiconfig/scalc/ui/checkwarningdialog \

View file

@ -277,6 +277,7 @@ class SvxZoomSliderItem;
#define FID_TOGGLEINPUTLINE TypedWhichId<SfxBoolItem>(VIEW_MENU_START + 1)
#define FID_TOGGLEHEADERS (VIEW_MENU_START + 2)
#define FID_HANDLEDUPLICATERECORDS (VIEW_MENU_START + 3)
#define FID_SCALE TypedWhichId<SvxZoomItem>(VIEW_MENU_START + 4)
#define FID_TOGGLESYNTAX (VIEW_MENU_START + 5)
#define FID_TOGGLECOLROWHIGHLIGHTING (VIEW_MENU_START + 6)

View file

@ -437,6 +437,10 @@
#define STR_CONDITION_THISYEAR NC_("STR_CONDITION_THISYEAR", "date is in this year")
#define STR_CONDITION_LASTYEAR NC_("STR_CONDITION_LASTYEAR", "date is in last year")
#define STR_CONDITION_NEXTYEAR NC_("STR_CONDITION_NEXTYEAR", "date is in next year")
#define STR_DUPLICATERECORDSDLG_WARNING NC_("STR_DUPLICATERECORDS_WARNING", "Warning!")
#define STR_DUPLICATERECORDSDLG_NODATAFOUND NC_("STR_DUPLICATERECORDS_NODATAFOUND", "No data found to operate on.")
#define STR_DUPLICATERECORDS_DATACONATINSROWHEADERS NC_("STR_DUPLICATERECORDS_DATACONATINSROWHEADERS", "Data contains row headers")
#define STR_DUPLICATERECORDS_DATACONATINSCOLUMNHEADERS NC_("STR_DUPLICATERECORDS_DATACONATINSCOLUMNHEADERS", "Data contains column headers")
#define STR_CONTENT_WITH_UNKNOWN_ENCRYPTION NC_("STR_CONTENT_WITH_UNKNOWN_ENCRYPTION", "Document contains DRM content that is encrypted with an unknown encryption method. Only the un-encrypted content will be shown.")

View file

@ -6054,6 +6054,32 @@ SfxBoolItem ViewValueHighlighting FID_TOGGLESYNTAX
GroupId = SfxGroupId::View;
]
SfxBoolItem HandleDuplicateRecords FID_HANDLEDUPLICATERECORDS
(
SfxBoolItem Remove FID_HANDLEDUPLICATERECORDS,
SfxBoolItem IncludesHeaders FN_PARAM_1,
SfxBoolItem DuplicateRows FN_PARAM_2,
SfxInt32Item StartColumn FN_PARAM_3,
SfxInt32Item StartRow FN_PARAM_4,
SfxInt32Item EndColumn FN_PARAM_5,
SfxInt32Item EndRow FN_PARAM_6,
SfxInt32Item TabNo FN_PARAM_7
)
[
AutoUpdate = FALSE,
FastCall = FALSE,
ReadOnlyDoc = TRUE,
Toggle = FALSE,
Container = FALSE,
RecordAbsolute = FALSE,
RecordPerSet;
AccelConfig = TRUE,
MenuConfig = TRUE,
ToolBoxConfig = TRUE,
GroupId = SfxGroupId::View;
]
SfxBoolItem ViewColumnRowHighlighting FID_TOGGLECOLROWHIGHLIGHTING
[

View file

@ -174,6 +174,7 @@ interface TableEditView
FID_SCALESTATUS [ ExecMethod = Execute; StateMethod = GetState; ]
FID_TOGGLESYNTAX [ ExecMethod = Execute; StateMethod = GetState; ]
FID_HANDLEDUPLICATERECORDS [ ExecMethod = Execute; StateMethod = GetState; ]
FID_TOGGLECOLROWHIGHLIGHTING [ ExecMethod = Execute; StateMethod = GetState; ]
FID_TOGGLEHEADERS [ ExecMethod = Execute; StateMethod = GetState; ]
FID_TOGGLEFORMULA [ ExecMethod = Execute; StateMethod = GetState; ]

View file

@ -0,0 +1,75 @@
/* -*- 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 .
*/
#pragma once
#include "viewdata.hxx"
#include <vcl/weld.hxx>
using namespace css;
struct DuplicatesResponse
{
std::vector<int> vEntries;
bool bRemove; // false ==> Select
bool bIncludesHeaders;
bool bDuplicatRows; // false ==> DuplicateColumns
};
class ScDuplicateRecordsDlg : public weld::GenericDialogController
{
public:
ScDuplicateRecordsDlg() = delete;
explicit ScDuplicateRecordsDlg(weld::Window* pParent,
css::uno::Sequence<uno::Sequence<uno::Any>>& rData,
ScViewData& rViewData, ScRange& aRange);
virtual ~ScDuplicateRecordsDlg() override;
DuplicatesResponse GetDialogData() { return maResponse; };
private:
void Init();
void Okay();
void SetDialogData(bool bToggle);
std::unique_ptr<weld::CheckButton> m_xIncludesHeaders;
std::unique_ptr<weld::RadioButton> m_xRadioRow;
std::unique_ptr<weld::RadioButton> m_xRadioColumn;
std::unique_ptr<weld::RadioButton> m_xRadioSelect;
std::unique_ptr<weld::RadioButton> m_xRadioRemove;
std::unique_ptr<weld::TreeView> m_xCheckList;
std::unique_ptr<weld::CheckButton> m_xAllChkBtn;
std::unique_ptr<weld::Button> m_xOkBtn;
std::unique_ptr<weld::Button> m_xHelpBtn;
uno::Sequence<uno::Sequence<uno::Any>>& mrCellData;
ScRange& mrRange;
ScViewData& mrViewData;
DuplicatesResponse maResponse;
void InsertEntry(const OUString& rTxt, bool bToggle);
DECL_LINK(OrientationHdl, weld::Toggleable&, void);
DECL_LINK(HeaderCkbHdl, weld::Toggleable&, void);
DECL_LINK(OkHdl, weld::Button&, void);
DECL_LINK(AllCheckBtnHdl, weld::Toggleable&, void);
DECL_LINK(RecordsChkHdl, const weld::TreeView::iter_col&, void);
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -31,6 +31,7 @@
#include <shellids.hxx>
#include <tabprotection.hxx>
#include <com/sun/star/ui/dialogs/DialogClosedEvent.hpp>
#include <com/sun/star/sheet/XSpreadsheet.hpp>
#include <dragdata.hxx>
#include <memory>
@ -267,6 +268,13 @@ public:
SC_DLLPUBLIC bool IsRefInputMode() const;
void ExecuteInputDirect();
void HandleDuplicateRecords(css::uno::Reference<css::sheet::XSpreadsheet> ActiveSheet,
const css::table::CellRangeAddress& aRange, bool bRemove,
bool bIncludesHeaders, bool bDuplicateRows,
const std::vector<int>& rSelectedEntries);
css::uno::Reference<css::sheet::XSpreadsheet> GetRangeWithSheet(css::table::CellRangeAddress& rRangeData, bool& bHasData, bool bHasUnoArguments);
void ExtendSingleSelection(css::table::CellRangeAddress& rRangeData);
const ScInputHandler* GetInputHandler() const { return mpInputHandler.get(); }
ScInputHandler* GetInputHandler() { return mpInputHandler.get(); }
SC_DLLPUBLIC const OUString* GetEditString() const;

View file

@ -0,0 +1,214 @@
/* -*- 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 <scresid.hxx>
#include <dbdata.hxx>
#include <cellvalue.hxx>
#include <duplicaterecordsdlg.hxx>
#include <string>
#include <strings.hrc>
#include <tabprotection.hxx>
#include <gridwin.hxx>
#include <vector>
#include <officecfg/Office/Calc.hxx>
ScDuplicateRecordsDlg::ScDuplicateRecordsDlg(weld::Window* pParent,
css::uno::Sequence<uno::Sequence<uno::Any>>& rData,
ScViewData& rViewData, ScRange& rRange)
: weld::GenericDialogController(pParent, "modules/scalc/ui/duplicaterecordsdlg.ui",
"DuplicateRecordsDialog")
, m_xIncludesHeaders(m_xBuilder->weld_check_button("includesheaders"))
, m_xRadioRow(m_xBuilder->weld_radio_button("row"))
, m_xRadioColumn(m_xBuilder->weld_radio_button("column"))
, m_xRadioSelect(m_xBuilder->weld_radio_button("select"))
, m_xRadioRemove(m_xBuilder->weld_radio_button("remove"))
, m_xCheckList(m_xBuilder->weld_tree_view("checklist"))
, m_xAllChkBtn(m_xBuilder->weld_check_button("allcheckbtn"))
, m_xOkBtn(m_xBuilder->weld_button("okaybtn"))
, m_xHelpBtn(m_xBuilder->weld_button("helpbutton"))
, mrCellData(rData)
, mrRange(rRange)
, mrViewData(rViewData)
{
m_xCheckList->enable_toggle_buttons(weld::ColumnToggleType::Check);
m_xCheckList->connect_toggled(LINK(this, ScDuplicateRecordsDlg, RecordsChkHdl));
Init();
}
ScDuplicateRecordsDlg::~ScDuplicateRecordsDlg()
{
auto pChange(comphelper::ConfigurationChanges::create());
officecfg::Office::Calc::Misc::HandleDuplicateRecords::RemoveDuplicateRows::set(
m_xRadioRow->get_active(), pChange);
pChange->commit();
officecfg::Office::Calc::Misc::HandleDuplicateRecords::DataIncludesHeaders::set(
m_xIncludesHeaders->get_active(), pChange);
pChange->commit();
officecfg::Office::Calc::Misc::HandleDuplicateRecords::RemoveRecords::set(
m_xRadioRemove->get_active(), pChange);
pChange->commit();
}
void ScDuplicateRecordsDlg::SetDialogData(bool bToggle)
{
m_xCheckList->freeze();
m_xCheckList->clear();
if (m_xRadioRow->get_active())
{
if (m_xIncludesHeaders->get_active())
{
// insert the first row's contents
ScRefCellValue aCell;
for (SCCOL i = mrRange.aStart.Col(); i <= mrRange.aEnd.Col(); ++i)
{
aCell.assign(mrViewData.GetDocument(),
ScAddress{ i, mrRange.aStart.Row(), mrRange.aStart.Tab() });
InsertEntry(aCell.getString(&mrViewData.GetDocument()), bToggle);
}
}
else
{
for (int i = mrRange.aStart.Col(); i <= mrRange.aEnd.Col(); ++i)
{
OUString aStr(ScAddress(i, 0, mrViewData.GetTabNo())
.Format(ScRefFlags::COL_VALID, &mrViewData.GetDocument()));
InsertEntry(aStr, bToggle);
}
}
}
else
{
// insert row names
if (m_xIncludesHeaders->get_active())
{
ScRefCellValue aCell;
for (SCROW i = mrRange.aStart.Row(); i <= mrRange.aEnd.Row(); ++i)
{
aCell.assign(mrViewData.GetDocument(),
ScAddress{ mrRange.aStart.Col(), i, mrRange.aStart.Tab() });
InsertEntry(aCell.getString(&mrViewData.GetDocument()), bToggle);
}
}
else
{
for (int i = mrRange.aStart.Row() + 1; i <= mrRange.aEnd.Row() + 1; ++i)
{
std::string aStr = std::to_string(i);
InsertEntry(rtl::OUString::fromUtf8(aStr), bToggle);
}
}
}
m_xCheckList->thaw();
}
void ScDuplicateRecordsDlg::InsertEntry(const OUString& rTxt, bool bToggle)
{
m_xCheckList->append();
const int nRow = m_xCheckList->n_children() - 1;
m_xCheckList->set_toggle(nRow, bToggle ? TRISTATE_TRUE : TRISTATE_FALSE);
m_xCheckList->set_text(nRow, rTxt, 0);
m_xCheckList->set_sensitive(m_xAllChkBtn->get_state() != TRISTATE_TRUE);
}
void ScDuplicateRecordsDlg::Init()
{
m_xIncludesHeaders->connect_toggled(LINK(this, ScDuplicateRecordsDlg, HeaderCkbHdl));
m_xRadioRow->connect_toggled(LINK(this, ScDuplicateRecordsDlg, OrientationHdl));
// m_xRadioColumn->connect_toggled(LINK(this, ScDuplicateRecordsDlg, OrientationHdl));
m_xOkBtn->connect_clicked(LINK(this, ScDuplicateRecordsDlg, OkHdl));
m_xAllChkBtn->connect_toggled(LINK(this, ScDuplicateRecordsDlg, AllCheckBtnHdl));
// defaults (find duplicate rows | data doesn't include headers)
m_xRadioRow->set_active(
officecfg::Office::Calc::Misc::HandleDuplicateRecords::RemoveDuplicateRows::get());
m_xRadioColumn->set_active(
!officecfg::Office::Calc::Misc::HandleDuplicateRecords::RemoveDuplicateRows::get());
m_xIncludesHeaders->set_active(
officecfg::Office::Calc::Misc::HandleDuplicateRecords::DataIncludesHeaders::get());
m_xRadioRemove->set_active(
officecfg::Office::Calc::Misc::HandleDuplicateRecords::RemoveRecords::get());
m_xRadioSelect->set_active(
!officecfg::Office::Calc::Misc::HandleDuplicateRecords::RemoveRecords::get());
const OUString aHeaderLabel = m_xRadioRow->get_active()
? ScResId(STR_DUPLICATERECORDS_DATACONATINSROWHEADERS)
: ScResId(STR_DUPLICATERECORDS_DATACONATINSCOLUMNHEADERS);
m_xIncludesHeaders->set_label(aHeaderLabel);
m_xAllChkBtn->set_state(TRISTATE_FALSE);
SetDialogData(true);
}
IMPL_LINK_NOARG(ScDuplicateRecordsDlg, OrientationHdl, weld::Toggleable&, void)
{
const OUString aHeaderLabel = m_xRadioRow->get_active()
? ScResId(STR_DUPLICATERECORDS_DATACONATINSROWHEADERS)
: ScResId(STR_DUPLICATERECORDS_DATACONATINSCOLUMNHEADERS);
m_xIncludesHeaders->set_label(aHeaderLabel);
SetDialogData(true);
}
IMPL_LINK_NOARG(ScDuplicateRecordsDlg, HeaderCkbHdl, weld::Toggleable&, void)
{
SetDialogData(true);
}
IMPL_LINK_NOARG(ScDuplicateRecordsDlg, RecordsChkHdl, const weld::TreeView::iter_col&, void)
{
int nRet = 0;
int nTotalCount = 0;
m_xCheckList->all_foreach([this, &nRet, &nTotalCount](weld::TreeIter& rEntry) {
++nTotalCount;
if (m_xCheckList->get_toggle(rEntry) == TRISTATE_TRUE)
++nRet;
return false;
});
if (nRet == nTotalCount)
m_xAllChkBtn->set_state(TRISTATE_TRUE);
else
m_xAllChkBtn->set_state(TRISTATE_FALSE);
}
IMPL_LINK_NOARG(ScDuplicateRecordsDlg, AllCheckBtnHdl, weld::Toggleable&, void)
{
SetDialogData(true);
}
IMPL_LINK_NOARG(ScDuplicateRecordsDlg, OkHdl, weld::Button&, void)
{
maResponse.bRemove = m_xRadioRemove->get_active();
maResponse.bDuplicatRows = m_xRadioRow->get_active();
maResponse.bIncludesHeaders = m_xIncludesHeaders->get_active();
int nCount = (maResponse.bDuplicatRows ? mrCellData[0].size() : mrCellData.size());
for (int i = 0; i < nCount; ++i)
{
if (m_xCheckList->get_toggle(i))
maResponse.vEntries.push_back(i);
}
m_xDialog->response(RET_OK);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -45,6 +45,7 @@
#include <reffact.hxx>
#include <tabprotection.hxx>
#include <protectiondlg.hxx>
#include <duplicaterecordsdlg.hxx>
#include <markdata.hxx>
#include <svl/ilstitem.hxx>
@ -54,6 +55,8 @@
#include <svx/svxdlg.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/string.hxx>
#include <com/sun/star/uno/Reference.h>
#include <com/sun/star/sheet/XCellRangeData.hpp>
#include <sfx2/lokhelper.hxx>
#include <scabstdlg.hxx>
#include <officecfg/Office/Calc.hxx>
@ -809,6 +812,115 @@ void ScTabViewShell::Execute( SfxRequest& rReq )
rReq.Done();
}
break;
case FID_HANDLEDUPLICATERECORDS:
{
using namespace com::sun::star;
table::CellRangeAddress aCellRange;
uno::Reference<sheet::XSpreadsheet> xActiveSheet;
DuplicatesResponse aResponse;
bool bHasData = true;
if (pReqArgs)
{
const SfxPoolItem* pItem;
if (pReqArgs->HasItem(FID_HANDLEDUPLICATERECORDS, &pItem))
aResponse.bRemove = static_cast<const SfxBoolItem*>(pItem)->GetValue();
if (pReqArgs->HasItem(FN_PARAM_1, &pItem))
aResponse.bIncludesHeaders = static_cast<const SfxBoolItem*>(pItem)->GetValue();
if (pReqArgs->HasItem(FN_PARAM_2, &pItem))
aResponse.bDuplicatRows = static_cast<const SfxBoolItem*>(pItem)->GetValue();
if (pReqArgs->HasItem(FN_PARAM_3, &pItem))
aCellRange.StartColumn = static_cast<const SfxInt32Item*>(pItem)->GetValue();
if (pReqArgs->HasItem(FN_PARAM_4, &pItem))
aCellRange.StartRow = static_cast<const SfxInt32Item*>(pItem)->GetValue();
if (pReqArgs->HasItem(FN_PARAM_5, &pItem))
aCellRange.EndColumn = static_cast<const SfxInt32Item*>(pItem)->GetValue();
if (pReqArgs->HasItem(FN_PARAM_6, &pItem))
aCellRange.EndRow = static_cast<const SfxInt32Item*>(pItem)->GetValue();
if (pReqArgs->HasItem(FN_PARAM_7, &pItem))
aCellRange.Sheet = static_cast<const SfxInt32Item*>(pItem)->GetValue();
// check for the tab range here
if (aCellRange.StartColumn < 0 || aCellRange.StartRow < 0
|| aCellRange.EndColumn < 0 || aCellRange.EndRow < 0
|| aCellRange.StartRow > aCellRange.EndRow
|| aCellRange.StartColumn > aCellRange.EndColumn || aCellRange.Sheet < 0
|| aCellRange.Sheet >= GetViewData().GetDocument().GetTableCount())
{
rReq.Done();
break;
}
xActiveSheet = GetViewData().GetViewShell()->GetRangeWithSheet(aCellRange,
bHasData, true);
if (!bHasData)
{
rReq.Done();
break;
}
int nLenEntries
= (aResponse.bDuplicatRows ? aCellRange.EndColumn - aCellRange.StartColumn
: aCellRange.EndRow - aCellRange.StartRow);
for (int i = 0; i <= nLenEntries; ++i)
aResponse.vEntries.push_back(i);
}
else
{
xActiveSheet = GetViewData().GetViewShell()->GetRangeWithSheet(aCellRange,
bHasData, false);
if (bHasData)
{
if (!GetViewData().GetMarkData().IsMarked())
GetViewData().GetViewShell()->ExtendSingleSelection(aCellRange);
uno::Reference<frame::XModel> xModel(GetViewData().GetDocShell()->GetModel());
uno::Reference<sheet::XSheetCellRange> xSheetRange(
xActiveSheet->getCellRangeByPosition(
aCellRange.StartColumn, aCellRange.StartRow, aCellRange.EndColumn,
aCellRange.EndRow),
uno::UNO_QUERY);
ScRange aRange(ScAddress(aCellRange.StartColumn, aCellRange.StartRow,
GetViewData().GetTabNo()),
ScAddress(aCellRange.EndColumn, aCellRange.EndRow,
GetViewData().GetTabNo()));
uno::Reference<sheet::XCellRangeData> xCellRangeData(xSheetRange,
uno::UNO_QUERY);
uno::Sequence<uno::Sequence<uno::Any>> aDataArray
= xCellRangeData->getDataArray();
ScDuplicateRecordsDlg aDlg(GetFrameWeld(), aDataArray, GetViewData(), aRange);
bHasData = aDlg.run();
if (bHasData)
aResponse = aDlg.GetDialogData();
else
{
rReq.Done();
break;
}
}
else
{
std::unique_ptr<weld::MessageDialog> aDialog(
Application::CreateMessageDialog(GetFrameWeld(),
VclMessageType::Warning,
VclButtonsType::Ok,
ScResId(STR_DUPLICATERECORDSDLG_NODATAFOUND)));
aDialog->set_title(ScResId(STR_DUPLICATERECORDSDLG_WARNING));
aDialog->run();
}
}
if (bHasData)
GetViewData().GetViewShell()->HandleDuplicateRecords(
xActiveSheet, aCellRange, aResponse.bRemove, aResponse.bIncludesHeaders,
aResponse.bDuplicatRows, aResponse.vEntries);
rReq.Done();
}
break;
case FID_TOGGLECOLROWHIGHLIGHTING:
{
bool bNewVal = !officecfg::Office::Calc::Content::Display::ColumnRowHighlighting::get();

View file

@ -80,6 +80,9 @@
#include <com/sun/star/document/XDocumentProperties.hpp>
#include <com/sun/star/configuration/theDefaultProvider.hpp>
#include <com/sun/star/sheet/XCellRangeMovement.hpp>
#include <com/sun/star/sheet/XCellRangeData.hpp>
#include <com/sun/star/sheet/XCellRangeAddressable.hpp>
#include <comphelper/processfactory.hxx>
#include <sfx2/lokhelper.hxx>
#include <comphelper/flagguard.hxx>
@ -1775,6 +1778,242 @@ ScViewOptiChangesListener::ScViewOptiChangesListener(ScTabViewShell& rViewShell)
m_xColorSchemeChangesNotifier->addChangesListener(this);
}
static void lcl_RemoveCells(uno::Reference<sheet::XSpreadsheet>& rSheet, sal_uInt16 nSheet,
sal_uInt32 nStartColumn, sal_uInt32 nStartRow, sal_uInt32 nEndColumn,
sal_uInt32 nEndRow, bool bRows)
{
table::CellRangeAddress aCellRange(nSheet, nStartColumn, nStartRow, nEndColumn, nEndRow);
uno::Reference<sheet::XCellRangeMovement> xCRM(rSheet, uno::UNO_QUERY);
if (xCRM.is())
{
if (bRows)
xCRM->removeRange(aCellRange, sheet::CellDeleteMode_UP);
else
xCRM->removeRange(aCellRange, sheet::CellDeleteMode_LEFT);
}
}
/* For rows (bool bRows), I am passing reference to already existing sequence, and comparing the required
* columns, whereas for columns, I am creating a sequence for each, with only the checked entries
* in the dialog.
*/
static bool lcl_CheckInArray(std::vector<uno::Sequence<uno::Any>>& nUniqueRecords,
const uno::Sequence<uno::Any>& nCurrentRecord,
const std::vector<int>& rSelectedEntries, bool bRows)
{
for (size_t m = 0; m < nUniqueRecords.size(); ++m)
{
bool bIsDuplicate = true;
for (size_t n = 0; n < rSelectedEntries.size(); ++n)
{
// when the first different element is found
int nColumn = (bRows ? rSelectedEntries[n] : n);
if (nUniqueRecords[m][nColumn] != (bRows ? nCurrentRecord[rSelectedEntries[n]] : nCurrentRecord[n]))
{
bIsDuplicate = false;
break;
}
}
if (bIsDuplicate)
return true;
}
return false;
}
uno::Reference<css::sheet::XSpreadsheet> ScTabViewShell::GetRangeWithSheet(css::table::CellRangeAddress& rRangeData, bool& bHasData, bool bHasUnoArguments)
{
// get spreadsheet document model & controller
uno::Reference<frame::XModel> xModel(GetViewData().GetDocShell()->GetModel());
uno::Reference<frame::XController> xController(xModel->getCurrentController());
// spreadsheet's extension of com.sun.star.frame.Controller service
uno::Reference<sheet::XSpreadsheetView> SpreadsheetDocument(xController, uno::UNO_QUERY);
uno::Reference<sheet::XSpreadsheet> ActiveSheet = SpreadsheetDocument->getActiveSheet();
if (!bHasUnoArguments)
{
// get the selection supplier, extract selection in XSheetCellRange
uno::Reference<view::XSelectionSupplier> xSelectionSupplier(SpreadsheetDocument, uno::UNO_QUERY);
uno::Any Selection = xSelectionSupplier->getSelection();
uno::Reference<sheet::XSheetCellRange> SelectedCellRange;
Selection >>= SelectedCellRange;
// Get the Selected Range Address.
uno::Reference<sheet::XCellRangeAddressable> xAddressable( SelectedCellRange, uno::UNO_QUERY);
if (xAddressable.is())
rRangeData = xAddressable->getRangeAddress();
else
{
bHasData = false;
return ActiveSheet;
}
}
SCCOL nStartColumn = rRangeData.StartColumn;
SCCOL nEndColumn = rRangeData.EndColumn;
SCROW nStartRow = rRangeData.StartRow;
SCROW nEndRow = rRangeData.EndRow;
// shrink to inersection of data and selection. If no intersection ==> return
bHasData = GetViewData().GetDocument().ShrinkToDataArea(rRangeData.Sheet, nStartColumn, nStartRow, nEndColumn, nEndRow);
rRangeData.StartColumn = nStartColumn;
rRangeData.StartRow = nStartRow;
rRangeData.EndColumn = nEndColumn;
rRangeData.EndRow = nEndRow;
return ActiveSheet;
}
void ScTabViewShell::ExtendSingleSelection(css::table::CellRangeAddress& rRangeData)
{
SCCOL aStartCol(rRangeData.StartColumn);
SCCOL aEndCol(rRangeData.EndColumn);
SCROW aStartRow(rRangeData.StartRow);
SCROW aEndRow(rRangeData.EndRow);
GetViewData().GetDocument().GetDataArea(rRangeData.Sheet, aStartCol, aStartRow, aEndCol,
aEndRow, true, false);
MarkRange(ScRange(ScAddress(aStartCol, aStartRow, rRangeData.Sheet),
ScAddress(aEndCol, aEndRow, rRangeData.Sheet)));
rRangeData.StartRow = aStartRow;
rRangeData.StartColumn = aStartCol;
rRangeData.EndRow = aEndRow;
rRangeData.EndColumn = aEndCol;
}
/* bool bRemove == false ==> highlight duplicate rows */
void ScTabViewShell::HandleDuplicateRecords(css::uno::Reference<css::sheet::XSpreadsheet> ActiveSheet,
const css::table::CellRangeAddress& aRange, bool bRemove,
bool bIncludesHeaders, bool bDuplicateRows,
const std::vector<int>& rSelectedEntries)
{
if (rSelectedEntries.size() == 0)
{
Unmark();
return;
}
uno::Reference<frame::XModel> xModel(GetViewData().GetDocShell()->GetModel());
uno::Reference<sheet::XSheetCellRange> xSheetRange(
ActiveSheet->getCellRangeByPosition(aRange.StartColumn, aRange.StartRow, aRange.EndColumn, aRange.EndRow),
uno::UNO_QUERY);
uno::Reference<sheet::XCellRangeData> xCellRangeData(xSheetRange, uno::UNO_QUERY);
uno::Sequence<uno::Sequence<uno::Any>> aDataArray = xCellRangeData->getDataArray();
uno::Reference< document::XUndoManagerSupplier > xUndoManager( xModel, uno::UNO_QUERY );
uno::Reference<document::XActionLockable> xLockable(xModel, uno::UNO_QUERY);
uno::Reference<sheet::XCalculatable> xCalculatable(xModel, uno::UNO_QUERY);
bool bAutoCalc = xCalculatable->isAutomaticCalculationEnabled();
comphelper::ScopeGuard aUndoContextGaurd(
[&xUndoManager, &xLockable, &xModel, &xCalculatable, &bAutoCalc, &bRemove] {
xUndoManager->getUndoManager()->leaveUndoContext();
if (bRemove)
xCalculatable->enableAutomaticCalculation(bAutoCalc);
xLockable->removeActionLock();
if (xModel->hasControllersLocked())
xModel->unlockControllers();
});
xModel->lockControllers();
xLockable->addActionLock();
if (bRemove)
xCalculatable->enableAutomaticCalculation(true);
xUndoManager->getUndoManager()->enterUndoContext("HandleDuplicateRecords");
bool nModifier = false; // modifier key pressed?
bool bNoDuplicatesForSelection = true;
if (bDuplicateRows)
{
std::vector<uno::Sequence<uno::Any>> aUnionArray;
sal_uInt32 nRow = bIncludesHeaders ? 1 : 0;
sal_uInt32 lRows = aDataArray.getLength();
sal_uInt32 nDeleteCount = 0;
while (nRow < lRows)
{
if (lcl_CheckInArray(aUnionArray, aDataArray[nRow], rSelectedEntries, true))
{
if (bRemove)
{
lcl_RemoveCells(ActiveSheet, aRange.Sheet, aRange.StartColumn,
aRange.StartRow + nRow - nDeleteCount, aRange.EndColumn,
aRange.StartRow + nRow - nDeleteCount, true);
++nDeleteCount;
}
else
{
for (int nCol = aRange.StartColumn; nCol <= aRange.EndColumn; ++nCol)
{
bNoDuplicatesForSelection = false;
DoneBlockMode( nModifier );
nModifier = true;
InitBlockMode( nCol, aRange.StartRow + nRow, aRange.Sheet, false, false);
}
}
}
else
{
aUnionArray.push_back(aDataArray[nRow]);
}
++nRow;
}
}
else
{
std::vector<uno::Sequence<uno::Any>> aUnionArray;
sal_uInt32 nDeleteCount = 0;
sal_uInt32 nColumn = bIncludesHeaders ? 1 : 0;
sal_uInt32 lColumns = aDataArray[0].getLength();
while (nColumn < lColumns)
{
uno::Sequence<uno::Any> aSeq;
aSeq.realloc(rSelectedEntries.size());
for (size_t i = 0; i < rSelectedEntries.size(); ++i)
aSeq.getArray()[i] = aDataArray[rSelectedEntries[i]][nColumn];
if (lcl_CheckInArray(aUnionArray, aSeq, rSelectedEntries, false))
{
if (bRemove)
{
lcl_RemoveCells(ActiveSheet, aRange.Sheet,
aRange.StartColumn + nColumn - nDeleteCount, aRange.StartRow,
aRange.StartColumn + nColumn - nDeleteCount, aRange.EndRow, false);
++nDeleteCount;
}
else
{
for (int nRow = aRange.StartRow; nRow <= aRange.EndRow; ++nRow)
{
bNoDuplicatesForSelection = false;
DoneBlockMode( nModifier );
nModifier = true;
InitBlockMode( aRange.StartColumn + nColumn, nRow, aRange.Sheet, false, false);
}
}
}
else
{
aUnionArray.push_back(aSeq);
}
++nColumn;
}
}
if (bNoDuplicatesForSelection && !bRemove)
Unmark();
}
ScTabViewShell::ScTabViewShell( SfxViewFrame& rViewFrame,
SfxViewShell* pOldSh ) :
SfxViewShell(rViewFrame, SfxViewShellFlags::HAS_PRINTOPTIONS),

View file

@ -604,6 +604,7 @@
<menu:menuitem menu:id=".uno:DataFilterHideAutoFilter"/>
</menu:menupopup>
</menu:menu>
<menu:menuitem menu:id=".uno:HandleDuplicateRecords"/>
<menu:menuseparator/>
<menu:menuitem menu:id=".uno:DefineDBName"/>
<menu:menuitem menu:id=".uno:SelectDB"/>

View file

@ -0,0 +1,339 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface domain="sc">
<requires lib="gtk+" version="3.20"/>
<object class="GtkTreeStore" id="liststore1">
<columns>
<!-- column-name check1 -->
<column type="gboolean"/>
<!-- column-name text -->
<column type="gchararray"/>
<!-- column-name id -->
<column type="gchararray"/>
<!-- column-name checkvis1 -->
<column type="gboolean"/>
<!-- column-name checktri1 -->
<column type="gboolean"/>
</columns>
</object>
<object class="GtkDialog" id="DuplicateRecordsDialog">
<property name="can-focus">False</property>
<property name="border-width">6</property>
<property name="title" translatable="yes" context="duplicaterecordsdialog|duplicaterecordsdialog">Handle Duplicate Records</property>
<property name="resizable">False</property>
<property name="type-hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="layout-style">end</property>
<child>
<object class="GtkButton" id="helpbutton">
<property name="label" translatable="yes" context="duplicaterecordsdialog|helpbutton">_Help</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="secondary">True</property>
</packing>
</child>
<child>
<object class="GtkButton" id="okaybtn">
<property name="label" translatable="yes" context="duplicaterecordsdialog|okaybtn">_Okay</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="cancelbtn">
<property name="label" translatable="yes" context="duplicaterecordsdialog|cancelbtn">Ca_ncel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=5 -->
<object class="GtkGrid" id="grid1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="row-spacing">6</property>
<property name="column-spacing">6</property>
<child>
<object class="GtkLabel" id="orientation">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="label" translatable="yes" context="duplicaterecordsdialog|orientation">Orientation:</property>
<property name="mnemonic-widget">row</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="header">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes" context="duplicaterecordsdialog|header">Hea_der:</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">includesheaders</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="action">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes" context="duplicaterecordsdialog|action">Action:</property>
<property name="mnemonic-widget">select</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">4</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="width-request">280</property>
<property name="height-request">140</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-top">2</property>
<property name="hscrollbar-policy">never</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="checklist">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="hexpand">False</property>
<property name="model">liststore1</property>
<property name="headers-visible">False</property>
<property name="headers-clickable">False</property>
<property name="search-column">0</property>
<property name="show-expanders">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn4">
<property name="sizing">fixed</property>
<property name="title" translatable="yes" context="duplicaterecordsdialog|treeviewcolumn4">All</property>
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererToggle" id="cellrenderer5"/>
<attributes>
<attribute name="visible">3</attribute>
<attribute name="active">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn5">
<property name="resizable">True</property>
<property name="spacing">6</property>
<property name="title" translatable="yes" context="duplicaterecordsdialog|treeviewcolumn5">Row/Column</property>
<child>
<object class="GtkCellRendererText" id="cellrenderer4"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkRadioButton" id="row">
<property name="label" translatable="yes" context="duplicaterecordsdialog|row">By _Row</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="column">
<property name="label" translatable="yes" context="duplicaterecordsdialog|column">By _Column</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">row</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box2">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkRadioButton" id="select">
<property name="label" translatable="yes" context="duplicaterecordsdialog|select">_Select</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="remove">
<property name="label" translatable="yes" context="duplicaterecordsdialog|remove">R_emove</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">select</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">4</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="includesheaders">
<property name="label" translatable="yes" context="duplicaterecordsdialog|includesheaders">Data contains row/col headers</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="items">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes" context="duplicaterecordsdialog|items">Items:</property>
<property name="mnemonic-widget">allcheckbtn</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="allcheckbtn">
<property name="label" translatable="yes" context="duplicaterecordsdialog|allcheckbtn">_All</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-11">helpbutton</action-widget>
<action-widget response="-5">okaybtn</action-widget>
<action-widget response="-6">cancelbtn</action-widget>
</action-widgets>
</object>
</interface>