diff options
author | Sahil Gautam <me.sahilgautam@gmail.com> | 2024-09-24 23:57:22 +0530 |
---|---|---|
committer | Heiko Tietze <heiko.tietze@documentfoundation.org> | 2024-09-27 15:22:46 +0200 |
commit | 29fd68bb682006ccaa5aaed516c064b5b6368463 (patch) | |
tree | 063cb9f656130ef2217e9016967f888aea4bb584 /sc | |
parent | b8368ad96e779196f7fdaa4dfd5e26a51e1bd829 (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.mk | 1 | ||||
-rw-r--r-- | sc/UIConfig_scalc.mk | 1 | ||||
-rw-r--r-- | sc/inc/sc.hrc | 1 | ||||
-rw-r--r-- | sc/inc/strings.hrc | 4 | ||||
-rw-r--r-- | sc/sdi/scalc.sdi | 26 | ||||
-rw-r--r-- | sc/sdi/tabvwsh.sdi | 1 | ||||
-rw-r--r-- | sc/source/ui/inc/duplicaterecordsdlg.hxx | 75 | ||||
-rw-r--r-- | sc/source/ui/inc/tabvwsh.hxx | 8 | ||||
-rw-r--r-- | sc/source/ui/miscdlgs/duplicaterecordsdlg.cxx | 214 | ||||
-rw-r--r-- | sc/source/ui/view/tabvwsh3.cxx | 112 | ||||
-rw-r--r-- | sc/source/ui/view/tabvwsh4.cxx | 239 | ||||
-rw-r--r-- | sc/uiconfig/scalc/menubar/menubar.xml | 1 | ||||
-rw-r--r-- | sc/uiconfig/scalc/ui/duplicaterecordsdlg.ui | 339 |
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> |