# HG changeset patch # User Ian Neal # Date 1639519659 0 # Parent 563b3f46f824ac33f474c0d18186363b364f65be Bug 1746081 - Use listbox rather than tree in FilterListDialog. r=frg a=frg diff --git a/suite/mailnews/content/FilterListDialog.js b/suite/mailnews/content/FilterListDialog.js --- a/suite/mailnews/content/FilterListDialog.js +++ b/suite/mailnews/content/FilterListDialog.js @@ -14,17 +14,18 @@ var gTopButton; var gUpButton; var gDownButton; var gBottomButton; var gRunFiltersFolderPrefix; var gRunFiltersFolder; var gRunFiltersButton; var gFilterBundle; var gFilterListMsgWindow = null; -var gFilterTree; +var gFilterListbox; +var gCurrentFilterList; var gStatusBar; var gStatusText; var gServerMenu; var msgMoveMotion = { Up : 0, Down : 1, Top : 2, @@ -58,119 +59,47 @@ var gStatusFeedback = { showProgress: function(percentage) { }, closeWindow: function() { } }; -var gFilterTreeView = { - mTree: null, - get tree() { - return this.mTree; - }, - mFilterList: null, - get filterList() { - return this.mFilterList; - }, - set filterList(val) { - if (this.mTree) - this.mTree.beginUpdateBatch(); - if (this.selection) { - this.selection.clearSelection(); - this.selection.currentIndex = -1; - } - this.mFilterList = val; - if (this.mTree) { - this.mTree.scrollToRow(0); - this.mTree.endUpdateBatch(); - } - }, - /* nsITreeView methods */ - get rowCount() { - return this.mFilterList ? this.mFilterList.filterCount : 0; - }, - selection: null, - getRowProperties: function getRowProperties(row) { - return this.mFilterList.getFilterAt(row).enabled ? "Enabled-true" : ""; - }, - getCellProperties: function getCellProperties(row, col) { - return this.mFilterList.getFilterAt(row).enabled ? "Enabled-true" : ""; - }, - getColumnProperties: function getColumnProperties(col) { return ""; }, - isContainer: function isContainer(index) { return false; }, - isContainerOpen: function isContainerOpen(index) { return false; }, - isContainerEmpty: function isContainerEmpty(index) { return false; }, - isSeparator: function isSeparator(index) { return false; }, - isSorted: function isSorted() { return false; }, - canDrop: function canDrop(index, orientation) { return false; }, - drop: function drop(index, orientation) {}, - getParentIndex: function getParentIndex(index) { return -1; }, - hasNextSibling: function hasNextSibling(rowIndex, afterIndex) { return false; }, - getLevel: function getLevel(index) { return 0; }, - getImageSrc: function getImageSrc(row, col) { return null; }, - getProgressMode: function getProgressMode(row, col) { return 0; }, - getCellValue: function getCellValue(row, col) { return null; }, - getCellText: function getCellText(row, col) { - return this.mFilterList.getFilterAt(row).filterName; - }, - setTree: function setTree(tree) { - this.mTree = tree; - }, - toggleOpenState: function toggleOpenState(index) {}, - cycleHeader: function cycleHeader(col) {}, - selectionChanged: function selectionChanged() {}, - cycleCell: function cycleCell(row, col) { - if (toggleFilter(row)) - this.mTree.invalidateCell(row, col); - }, - isEditable: function isEditable(row, col) { return false; }, // XXX Fix me! - isSelectable: function isSelectable(row, col) { return false; }, - setCellValue: function setCellValue(row, col, value) {}, - setCellText: function setCellText(row, col, value) { /* XXX Write me */ }, - performAction: function performAction(action) {}, - performActionOnRow: function performActionOnRow(action, row) {}, - performActionOnCell: function performActionOnCell(action, row, col) {} -} - function onLoad() { setHelpFileURI("chrome://communicator/locale/help/suitehelp.rdf"); gFilterListMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(Ci.nsIMsgWindow); gFilterListMsgWindow.domWindow = window; gFilterListMsgWindow.rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL; gFilterListMsgWindow.statusFeedback = gStatusFeedback; gFilterBundle = document.getElementById("bundle_filter"); gServerMenu = document.getElementById("serverMenu"); - gFilterTree = document.getElementById("filterTree"); + gFilterListbox = document.getElementById("filterList"); gEditButton = document.getElementById("editButton"); gDeleteButton = document.getElementById("deleteButton"); gNewButton = document.getElementById("newButton"); gCopyToNewButton = document.getElementById("copyToNewButton"); gTopButton = document.getElementById("reorderTopButton"); gUpButton = document.getElementById("reorderUpButton"); gDownButton = document.getElementById("reorderDownButton"); gBottomButton = document.getElementById("reorderBottomButton"); gRunFiltersFolderPrefix = document.getElementById("folderPickerPrefix"); gRunFiltersFolder = document.getElementById("runFiltersFolder"); gRunFiltersButton = document.getElementById("runFiltersButton"); gStatusBar = document.getElementById("statusbar-icon"); gStatusText = document.getElementById("statusText"); - gFilterTree.view = gFilterTreeView; + updateButtons(); processWindowArguments(window.arguments[0]); - // Focus the list. - gFilterTree.focus(); - Services.obs.addObserver(onFilterClose, "quit-application-requested"); top.controllers.insertControllerAt(0, gFilterController); } /** * Processes arguments sent to this dialog when opened or refreshed. @@ -199,21 +128,23 @@ function processWindowArguments(aArgumen if (!firstItem) { var server = getServerThatCanHaveFilters(); if (server) firstItem = server.rootFolder; } if (firstItem) setFilterFolder(firstItem); - else - updateButtons(); if (wantedFolder) setRunFolder(wantedFolder); + } else { + // If we didn't change folder still redraw the list + // to show potential new filters if we were called for refresh. + rebuildFilterList(); } } /** * This is called from OpenOrFocusWindow() if the dialog is already open. * New filters could have been created by operations outside the dialog. * * @param aArguments An object of arguments having the same format @@ -237,32 +168,32 @@ function CanRunFiltersAfterTheFact(aServ * (or a folder for NNTP server). */ function setFilterFolder(msgFolder) { if (!msgFolder || msgFolder == gServerMenu._folder) return; // Save the current filters to disk before switching because // the dialog may be closed and we'll lose current filters. - let filterList = currentFilterList(); - if (filterList) - filterList.saveToDefaultFile(); + if (gCurrentFilterList) + gCurrentFilterList.saveToDefaultFile(); // Setting this attribute should go away in bug 473009. gServerMenu._folder = msgFolder; // Calling this should go away in bug 802609. gServerMenu.menupopup.selectFolder(msgFolder); - // Calling getFilterList will detect any errors in rules.dat, - // backup the file, and alert the user - gFilterTreeView.filterList = msgFolder.getEditableFilterList(gFilterListMsgWindow); + // Calling getEditableFilterList will detect any errors in + // msgFilterRules.dat, backup the file, and alert the user. + gCurrentFilterList = msgFolder.getEditableFilterList(gFilterListMsgWindow); + rebuildFilterList(); // Select the first item in the list, if there is one. - if (gFilterTreeView.rowCount) - gFilterTreeView.selection.select(0); + if (gFilterListbox.itemCount > 0) + gFilterListbox.selectItem(gFilterListbox.getItemAtIndex(0)); // This will get the deferred to account root folder, if server is deferred. // We intentionally do this after setting the current server, as we want // that to refer to the rootFolder for the actual server, not the // deferred-to server, as current server is really a proxy for the // server whose filters we are editing. But below here we are managing // where the filters will get applied, which is on the deferred-to server. msgFolder = msgFolder.server.rootMsgFolder; @@ -325,63 +256,65 @@ function setFilterFolder(msgFolder) { function setRunFolder(aFolder) { // Setting this attribute should go away in bug 473009. gRunFiltersFolder._folder = aFolder; // Calling this should go away in bug 802609. gRunFiltersFolder.menupopup.selectFolder(gRunFiltersFolder._folder); updateButtons(); } -function toggleFilter(index) +/** + * Toggle enabled state of a filter, in both the filter properties and the UI. + * + * @param aFilterItem an item (row) of the filter list to be toggled + */ +function toggleFilter(aFilterItem) { - var filter = getFilter(index); - if (filter.unparseable) - { - Services.prompt.alert(window, null, - gFilterBundle.getFormattedString("cannotEnableIncompatFilter", - [document.getElementById("bundle_brand").getString("brandShortName")])); - return false; - } - filter.enabled = !filter.enabled; - return true; + let filter = aFilterItem._filter; + if (filter.unparseable && !filter.enabled) + { + Services.prompt.alert(window, null, + gFilterBundle.getFormattedString("cannotEnableIncompatFilter", + [document.getElementById("bundle_brand").getString("brandShortName")])); + return; + } + filter.enabled = !filter.enabled; + + // Now update the checkbox + aFilterItem.childNodes[1].setAttribute("enabled", filter.enabled); + // For accessibility set the checked state on listitem + aFilterItem.setAttribute("aria-checked", filter.enabled); } -function getFilter(index) -{ - return gFilterTreeView.filterList.getFilterAt(index); -} - +/** + * Returns the currently selected filter. If multiple filters are selected, + * returns the first one. If none are selected, returns null. + */ function currentFilter() { - var currentIndex = gFilterTree.currentIndex; - return currentIndex == -1 ? null : getFilter(currentIndex); -} - -function currentFilterList() -{ - return gFilterTreeView.filterList; -} - -function onFilterSelect(event) -{ - updateButtons(); + let currentItem = gFilterListbox.selectedItem; + return currentItem ? currentItem._filter : null; } function onEditFilter() { if (gEditButton.disabled) return; var selectedFilter = currentFilter(); - var curFilterList = currentFilterList(); - var args = {filter: selectedFilter, filterList: curFilterList}; + if (!selectedFilter) + return; + + let args = {filter: selectedFilter, filterList: gCurrentFilterList}; window.openDialog("chrome://messenger/content/FilterEditor.xul", "FilterEditor", "chrome,modal,titlebar,resizable,centerscreen", args); - // The focus change will cause a repaint of the row updating any name change + if ("refresh" in args && args.refresh) { + rebuildFilterList(); + } } /** * Handler function for the 'New...' buttons. * Opens the filter dialog for creating a new filter. */ function onNewFilter() { calculatePositionAndShowCreateFilterDialog({}); @@ -407,69 +340,88 @@ function onCopyToNewFilter() { * and then displays the create dialog. * * @param args The object containing the arguments for the dialog, * passed to the filterEditorOnLoad() function. * It will be augmented with the insertion position * and global filters list properties by this function. */ function calculatePositionAndShowCreateFilterDialog(args) { - var position = Math.max(gFilterTree.currentIndex, 0); - args.filterList = currentFilterList(); + let selectedFilter = currentFilter(); + // If no filter is selected use the first position. + let position = 0; + if (selectedFilter) { + // Get the position in the unfiltered list. + // - this is where the new filter should be inserted! + let filterCount = gCurrentFilterList.filterCount; + for (let i = 0; i < filterCount; i++) { + if (gCurrentFilterList.getFilterAt(i) == selectedFilter) { + position = i; + break; + } + } + } args.filterPosition = position; + args.filterList = gCurrentFilterList; args.refresh = false; window.openDialog("chrome://messenger/content/FilterEditor.xul", "FilterEditor", "chrome,modal,titlebar,resizable,centerscreen", args); if (args.refresh) { - gFilterTreeView.tree.rowCountChanged(position, 1); - gFilterTree.view.selection.select(position); - gFilterTree.treeBoxObject.ensureRowIsVisible(position); + rebuildFilterList(); + + // Select the new filter, it is at the position of previous selection. + gFilterListbox.selectItem(gFilterListbox.getItemAtIndex(position)); + if (currentFilter() != args.newFilter) + Cu.reportError("Filter created at an unexpected position!"); } } function onDeleteFilter() { if (gDeleteButton.disabled) return; - var filterList = currentFilterList(); - if (!filterList) - return; - - var sel = gFilterTree.view.selection; - var selCount = sel.getRangeCount(); - if (!selCount) + let items = gFilterListbox.selectedItems; + if (!items.length) return; let checkValue = {value: false}; if (Services.prefs.getBoolPref("mailnews.filters.confirm_delete") && Services.prompt.confirmEx(window, null, gFilterBundle.getString("deleteFilterConfirmation"), Services.prompt.STD_YES_NO_BUTTONS, '', '', '', gFilterBundle.getString('dontWarnAboutDeleteCheckbox'), checkValue)) return; if (checkValue.value) Services.prefs.setBoolPref("mailnews.filters.confirm_delete", false); - for (var i = selCount - 1; i >= 0; --i) { - var start = {}, end = {}; - sel.getRangeAt(i, start, end); - for (var j = end.value; j >= start.value; --j) { - var curFilter = getFilter(j); - if (curFilter) - filterList.removeFilter(curFilter); - } - gFilterTreeView.tree.rowCountChanged(start.value, start.value - end.value - 1); + // Save filter position before the first selected one. + let newSelectionIndex = gFilterListbox.selectedIndex - 1; + + // Must reverse the loop, as the items list shrinks when we delete. + for (let index = items.length - 1; index >= 0; --index) { + let item = items[index]; + gCurrentFilterList.removeFilter(item._filter); + gFilterListbox.removeItemAt(gFilterListbox.getIndexOfItem(item)); + } + + // Select filter above previously selected if one existed, + // otherwise the first one. + if (newSelectionIndex == -1 && gFilterListbox.itemCount > 0) + newSelectionIndex = 0; + if (newSelectionIndex > -1) { + gFilterListbox.selectedIndex = newSelectionIndex; + updateViewPosition(-1); } } /** * Move filter one step up in visible list. */ function onUp(event) { moveFilter(msgMoveMotion.Up); @@ -504,57 +456,57 @@ function onBottom(event) { * msgMoveMotion.Up, msgMoveMotion.Down, msgMoveMotion.Top, msgMoveMotion.Bottom */ function moveFilter(motion) { // At the moment, do not allow moving groups of filters. let selectedFilter = currentFilter(); if (!selectedFilter) return; - let filterList = currentFilterList(); let moveFilterNative; switch (motion) { case msgMoveMotion.Top: - filterList.removeFilter(selectedFilter); - filterList.insertFilterAt(0, selectedFilter); - gFilterTree.treeBoxObject.ensureRowIsVisible(0); - gFilterTree.view.selection.select(0); + if (selectedFilter) { + gCurrentFilterList.removeFilter(selectedFilter); + gCurrentFilterList.insertFilterAt(0, selectedFilter); + rebuildFilterList(); + } return; case msgMoveMotion.Bottom: - filterList.removeFilter(selectedFilter); - filterList.insertFilterAt(filterList.filterCount, selectedFilter); - gFilterTree.treeBoxObject.ensureRowIsVisible(filterList.filterCount - 1); - gFilterTree.view.selection.select(filterList.filterCount - 1); + if (selectedFilter) { + gCurrentFilterList.removeFilter(selectedFilter); + gCurrentFilterList.insertFilterAt(gCurrentFilterList.filterCount, + selectedFilter); + rebuildFilterList(); + } return; case msgMoveMotion.Up: moveFilterNative = Ci.nsMsgFilterMotion.up; break; case msgMoveMotion.Down: moveFilterNative = Ci.nsMsgFilterMotion.down; break; } moveCurrentFilter(moveFilterNative); } function viewLog() { - var filterList = currentFilterList(); - var args = {filterList: filterList}; + let args = {filterList: gCurrentFilterList}; window.openDialog("chrome://messenger/content/viewLog.xul", "FilterLog", "chrome,modal,titlebar,resizable,centerscreen", args); } function onFilterUnload() { // make sure to save the filter to disk - var filterList = currentFilterList(); - if (filterList) - filterList.saveToDefaultFile(); + if (gCurrentFilterList) + gCurrentFilterList.saveToDefaultFile(); Services.obs.removeObserver(onFilterClose, "quit-application-requested"); top.controllers.removeController(gFilterController); } function onFilterClose(aCancelQuit, aTopic, aData) { if (aTopic == "quit-application-requested" && @@ -596,58 +548,150 @@ function runSelectedFilters() if (!folder) return; let filterList = MailServices.filters.getTempFilterList(folder); let folders = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); folders.appendElement(folder); // make sure the tmp filter list uses the real filter list log stream - filterList.logStream = currentFilterList().logStream; - filterList.loggingEnabled = currentFilterList().loggingEnabled; - var index = 0, sel = gFilterTree.view.selection; - for (var i = 0; i < sel.getRangeCount(); i++) { - var start = {}, end = {}; - sel.getRangeAt(i, start, end); - for (var j = start.value; j <= end.value; j++) { - var curFilter = getFilter(j); - if (curFilter) - filterList.insertFilterAt(index++, curFilter); - } + filterList.logStream = gCurrentFilterList.logStream; + filterList.loggingEnabled = gCurrentFilterList.loggingEnabled; + + let index = 0; + for (let item of gFilterListbox.selectedItems) { + filterList.insertFilterAt(index++, item._filter); } MailServices.filters.applyFiltersToFolders(filterList, folders, gFilterListMsgWindow); } function moveCurrentFilter(motion) { - var filterList = currentFilterList(); - var filter = currentFilter(); - if (!filterList || !filter) - return; + let filter = currentFilter(); + if (!filter) + return; + + gCurrentFilterList.moveFilter(filter, motion); + rebuildFilterList(); +} + +/** + * Redraws the list of filters. Takes the search box value into account. + * + * This function should perform very fast even in case of high number of filters. + * Therefore there are some optimizations (e.g. listelement.children[] instead of + * list.getItemAtIndex()), that favour speed vs. semantical perfection. + */ +function rebuildFilterList() +{ + // Make a note of which filters were previously selected + let selectedNames = []; + for (let i = 0; i < gFilterListbox.selectedItems.length; i++) + selectedNames.push(gFilterListbox.selectedItems[i]._filter.filterName); + + // Save scroll position so we can try to restore it later. + // Doesn't work when the list is rebuilt after search box condition changed. + let firstVisibleRowIndex = gFilterListbox.getIndexOfFirstVisibleRow(); + + // listbox.xml seems to cache the value of the first selected item in a + // range at _selectionStart. The old value though is now obsolete, + // since we will recreate all of the elements. We need to clear this, + // and one way to do this is with a call to clearSelection. This might be + // ugly from an accessibility perspective, since it fires an onSelect event. + gFilterListbox.clearSelection(); + + let listitem, nameCell, enabledCell, filter; + let filterCount = gCurrentFilterList.filterCount; + let listitemCount = gFilterListbox.itemCount; + let listitemIndex = 0; + for (let i = 0; i < filterCount; i++) { + filter = gCurrentFilterList.getFilterAt(i); - filterList.moveFilter(filter, motion); - if (motion == Ci.nsMsgFilterMotion.up) - gFilterTree.view.selection.select(gFilterTree.currentIndex - 1); + if (listitemCount > listitemIndex) { + // If there is a free existing listitem, reuse it. + // Use .children[] instead of .getItemAtIndex() as it is much faster. + listitem = gFilterListbox.children[listitemIndex + 1]; + nameCell = listitem.childNodes[0]; + enabledCell = listitem.childNodes[1]; + } else - gFilterTree.view.selection.select(gFilterTree.currentIndex + 1); + { + // If there are not enough listitems in the list, create a new one. + listitem = document.createElement("listitem"); + listitem.setAttribute("role", "checkbox"); + nameCell = document.createElement("listcell"); + enabledCell = document.createElement("listcell"); + enabledCell.setAttribute("class", "listcell-iconic"); + listitem.appendChild(nameCell); + listitem.appendChild(enabledCell); + gFilterListbox.appendChild(listitem); + let size = (enabledCell.clientWidth - 28) / 2; + enabledCell.style.paddingLeft = size.toString() + "px"; + // We have to attach this listener to the listitem, even though we only + // care about clicks on the enabledCell. However, attaching to that item + // doesn't result in any events actually getting received. + listitem.addEventListener("click", onFilterClick, true); + listitem.addEventListener("dblclick", onFilterDoubleClick, true); + } + // For accessibility set the label on listitem. + listitem.setAttribute("label", filter.filterName); + // Set the listitem values to represent the current filter. + nameCell.setAttribute("label", filter.filterName); + enabledCell.setAttribute("enabled", filter.enabled); + listitem.setAttribute("aria-checked", filter.enabled); + listitem._filter = filter; + + if (selectedNames.includes(filter.filterName)) + gFilterListbox.addItemToSelection(listitem); - gFilterTree.treeBoxObject.ensureRowIsVisible(gFilterTree.currentIndex); + listitemIndex++; + } + // Remove any superfluous listitems, if the number of filters shrunk. + for (let i = listitemCount - 1; i >= listitemIndex; i--) { + gFilterListbox.lastChild.remove(); + } + + updateViewPosition(firstVisibleRowIndex); + + gFilterListbox.focus(); +} + +function updateViewPosition(firstVisibleRowIndex) +{ + if (firstVisibleRowIndex == -1) + firstVisibleRowIndex = gFilterListbox.getIndexOfFirstVisibleRow(); + + // Restore to the extent possible the scroll position. + if (firstVisibleRowIndex && gFilterListbox.itemCount) + gFilterListbox.scrollToIndex(Math.min(firstVisibleRowIndex, + gFilterListbox.itemCount - 1)); + + if (gFilterListbox.selectedCount) { + // Make sure that at least the first selected item is visible. + gFilterListbox.ensureElementIsVisible(gFilterListbox.selectedItems[0]); + + // The current item should be the first selected item, so that keyboard + // selection extension can work. + gFilterListbox.currentItem = gFilterListbox.selectedItems[0]; + } + + updateButtons(); } /** * Try to only enable buttons that make sense * - moving filters is currently only enabled for single selection * also movement is restricted by searchBox and current selection position * - edit only for single filters * - delete / run only for one or more selected filters */ function updateButtons() { - var numFiltersSelected = gFilterTree.view.selection.count; + var numFiltersSelected = gFilterListbox.selectedItems.length; var oneFilterSelected = (numFiltersSelected == 1); // "edit" only enabled when one filter selected // or if we couldn't parse the filter. let disabled = !oneFilterSelected || currentFilter().unparseable; gEditButton.disabled = disabled; // "copy" is the same as "edit". @@ -660,24 +704,27 @@ function updateButtons() // so only disable this UI if no filters are selected gRunFiltersFolderPrefix.disabled = !numFiltersSelected; gRunFiltersFolder.disabled = !numFiltersSelected; gRunFiltersButton.disabled = !numFiltersSelected || !gRunFiltersFolder._folder; // "up" and "top" enabled only if one filter is selected, // and it's not the first. - disabled = !(oneFilterSelected && gFilterTree.currentIndex > 0); + // Don't use gFilterListbox.currentIndex here, it's buggy when we've just + // changed the children in the list (via rebuildFilterList) + disabled = !(oneFilterSelected && + gFilterListbox.getSelectedItem(0) != gFilterListbox.getItemAtIndex(0)); gUpButton.disabled = disabled; gTopButton.disabled = disabled; // "down" and "bottom" enabled only if one filter selected, // and it's not the last. disabled = !(oneFilterSelected && - gFilterTree.currentIndex < gFilterTree.view.rowCount - 1); + gFilterListbox.selectedIndex < gFilterListbox.itemCount - 1); gDownButton.disabled = disabled; gBottomButton.disabled = disabled; } /** * Given a selected folder, returns the folder where filters should * be defined (the root folder except for news) if the server can * accept filters. @@ -724,35 +771,42 @@ function getServerThatCanHaveFilters() if (currentServer.canHaveFilters) return currentServer; } return null; } +function onFilterClick(event) +{ + // We only care about button 0 (left click) events. + if (event.button != 0) + return; + + // Remember, we had to attach the click-listener to the whole listitem, so + // now we need to see if the clicked the enable-column + let toggle = event.target.childNodes[1]; + if ((event.clientX < toggle.boxObject.x + toggle.boxObject.width) && + (event.clientX > toggle.boxObject.x)) { + toggleFilter(event.target); + event.stopPropagation(); + } +} + function onFilterDoubleClick(event) { - // we only care about button 0 (left click) events - if (event.button != 0) - return; + // We only care about button 0 (left click) events. + if (event.button != 0) + return; - var cell = gFilterTree.treeBoxObject.getCellAt(event.clientX, event.clientY); - if (cell.row == -1 || cell.row > gFilterTree.view.rowCount - 1 || event.originalTarget.localName != "treechildren") { - // double clicking on a non valid row should not open the edit filter dialog - return; - } - - // if the cell is in a "cycler" column (the enabled column) - // don't open the edit filter dialog with the selected filter - if (!cell.col.cycler) - onEditFilter(); + onEditFilter(); } -function onFilterTreeKeyPress(aEvent) { +function onFilterListKeyPress(aEvent) { if (aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey || aEvent.shiftKey) return; if (aEvent.keyCode) { switch (aEvent.keyCode) { case KeyEvent.DOM_VK_INSERT: if (!gNewButton.disabled) onNewFilter(); @@ -766,24 +820,19 @@ function onFilterTreeKeyPress(aEvent) { onEditFilter(); break; } return; } switch (aEvent.charCode) { case KeyEvent.DOM_VK_SPACE: - let rangeCount = gFilterTree.view.selection.getRangeCount(); - for (let i = 0; i < rangeCount; ++i) { - let start = {}, end = {}; - gFilterTree.view.selection.getRangeAt(i, start, end); - for (let k = start.value; k <= end.value; ++k) - toggleFilter(k); + for (let item of gFilterListbox.selectedItems) { + toggleFilter(item); } - gFilterTree.view.selection.invalidateSelection(); break; default: } } function doHelpButton() { openHelp("mail-filters"); @@ -799,15 +848,15 @@ var gFilterController = isCommandEnabled: function(aCommand) { return aCommand == "cmd_selectAll"; }, doCommand: function(aCommand) { if (aCommand == "cmd_selectAll") - gFilterTree.view.selection.selectAll(); + gFilterListbox.selectAll(); }, onEvent: function(aEvent) { } }; diff --git a/suite/mailnews/content/FilterListDialog.xul b/suite/mailnews/content/FilterListDialog.xul --- a/suite/mailnews/content/FilterListDialog.xul +++ b/suite/mailnews/content/FilterListDialog.xul @@ -67,34 +67,33 @@ - + + + + + + + + - - - - - - -