diff --git a/apps/spreadsheeteditor/main/app/controller/DataTab.js b/apps/spreadsheeteditor/main/app/controller/DataTab.js
index c09d9ac0d..5fe81fb6b 100644
--- a/apps/spreadsheeteditor/main/app/controller/DataTab.js
+++ b/apps/spreadsheeteditor/main/app/controller/DataTab.js
@@ -43,7 +43,8 @@ define([
'core',
'spreadsheeteditor/main/app/view/DataTab',
'spreadsheeteditor/main/app/view/GroupDialog',
- 'spreadsheeteditor/main/app/view/SortDialog'
+ 'spreadsheeteditor/main/app/view/SortDialog',
+ 'spreadsheeteditor/main/app/view/RemoveDuplicatesDialog'
], function () {
'use strict';
@@ -88,7 +89,8 @@ define([
'data:show': this.onShowClick,
'data:hide': this.onHideClick,
'data:groupsettings': this.onGroupSettings,
- 'data:sortcustom': this.onCustomSort
+ 'data:sortcustom': this.onCustomSort,
+ 'data:remduplicates': this.onRemoveDuplicates
},
'Statusbar': {
'sheet:changed': this.onApiSheetChanged
@@ -258,6 +260,49 @@ define([
}
},
+ onRemoveDuplicates: function() {
+ var me = this;
+ if (this.api) {
+ var res = this.api.asc_sortCellsRangeExpand();
+ if (res) {
+ var config = {
+ width: 500,
+ title: this.txtRemDuplicates,
+ msg: this.txtExpandRemDuplicates,
+
+ buttons: [ {caption: this.txtExpand, primary: true, value: 'expand'},
+ {caption: this.txtRemSelected, primary: true, value: 'remove'},
+ 'cancel'],
+ callback: function(btn){
+ if (btn == 'expand' || btn == 'remove') {
+ setTimeout(function(){
+ me.showRemDuplicates(btn == 'expand');
+ },1);
+ }
+ }
+ };
+ Common.UI.alert(config);
+ } else
+ me.showRemDuplicates(res !== null);
+ }
+ },
+
+ showRemDuplicates: function(expand) {
+ var me = this,
+ props = me.api.asc_getRemoveDuplicates(expand);
+ if (props) {
+ (new SSE.Views.RemoveDuplicatesDialog({
+ props: props,
+ api: me.api,
+ handler: function (result, settings) {
+ if (me && me.api) {
+ me.api.asc_setRemoveDuplicates(settings, result != 'ok');
+ }
+ }
+ })).show();
+ }
+ },
+
onWorksheetLocked: function(index,locked) {
if (index == this.api.asc_getActiveWorksheetIndex()) {
Common.Utils.lockControls(SSE.enumLock.sheetLock, locked, {array: this.view.btnsSortDown.concat(this.view.btnsSortUp, this.view.btnCustomSort, this.view.btnGroup, this.view.btnUngroup)});
@@ -271,7 +316,11 @@ define([
this.onWorksheetLocked(currentSheet, this.api.asc_isWorksheetLockedOrDeleted(currentSheet));
},
- textWizard: 'Text to Columns Wizard'
+ textWizard: 'Text to Columns Wizard',
+ txtRemDuplicates: 'Remove Duplicates',
+ txtExpandRemDuplicates: 'The data next to the selection will not be removed. Do you want to expand the selection to include the adjacent data or continue with the currently selected cells only?',
+ txtExpand: 'Expand',
+ txtRemSelected: 'Remove in selected'
}, SSE.Controllers.DataTab || {}));
});
\ No newline at end of file
diff --git a/apps/spreadsheeteditor/main/app/controller/Main.js b/apps/spreadsheeteditor/main/app/controller/Main.js
index d6c456e67..8f8bff632 100644
--- a/apps/spreadsheeteditor/main/app/controller/Main.js
+++ b/apps/spreadsheeteditor/main/app/controller/Main.js
@@ -1477,6 +1477,15 @@ define([
config.msg = this.errorFTRangeIncludedOtherTables;
break;
+
+ case Asc.c_oAscError.ID.RemoveDuplicates:
+ config.iconCls = 'info';
+ config.title = Common.UI.Window.prototype.textInformation;
+ config.buttons = ['ok'];
+ config.msg = (errData.asc_getDuplicateValues()!==null && errData.asc_getUniqueValues()!==null) ? Common.Utils.String.format(this.errRemDuplicates, errData.asc_getDuplicateValues(), errData.asc_getUniqueValues()) : this.errNoDuplicates;
+ config.maxwidth = 600;
+ break;
+
default:
config.msg = (typeof id == 'string') ? id : this.errorDefaultMessage.replace('%1', id);
break;
@@ -1525,7 +1534,7 @@ define([
if (id == Asc.c_oAscError.ID.EditingError || $('.asc-window.modal.alert:visible').length < 1 && (id !== Asc.c_oAscError.ID.ForceSaveTimeout)) {
Common.UI.alert(config);
- Common.component.Analytics.trackEvent('Internal Error', id.toString());
+ (id!==undefined) && Common.component.Analytics.trackEvent('Internal Error', id.toString());
}
},
@@ -2519,7 +2528,9 @@ define([
txtValues: 'Values',
txtGrandTotal: 'Grand Total',
txtRowLbls: 'Row Labels',
- txtColLbls: 'Column Labels'
+ txtColLbls: 'Column Labels',
+ errNoDuplicates: 'No duplicate values found.',
+ errRemDuplicates: 'Duplicate values found and deleted: {0}, unique values left: {1}.'
}
})(), SSE.Controllers.Main || {}))
});
diff --git a/apps/spreadsheeteditor/main/app/controller/Toolbar.js b/apps/spreadsheeteditor/main/app/controller/Toolbar.js
index c3ed3e331..08cd523a2 100644
--- a/apps/spreadsheeteditor/main/app/controller/Toolbar.js
+++ b/apps/spreadsheeteditor/main/app/controller/Toolbar.js
@@ -2273,7 +2273,7 @@ define([
}
need_disable = this._state.controlsdisabled.filters || (val===null);
toolbar.lockToolbar(SSE.enumLock.ruleFilter, need_disable,
- { array: toolbar.btnsSetAutofilter.concat(toolbar.btnsSortDown, toolbar.btnsSortUp, toolbar.btnCustomSort, toolbar.btnTableTemplate, toolbar.btnInsertTable) });
+ { array: toolbar.btnsSetAutofilter.concat(toolbar.btnsSortDown, toolbar.btnsSortUp, toolbar.btnCustomSort, toolbar.btnTableTemplate, toolbar.btnInsertTable, toolbar.btnRemoveDuplicates) });
val = (formatTableInfo) ? formatTableInfo.asc_getTableStyleName() : null;
if (this._state.tablestylename !== val && this.toolbar.mnuTableTemplatePicker) {
@@ -2303,11 +2303,12 @@ define([
toolbar.lockToolbar(SSE.enumLock.multiselect, this._state.multiselect, { array: [toolbar.btnTableTemplate, toolbar.btnInsertHyperlink, toolbar.btnInsertTable]});
this._state.inpivot = !!info.asc_getPivotTableInfo();
- toolbar.lockToolbar(SSE.enumLock.editPivot, this._state.inpivot, { array: toolbar.btnsSetAutofilter.concat(toolbar.btnsClearAutofilter, toolbar.btnsSortDown, toolbar.btnsSortUp, toolbar.btnCustomSort, toolbar.btnMerge, toolbar.btnInsertHyperlink, toolbar.btnInsertTable)});
+ toolbar.lockToolbar(SSE.enumLock.editPivot, this._state.inpivot, { array: toolbar.btnsSetAutofilter.concat(toolbar.btnsClearAutofilter, toolbar.btnsSortDown, toolbar.btnsSortUp, toolbar.btnCustomSort,
+ toolbar.btnMerge, toolbar.btnInsertHyperlink, toolbar.btnInsertTable, toolbar.btnRemoveDuplicates)});
need_disable = !this.appConfig.canModifyFilter;
- toolbar.lockToolbar(SSE.enumLock.cantModifyFilter, need_disable, { array: toolbar.btnsSetAutofilter.concat(toolbar.btnsSortDown, toolbar.btnsSortUp, toolbar.btnCustomSort, toolbar.btnTableTemplate, toolbar.btnClearStyle.menu.items[0], toolbar.btnClearStyle.menu.items[2],
- toolbar.btnInsertTable)});
+ toolbar.lockToolbar(SSE.enumLock.cantModifyFilter, need_disable, { array: toolbar.btnsSetAutofilter.concat(toolbar.btnsSortDown, toolbar.btnsSortUp, toolbar.btnCustomSort, toolbar.btnTableTemplate,
+ toolbar.btnClearStyle.menu.items[0], toolbar.btnClearStyle.menu.items[2], toolbar.btnInsertTable, toolbar.btnRemoveDuplicates)});
}
@@ -3175,6 +3176,7 @@ define([
me.toolbar.btnsSetAutofilter = datatab.getButtons('set-filter');
me.toolbar.btnsClearAutofilter = datatab.getButtons('clear-filter');
me.toolbar.btnCustomSort = datatab.getButtons('sort-custom');
+ me.toolbar.btnRemoveDuplicates = datatab.getButtons('rem-duplicates');
var formulatab = me.getApplication().getController('FormulaDialog');
formulatab.setConfig({toolbar: me});
diff --git a/apps/spreadsheeteditor/main/app/template/Toolbar.template b/apps/spreadsheeteditor/main/app/template/Toolbar.template
index 0cc1fe7b9..958f2d26d 100644
--- a/apps/spreadsheeteditor/main/app/template/Toolbar.template
+++ b/apps/spreadsheeteditor/main/app/template/Toolbar.template
@@ -205,6 +205,7 @@
+
diff --git a/apps/spreadsheeteditor/main/app/view/DataTab.js b/apps/spreadsheeteditor/main/app/view/DataTab.js
index 5b2f59a8d..3ad81a080 100644
--- a/apps/spreadsheeteditor/main/app/view/DataTab.js
+++ b/apps/spreadsheeteditor/main/app/view/DataTab.js
@@ -66,6 +66,9 @@ define([
me.btnTextToColumns.on('click', function (b, e) {
me.fireEvent('data:tocolumns');
});
+ me.btnRemoveDuplicates.on('click', function (b, e) {
+ me.fireEvent('data:remduplicates');
+ });
// isn't used for awhile
// me.btnShow.on('click', function (b, e) {
// me.fireEvent('data:show');
@@ -166,6 +169,16 @@ define([
// Common.Utils.injectComponent($host.find('#slot-btn-hide-details'), this.btnHide);
// this.lockedControls.push(this.btnHide);
+ this.btnRemoveDuplicates = new Common.UI.Button({
+ cls: 'btn-toolbar x-huge icon-top',
+ iconCls: 'toolbar__icon btn-remove-duplicates',
+ caption: this.capBtnTextRemDuplicates,
+ disabled: true,
+ lock: [_set.editCell, _set.selChart, _set.selChartText, _set.selShape, _set.selShapeText, _set.selImage, _set.lostConnect, _set.coAuth, _set.ruleFilter, _set.editPivot, _set.cantModifyFilter, _set.sheetLock]
+ });
+ Common.Utils.injectComponent($host.find('#slot-btn-rem-duplicates'), this.btnRemoveDuplicates);
+ this.lockedControls.push(this.btnRemoveDuplicates);
+
this.btnCustomSort = new Common.UI.Button({
cls: 'btn-toolbar x-huge icon-top',
iconCls: 'toolbar__icon btn-custom-sort',
@@ -226,6 +239,7 @@ define([
me.btnGroup.setMenu(_menu);
me.btnTextToColumns.updateHint(me.tipToColumns);
+ me.btnRemoveDuplicates.updateHint(me.tipRemDuplicates);
me.btnsSortDown.forEach( function(btn) {
btn.updateHint(me.toolbar.txtSortAZ);
@@ -261,6 +275,8 @@ define([
return this.btnsSetAutofilter;
else if (type == 'clear-filter')
return this.btnsClearAutofilter;
+ else if (type == 'rem-duplicates')
+ return this.btnRemoveDuplicates;
else if (type===undefined)
return this.lockedControls;
return [];
@@ -290,7 +306,9 @@ define([
textBelow: 'Summary rows below detail',
textRightOf: 'Summary columns to right of detail',
capBtnTextCustomSort: 'Custom Sort',
- tipCustomSort: 'Custom sort'
+ tipCustomSort: 'Custom sort',
+ capBtnTextRemDuplicates: 'Remove Duplicates',
+ tipRemDuplicates: 'Remove duplicate rows from a sheet'
}
}()), SSE.Views.DataTab || {}));
});
diff --git a/apps/spreadsheeteditor/main/app/view/RemoveDuplicatesDialog.js b/apps/spreadsheeteditor/main/app/view/RemoveDuplicatesDialog.js
new file mode 100644
index 000000000..887c691ff
--- /dev/null
+++ b/apps/spreadsheeteditor/main/app/view/RemoveDuplicatesDialog.js
@@ -0,0 +1,303 @@
+/*
+ *
+ * (c) Copyright Ascensio System SIA 2010-2020
+ *
+ * This program is a free software product. You can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License (AGPL)
+ * version 3 as published by the Free Software Foundation. In accordance with
+ * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
+ * that Ascensio System SIA expressly excludes the warranty of non-infringement
+ * of any third-party rights.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
+ * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
+ * street, Riga, Latvia, EU, LV-1050.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of the Program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU AGPL version 3.
+ *
+ * Pursuant to Section 7(b) of the License you must retain the original Product
+ * logo when distributing the program. Pursuant to Section 7(e) we decline to
+ * grant you any rights under trademark law for use of our trademarks.
+ *
+ * All the Product's GUI elements, including illustrations and icon sets, as
+ * well as technical writing content are licensed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International. See the License
+ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+ *
+ */
+/**
+ * RemoveDuplicatesDialog.js
+ *
+ * Created by Julia Radzhabova on 07.04.2020
+ * Copyright (c) 2020 Ascensio System SIA. All rights reserved.
+ *
+ */
+
+define([
+ 'common/main/lib/component/Window',
+ 'common/main/lib/component/ComboBox',
+ 'common/main/lib/component/ListView'
+], function () {
+ 'use strict';
+
+ SSE.Views.RemoveDuplicatesDialog = Common.UI.Window.extend(_.extend({
+ options: {
+ width: 350,
+ style: 'min-width: 230px;',
+ cls: 'modal-dlg',
+ buttons: ['ok', 'cancel']
+ },
+
+ initialize : function (options) {
+ var t = this,
+ _options = {};
+
+ _.extend(this.options, {
+ title: this.txtTitle
+ }, options || {});
+
+ this.template = [
+ '
',
+ '
',
+ '' + this.textDescription + ' ',
+ '
',
+ '
',
+ '',
+ '
',
+ '
',
+ '' + this.textColumns + ' ',
+ '
',
+ '
',
+ '
'
+ ].join('');
+
+ this.options.tpl = _.template(this.template)(this.options);
+ this.api = this.options.api;
+ this.props = this.options.props;
+ this.handler = this.options.handler;
+
+ Common.UI.Window.prototype.initialize.call(this, this.options);
+ },
+ render: function () {
+ Common.UI.Window.prototype.render.call(this);
+
+ this.chHeaders = new Common.UI.CheckBox({
+ el: $('#rem-duplicates-dlg-headers'),
+ labelText: this.textHeaders
+ });
+ this.chHeaders.on('change', _.bind(function(field, newValue, oldValue, eOpts){
+ this.props.asc_setHasHeaders(field.getValue()=='checked');
+ this.props.asc_updateColumnList();
+ this.updateColumnsList();
+ }, this));
+
+ this.columnsList = new Common.UI.ListView({
+ el: $('#rem-duplicates-dlg-columns', this.$window),
+ store: new Common.UI.DataViewStore(),
+ simpleAddMode: true,
+ scrollAlwaysVisible: true,
+ template: _.template(['
'].join('')),
+ itemTemplate: _.template([
+ '
',
+ '
',
+ ' ',
+ ' ',
+ ' ',
+ '
',
+ '
<%= Common.Utils.String.htmlEncode(value) %>
',
+ '
',
+ '
'
+ ].join(''))
+ });
+ this.columnsList.on({
+ 'item:change': this.onItemChanged.bind(this),
+ 'item:add': this.onItemChanged.bind(this),
+ 'item:select': this.onCellCheck.bind(this)
+ });
+ this.columnsList.onKeyDown = _.bind(this.onListKeyDown, this);
+
+ this.$window.find('.dlg-btn').on('click', _.bind(this.onBtnClick, this));
+ this.afterRender();
+ },
+
+ updateColumnsList: function() {
+ var selectAllState = false,
+ selectedCells = 0,
+ arr = [],
+ store = this.columnsList.store;
+
+ if (store.length<1) {
+ this.props.asc_getColumnList().forEach(function (item, index) {
+ var visible = item.asc_getVisible();
+ arr.push(new Common.UI.DataViewModel({
+ id : index,
+ selected : false,
+ allowSelected : true,
+ value : item.asc_getVal(),
+ groupid : '1',
+ check : visible
+ }));
+ if (visible) selectedCells++;
+ });
+
+ if (selectedCells==arr.length) selectAllState = true;
+ else if (selectedCells>0) selectAllState = 'indeterminate';
+
+ if (arr.length>0)
+ arr.unshift(new Common.UI.DataViewModel({
+ id : arr.length,
+ selected : false,
+ allowSelected : true,
+ value : this.textSelectAll,
+ groupid : '0',
+ check : selectAllState,
+ throughIndex : 0
+ }));
+ this.columnsList.store.reset(arr);
+ this.columnsList.scroller.update({minScrollbarLength : 40, alwaysVisibleY: true, suppressScrollX: true});
+ } else {
+ this.props.asc_getColumnList().forEach(function (item, index) {
+ store.at(index+1).set('value', item.asc_getVal());
+ });
+ }
+ },
+
+ onItemChanged: function (view, record) {
+ var state = record.model.get('check');
+ if ( state == 'indeterminate' )
+ $('input[type=checkbox]', record.$el).prop('indeterminate', true);
+ else $('input[type=checkbox]', record.$el).prop({checked: state, indeterminate: false});
+ },
+
+ onCellCheck: function (listView, itemView, record) {
+ if (this.checkCellTrigerBlock)
+ return;
+
+ var target = '', isLabel = false, bound = null;
+
+ var event = window.event ? window.event : window._event;
+ if (event) {
+ target = $(event.currentTarget).find('.list-item');
+
+ if (target.length) {
+ bound = target.get(0).getBoundingClientRect();
+ var _clientX = event.clientX*Common.Utils.zoom(),
+ _clientY = event.clientY*Common.Utils.zoom();
+ if (bound.left < _clientX && _clientX < bound.right &&
+ bound.top < _clientY && _clientY < bound.bottom) {
+ isLabel = true;
+ }
+ }
+
+ if (isLabel || event.target.className.match('checkbox')) {
+ this.updateCellCheck(listView, record);
+
+ _.delay(function () {
+ listView.$el.find('.listview').focus();
+ }, 100, this);
+ }
+ }
+ },
+
+ onListKeyDown: function (e, data) {
+ var record = null, listView = this.columnsList;
+
+ if (listView.disabled) return;
+ if (_.isUndefined(undefined)) data = e;
+
+ if (data.keyCode == Common.UI.Keys.SPACE) {
+ data.preventDefault();
+ data.stopPropagation();
+
+ this.updateCellCheck(listView, listView.getSelectedRec());
+
+ } else {
+ Common.UI.DataView.prototype.onKeyDown.call(this.columnsList, e, data);
+ }
+ },
+
+ updateCellCheck: function (listView, record) {
+ if (record && listView) {
+ var store = listView.store,
+ check = !record.get('check'),
+ me = this;
+ if ('0' == record.get('groupid')) {
+ store.each(function(cell) {
+ cell.set('check', check);
+ });
+ } else {
+ record.set('check', check);
+ var selectAllState = check;
+ for (var i=0; i< store.length; i++) {
+ var cell = store.at(i);
+ if ('1' == cell.get('groupid') && cell.get('check') !== check) {
+ selectAllState = 'indeterminate';
+ break;
+ }
+ }
+ this.checkCellTrigerBlock = true;
+ store.at(0).set('check', selectAllState);
+ this.checkCellTrigerBlock = undefined;
+ }
+
+ listView.scroller.update({minScrollbarLength : 40, alwaysVisibleY: true, suppressScrollX: true});
+ }
+ },
+
+ afterRender: function() {
+ this._setDefaults(this.props);
+ },
+
+ _setDefaults: function (props) {
+ if (props) {
+ this.chHeaders.setValue(!!props.asc_getHasHeaders(), true);
+ this.updateColumnsList();
+ }
+ },
+
+ getSettings: function () {
+ var store = this.columnsList.store,
+ props = this.props.asc_getColumnList();
+ store.each(function(item, index) {
+ if ('1' == item.get('groupid')) {
+ props[index-1].asc_setVisible(item.get('check'));
+ }
+ });
+ return this.props;
+ },
+
+ onBtnClick: function(event) {
+ this._handleInput(event.currentTarget.attributes['result'].value);
+ },
+
+ onDblClickFormat: function () {
+ this._handleInput('ok');
+ },
+
+ onPrimary: function(event) {
+ this._handleInput('ok');
+ return false;
+ },
+
+ _handleInput: function(state) {
+ if (this.options.handler) {
+ this.options.handler.call(this, state, (state == 'ok') ? this.getSettings() : this.props);
+ }
+
+ this.close();
+ },
+
+ //
+ txtTitle: 'Remove Duplicates',
+ textDescription: 'To delete duplicate values, select one or more columns that contain duplicates.',
+ textHeaders: 'My data has headers',
+ textColumns: 'Columns',
+ textSelectAll: 'Select All'
+
+ }, SSE.Views.RemoveDuplicatesDialog || {}));
+});
\ No newline at end of file
diff --git a/apps/spreadsheeteditor/main/locale/en.json b/apps/spreadsheeteditor/main/locale/en.json
index 45122c077..1f3fd1559 100644
--- a/apps/spreadsheeteditor/main/locale/en.json
+++ b/apps/spreadsheeteditor/main/locale/en.json
@@ -265,6 +265,10 @@
"Common.Views.SymbolTableDialog.textDOQuote": "Double Opening Quote",
"Common.Views.SymbolTableDialog.textDCQuote": "Double Closing Quote",
"SSE.Controllers.DataTab.textWizard": "Text to Columns",
+ "SSE.Controllers.DataTab.txtRemDuplicates": "Remove Duplicates",
+ "SSE.Controllers.DataTab.txtExpandRemDuplicates": "The data next to the selection will not be removed. Do you want to expand the selection to include the adjacent data or continue with the currently selected cells only?",
+ "SSE.Controllers.DataTab.txtExpand": "Expand",
+ "SSE.Controllers.DataTab.txtRemSelected": "Remove in selected",
"SSE.Controllers.DocumentHolder.alignmentText": "Alignment",
"SSE.Controllers.DocumentHolder.centerText": "Center",
"SSE.Controllers.DocumentHolder.deleteColumnText": "Delete Column",
@@ -807,6 +811,8 @@
"SSE.Controllers.Main.txtGrandTotal": "Grand Total",
"SSE.Controllers.Main.txtRowLbls": "Row Labels",
"SSE.Controllers.Main.txtColLbls": "Column Labels",
+ "SSE.Controllers.Main.errNoDuplicates": "No duplicate values found.",
+ "SSE.Controllers.Main.errRemDuplicates": "Duplicate values found and deleted: {0}, unique values left: {1}.",
"SSE.Controllers.Print.strAllSheets": "All Sheets",
"SSE.Controllers.Print.textWarning": "Warning",
"SSE.Controllers.Print.txtCustom": "Custom",
@@ -1420,6 +1426,8 @@
"SSE.Views.DataTab.tipGroup": "Group range of cells",
"SSE.Views.DataTab.tipToColumns": "Separate cell text into columns",
"SSE.Views.DataTab.tipUngroup": "Ungroup range of cells",
+ "SSE.Views.DataTab.capBtnTextRemDuplicates": "Remove Duplicates",
+ "SSE.Views.DataTab.tipRemDuplicates": "Remove duplicate rows from a sheet",
"SSE.Views.DigitalFilterDialog.capAnd": "And",
"SSE.Views.DigitalFilterDialog.capCondition1": "equals",
"SSE.Views.DigitalFilterDialog.capCondition10": "does not end with",
@@ -2080,6 +2088,11 @@
"SSE.Views.PrintTitlesDialog.textFirstRow": "First row",
"SSE.Views.PrintTitlesDialog.textFirstCol": "First column",
"SSE.Views.PrintTitlesDialog.textInvalidRange": "ERROR! Invalid cells range",
+ "SSE.Views.RemoveDuplicatesDialog.txtTitle": "Remove Duplicates",
+ "SSE.Views.RemoveDuplicatesDialog.textDescription": "To delete duplicate values, select one or more columns that contain duplicates.",
+ "SSE.Views.RemoveDuplicatesDialog.textHeaders": "My data has headers",
+ "SSE.Views.RemoveDuplicatesDialog.textColumns": "Columns",
+ "SSE.Views.RemoveDuplicatesDialog.textSelectAll": "Select All",
"SSE.Views.RightMenu.txtCellSettings": "Cell settings",
"SSE.Views.RightMenu.txtChartSettings": "Chart settings",
"SSE.Views.RightMenu.txtImageSettings": "Image settings",
diff --git a/apps/spreadsheeteditor/main/resources/img/toolbar/1.5x/big/btn-remove-duplicates.png b/apps/spreadsheeteditor/main/resources/img/toolbar/1.5x/big/btn-remove-duplicates.png
new file mode 100644
index 000000000..66bb66ca9
Binary files /dev/null and b/apps/spreadsheeteditor/main/resources/img/toolbar/1.5x/big/btn-remove-duplicates.png differ
diff --git a/apps/spreadsheeteditor/main/resources/img/toolbar/1x/big/btn-remove-duplicates.png b/apps/spreadsheeteditor/main/resources/img/toolbar/1x/big/btn-remove-duplicates.png
new file mode 100644
index 000000000..4705cae78
Binary files /dev/null and b/apps/spreadsheeteditor/main/resources/img/toolbar/1x/big/btn-remove-duplicates.png differ
diff --git a/apps/spreadsheeteditor/main/resources/img/toolbar/2x/big/btn-remove-duplicates.png b/apps/spreadsheeteditor/main/resources/img/toolbar/2x/big/btn-remove-duplicates.png
new file mode 100644
index 000000000..727024606
Binary files /dev/null and b/apps/spreadsheeteditor/main/resources/img/toolbar/2x/big/btn-remove-duplicates.png differ