summaryrefslogtreecommitdiff
path: root/sc
diff options
context:
space:
mode:
authorSahil Gautam <me.sahilgautam@gmail.com>2024-09-24 23:57:22 +0530
committerHeiko Tietze <heiko.tietze@documentfoundation.org>2024-09-27 15:22:46 +0200
commit29fd68bb682006ccaa5aaed516c064b5b6368463 (patch)
tree063cb9f656130ef2217e9016967f888aea4bb584 /sc
parentb8368ad96e779196f7fdaa4dfd5e26a51e1bd829 (diff)
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
Diffstat (limited to 'sc')
-rw-r--r--sc/Library_sc.mk1
-rw-r--r--sc/UIConfig_scalc.mk1
-rw-r--r--sc/inc/sc.hrc1
-rw-r--r--sc/inc/strings.hrc4
-rw-r--r--sc/sdi/scalc.sdi26
-rw-r--r--sc/sdi/tabvwsh.sdi1
-rw-r--r--sc/source/ui/inc/duplicaterecordsdlg.hxx75
-rw-r--r--sc/source/ui/inc/tabvwsh.hxx8
-rw-r--r--sc/source/ui/miscdlgs/duplicaterecordsdlg.cxx214
-rw-r--r--sc/source/ui/view/tabvwsh3.cxx112
-rw-r--r--sc/source/ui/view/tabvwsh4.cxx239
-rw-r--r--sc/uiconfig/scalc/menubar/menubar.xml1
-rw-r--r--sc/uiconfig/scalc/ui/duplicaterecordsdlg.ui339
13 files changed, 1022 insertions, 0 deletions
diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk
index 71103f726be8..824b02714f4d 100644
--- a/sc/Library_sc.mk
+++ b/sc/Library_sc.mk
@@ -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 \
diff --git a/sc/UIConfig_scalc.mk b/sc/UIConfig_scalc.mk
index a030ef783d73..2be15a1a6618 100644
--- a/sc/UIConfig_scalc.mk
+++ b/sc/UIConfig_scalc.mk
@@ -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 \
diff --git a/sc/inc/sc.hrc b/sc/inc/sc.hrc
index ba9054a5a1f8..a1a8be63f46a 100644
--- a/sc/inc/sc.hrc
+++ b/sc/inc/sc.hrc
@@ -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)
diff --git a/sc/inc/strings.hrc b/sc/inc/strings.hrc
index 2277de7826d8..3e23a800974e 100644
--- a/sc/inc/strings.hrc
+++ b/sc/inc/strings.hrc
@@ -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.")
diff --git a/sc/sdi/scalc.sdi b/sc/sdi/scalc.sdi
index d532de343600..d43d4ceea390 100644
--- a/sc/sdi/scalc.sdi
+++ b/sc/sdi/scalc.sdi
@@ -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
[
diff --git a/sc/sdi/tabvwsh.sdi b/sc/sdi/tabvwsh.sdi
index 7130ff4cabd8..48afa923e6fc 100644
--- a/sc/sdi/tabvwsh.sdi
+++ b/sc/sdi/tabvwsh.sdi
@@ -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; ]
diff --git a/sc/source/ui/inc/duplicaterecordsdlg.hxx b/sc/source/ui/inc/duplicaterecordsdlg.hxx
new file mode 100644
index 000000000000..29bcd22e8e2f
--- /dev/null
+++ b/sc/source/ui/inc/duplicaterecordsdlg.hxx
@@ -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: */
diff --git a/sc/source/ui/inc/tabvwsh.hxx b/sc/source/ui/inc/tabvwsh.hxx
index 739e98c33537..43ca32993d80 100644
--- a/sc/source/ui/inc/tabvwsh.hxx
+++ b/sc/source/ui/inc/tabvwsh.hxx
@@ -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;
diff --git a/sc/source/ui/miscdlgs/duplicaterecordsdlg.cxx b/sc/source/ui/miscdlgs/duplicaterecordsdlg.cxx
new file mode 100644
index 000000000000..3dd0eebc6ad5
--- /dev/null
+++ b/sc/source/ui/miscdlgs/duplicaterecordsdlg.cxx
@@ -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: */
diff --git a/sc/source/ui/view/tabvwsh3.cxx b/sc/source/ui/view/tabvwsh3.cxx
index 2984cb3c9e19..ddaf0fd31fc6 100644
--- a/sc/source/ui/view/tabvwsh3.cxx
+++ b/sc/source/ui/view/tabvwsh3.cxx
@@ -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();
diff --git a/sc/source/ui/view/tabvwsh4.cxx b/sc/source/ui/view/tabvwsh4.cxx
index a61bbf4ae242..60e0b6ab5ad3 100644
--- a/sc/source/ui/view/tabvwsh4.cxx
+++ b/sc/source/ui/view/tabvwsh4.cxx
@@ -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),
diff --git a/sc/uiconfig/scalc/menubar/menubar.xml b/sc/uiconfig/scalc/menubar/menubar.xml
index dad1d6844f4e..61ea6c74b286 100644
--- a/sc/uiconfig/scalc/menubar/menubar.xml
+++ b/sc/uiconfig/scalc/menubar/menubar.xml
@@ -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"/>
diff --git a/sc/uiconfig/scalc/ui/duplicaterecordsdlg.ui b/sc/uiconfig/scalc/ui/duplicaterecordsdlg.ui
new file mode 100644
index 000000000000..4b21b26d5cb1
--- /dev/null
+++ b/sc/uiconfig/scalc/ui/duplicaterecordsdlg.ui
@@ -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>