Bug 316916 - Implement Task filter (Search for tasks in task list on tasks tab) / Implement "Find Events or Tasks". r=philipp
authorMatthew Mecca <matthew.mecca@gmail.com>
Tue, 03 Jan 2012 21:23:00 -0500
changeset 10340 3ad982e0fa84ea090b4bec97d51282621ca465c1
parent 10339 e139a77f8bb1b701f23298e7200639a0f9de251a
child 10341 fd9f0ac2bcaf627de8e34bfe9ffcbac868af220c
push id402
push userbugzilla@standard8.plus.com
push dateTue, 13 Mar 2012 21:17:18 +0000
treeherdercomm-beta@d080a8ebf16a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilipp
bugs316916
Bug 316916 - Implement Task filter (Search for tasks in task list on tasks tab) / Implement "Find Events or Tasks". r=philipp
calendar/base/content/calendar-common-sets.js
calendar/base/content/calendar-task-tree.xml
calendar/base/content/calendar-task-view.js
calendar/base/content/calendar-task-view.xul
calendar/base/src/calFilter.js
calendar/base/themes/gnomestripe/calendar-task-view.css
calendar/base/themes/pinstripe/calendar-task-view.css
calendar/base/themes/winstripe/calendar-task-view.css
calendar/base/themes/winstripe/win-aero/calendar.css
calendar/locales/en-US/chrome/calendar/calendar.dtd
--- a/calendar/base/content/calendar-common-sets.js
+++ b/calendar/base/content/calendar-common-sets.js
@@ -721,17 +721,18 @@ var calendarController2 = {
         "button_print": true,
         "button_delete": true,
         "cmd_delete": true,
         "cmd_properties": true,
         "cmd_goForward": true,
         "cmd_goBack": true,
         "cmd_fullZoomReduce": true,
         "cmd_fullZoomEnlarge": true,
-        "cmd_fullZoomReset": true
+        "cmd_fullZoomReset": true,
+        "cmd_showQuickFilterBar": true
     },
 
     // These functions can use the same from the calendar controller for now.
     updateCommands: calendarController.updateCommands,
     supportsCommand: calendarController.supportsCommand,
     onEvent: calendarController.onEvent,
 
     isCommandEnabled: function isCommandEnabled(aCommand) {
@@ -755,16 +756,18 @@ var calendarController2 = {
             case "cmd_fullZoomReduce":
             case "cmd_fullZoomEnlarge":
             case "cmd_fullZoomReset":
               return calendarController.isInMode("calendar") &&
                      currentView().supportsZoom;
             case "cmd_properties":
             case "cmd_printpreview":
                 return false;
+            case "cmd_showQuickFilterBar":
+                return calendarController.isInMode("task");
             default:
                 return true;
         }
     },
 
     doCommand: function doCommand(aCommand) {
         switch (aCommand) {
             // These commands are overridden in lightning and native in sunbird.
@@ -802,16 +805,19 @@ var calendarController2 = {
                 currentView().zoomIn();
                 break;
             case "cmd_fullZoomEnlarge":
                 currentView().zoomOut();
                 break;
             case "cmd_fullZoomReset":
                 currentView().zoomReset();
                 break;
+            case "cmd_showQuickFilterBar":
+                document.getElementById('task-text-filter-field').select();
+                break;
 
             case "button_delete":
             case "cmd_delete":
                 calendarController.doCommand("calendar_delete_focused_item_command");
                 break;
         }
     }
 };
--- a/calendar/base/content/calendar-task-tree.xml
+++ b/calendar/base/content/calendar-task-tree.xml
@@ -246,16 +246,17 @@
       <field name="mHash2Index"><![CDATA[({})]]></field>
       <field name="mRefreshQueue">[]</field>
       <field name="mPendingRefresh">null</field>
       <field name="mShowCompletedTasks">true</field>
       <field name="mFilter">null</field>
       <field name="mStartDate">null</field>
       <field name="mEndDate">null</field>
       <field name="mDateRangeFilter">null</field>
+      <field name="mTextFilterField">null</field>
 
       <property name="currentIndex">
         <getter><![CDATA[
           var tree = document.getAnonymousElementByAttribute(
               this, "anonid", "calendar-task-tree");
           return tree.currentIndex;
         ]]></getter>
       </property>
@@ -300,16 +301,27 @@
         <getter><![CDATA[
           return this.mShowCompletedTasks;
         ]]></getter>
         <setter><![CDATA[
           this.mShowCompletedTasks = val;
           return val;
         ]]></setter>
       </property>
+
+      <property name="textFilterField">
+        <getter><![CDATA[
+          return this.mTextFilterField;
+        ]]></getter>
+        <setter><![CDATA[
+          this.mTextFilterField = val;
+          return val;
+        ]]></setter>
+      </property>
+
       <method name="duration">
         <parameter name="aTask"/>
         <body><![CDATA[
           if (aTask && aTask.dueDate && aTask.dueDate.isValid){
               let dur = aTask.dueDate.subtractDate(cal.now());
               if (!dur.isNegative) {
                   let minutes = Math.ceil(dur.inSeconds / 60);
                   if (minutes >= 1440) { // 1 day or more
@@ -1135,16 +1147,17 @@
           let initialDate = currentView().selectedDay;
           return initialDate ? initialDate : now();
         ]]></body>
       </method>
 
       <method name="updateFilter">
         <parameter name="aFilter"/>
         <body><![CDATA[
+            this.mFilter.textFilterField = this.mTextFilterField;
             this.mFilter.propertyFilter = aFilter || this.mFilter.propertyFilter || "all";
             this.mDateRangeFilter = aFilter || this.mDateRangeFilter;
             if (this.mDateRangeFilter) {
                 this.mFilter.setDateFilter(this.mDateRangeFilter);
             } else {
                 let oneDay = cal.createDuration();
                 oneDay.days = 1;
                 this.mFilter.startDate = cal.createDateTime();
--- a/calendar/base/content/calendar-task-view.js
+++ b/calendar/base/content/calendar-task-view.js
@@ -17,16 +17,17 @@
  * Portions created by the Initial Developer are Copyright (C) 2006
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Michael Buettner <michael.buettner@sun.com>
  *   Philipp Kewisch <mozilla@kewis.ch>
  *   Berend Cornelius <berend.cornelius@sun.com>
  *   Fred Jendrzejewski <fred.jen@web.de>
+ *   Matthew Mecca <matthew.mecca@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -217,22 +218,31 @@ var taskDetailsView = {
     },
 };
 
 
 /**
  * Updates the currently applied filter for the task view and refreshes the task
  * tree.
  *
- * @param filter        The filter name to set.
+ * @param aFilter        The filter name to set.
  */
-function taskViewUpdate(filter) {
-    document.getElementById("filterBroadcaster").setAttribute("value", filter);
+function taskViewUpdate(aFilter) {
+    let tree = document.getElementById("calendar-task-tree");
+    let broadcaster = document.getElementById("filterBroadcaster");
+    let oldFilter = broadcaster.getAttribute("value");
+    let filter = oldFilter;
 
-    var tree = document.getElementById("calendar-task-tree");
+    if (aFilter && !(aFilter instanceof Event)) {
+        filter = aFilter;
+    }
+
+    if (filter && (filter != oldFilter)) {
+        broadcaster.setAttribute("value", filter);
+    }
 
     // update the filter
     tree.updateFilter(filter || "all");
 }
 
 /**
  * Prepares a dialog to send an email to the organizer of the currently selected
  * task in the task view.
@@ -270,34 +280,52 @@ function sendMailToOrganizer() {
 /**
  * Handler function to observe changing of the calendar display deck. Updates
  * the task tree if the task view was selected.
  *
  * TODO Consolidate this function and anything connected, its still from times
  * before we had view tabs.
  */
 function taskViewObserveDisplayDeckChange(event) {
-    var deck = event.target;
+    let deck = event.target;
 
     // Bug 309505: The 'select' event also fires when we change the selected
     // panel of calendar-view-box.  Workaround with this check.
     if (deck.id != "calendarDisplayDeck") {
         return;
     }
 
-    var id = null;
+    let id = null;
     try {
-      id = deck.selectedPanel.id
+        id = deck.selectedPanel.id;
     }
     catch (e) {}
 
     // In case we find that the task view has been made visible, we refresh the view.
     if (id == "calendar-task-box") {
         taskViewUpdate(
             document.getElementById("task-tree-filtergroup").value || "all");
     }
 }
 
-// Install event listeners for the display deck change.
-window.addEventListener("load", function () {
-  document.getElementById("calendarDisplayDeck").
-    addEventListener("select", taskViewObserveDisplayDeckChange, true);
-  }, false);
+// Install event listeners for the display deck change and connect task tree to filter field
+function taskViewOnLoad() {
+    let deck = document.getElementById("calendarDisplayDeck");
+    let tree = document.getElementById("calendar-task-tree");
+
+    if (deck && tree) {
+        deck.addEventListener("select", taskViewObserveDisplayDeckChange, true);
+        tree.textFilterField = "task-text-filter-field";
+
+        // setup the platform-dependent placeholder for the text filter field
+        let textFilter = document.getElementById("task-text-filter-field");
+        if (textFilter) {
+            let base = textFilter.getAttribute("emptytextbase");
+            let keyLabel = textFilter.getAttribute(Application.platformIsMac ?
+                                                   "keyLabelMac" : "keyLabelNonMac");
+
+            textFilter.setAttribute("placeholder", base.replace("#1", keyLabel));
+            textFilter.value = "";
+        }
+    }
+}
+
+window.addEventListener("load", taskViewOnLoad, false);
--- a/calendar/base/content/calendar-task-view.xul
+++ b/calendar/base/content/calendar-task-view.xul
@@ -56,26 +56,36 @@
   <script type="application/javascript" src="chrome://calendar/content/calApplicationUtils.js"/>
   <script type="application/javascript" src="chrome://calendar/content/calFilter.js"/>
   <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
 
   <vbox id="calendarDisplayDeck">
     <vbox id="calendar-task-box" flex="1"
           onselect="taskDetailsView.onSelect(event);">
       <hbox id="task-addition-box" align="center">
-        <toolbarbutton id="calendar-add-task-button"
-                       label="&calendar.newtask.button.label;"
-                       tooltiptext="&calendar.newtask.button.tooltip;"
-                       observes="calendar_new_todo_command"/>
-        <textbox id="view-task-edit-field"
-                 flex="1"
-                 class="task-edit-field"
-                 onfocus="taskEdit.onFocus(event)"
-                 onblur="taskEdit.onBlur(event)"
-                 onkeypress="taskEdit.onKeyPress(event)"/>
+        <box align="center" flex="1">
+          <toolbarbutton id="calendar-add-task-button"
+                         label="&calendar.newtask.button.label;"
+                         tooltiptext="&calendar.newtask.button.tooltip;"
+                         observes="calendar_new_todo_command"/>
+          <textbox id="view-task-edit-field"
+                   flex="1"
+                   class="task-edit-field"
+                   onfocus="taskEdit.onFocus(event)"
+                   onblur="taskEdit.onBlur(event)"
+                   onkeypress="taskEdit.onKeyPress(event)"/>
+        </box>
+        <box align="center" flex="1">
+          <textbox id="task-text-filter-field" type="search" flex="1"
+                   placeholder=""
+                   emptytextbase="&calendar.task.text-filter.textbox.emptytext.base;"
+                   keyLabelNonMac="&calendar.task.text-filter.textbox.emptytext.keylabel.nonmac;"
+                   keyLabelMac="&calendar.task.text-filter.textbox.emptytext.keylabel.mac;"
+                   oncommand="taskViewUpdate();"/>
+        </box>
       </hbox>
       <vbox flex="1">
         <calendar-task-tree id="calendar-task-tree" flex="1"
                             visible-columns="completed priority title startdate duedate"
                             persist="visible-columns ordinals widths sort-active sort-direction height"
                             context="taskitem-context-menu"/>
         <splitter id="calendar-task-view-splitter" collapse="none" persist="state" class="calendar-splitter"/>
         <vbox id="calendar-task-details-container"
--- a/calendar/base/src/calFilter.js
+++ b/calendar/base/src/calFilter.js
@@ -150,18 +150,21 @@ calFilter.prototype = {
     },
 
     set selectedDate(aSelectedDate) {
         return (this.mSelectedDate = aSelectedDate);
     },
 
     // checks if the item contains the text of mTextFilterField
     textFilter: function cF_filterByText(aItem) {
+        if (!this.mTextFilterField) {
+            return true;
+        }
         filterByText.mTextFilterField = this.mTextFilterField;
-        var inIt = filterByText(aItem);
+        let inIt = filterByText(aItem);
         return inIt;
     },
 
     get propertyFilter() {
         if(!this.mPropertyFilter) {
             this.mPropertyFilter = filterAll;
         }
 
@@ -203,19 +206,20 @@ calFilter.prototype = {
       return [this.mStartdate, this.mEndDate];
     },
 
     // checks if the item is between startDate and endDate
     isItemWithinRange: function cF_isDateWithinRange(aItem) {
         return checkIfInRange(aItem, this.mStartDate, this.mEndDate);
     },
 
-    // checks if the item is between startDate and endDate and its properties
+    // checks if the item is between startDate and endDate, matches its properties, and
+    // contains the text of mTextFilterField if set
     isItemInFilters: function cF_isItemInFilters(aItem) {
-        return (this.isItemWithinRange(aItem) && this.propertyFilter(aItem));
+        return (this.isItemWithinRange(aItem) && this.propertyFilter(aItem) && this.textFilter(aItem));
     }
 };
 
 /**
  * @param aFilter         a String describing the filter, it should met a regEx to call 
  *                        duration from filter otherwise a costumized filter is called
  * @param aSelectedDate   Optional - the selected date to use for filters that require it.
  *                        The selected day of the current view will be used by default.
--- a/calendar/base/themes/gnomestripe/calendar-task-view.css
+++ b/calendar/base/themes/gnomestripe/calendar-task-view.css
@@ -127,16 +127,20 @@
 #view-task-edit-field {
     margin: 5px;
 }
 
 .task-edit-field[readonly="true"] {
     color: GrayText;
 }
 
+#task-text-filter-field {
+    margin: 5px;
+}
+
 #calendar-task-details-title {
     font-weight: bold;
 }
 
 #unifinder-task-edit-field {
     margin: 3px;
 }
 
--- a/calendar/base/themes/pinstripe/calendar-task-view.css
+++ b/calendar/base/themes/pinstripe/calendar-task-view.css
@@ -118,16 +118,20 @@
 #view-task-edit-field {
     margin: 5px;
 }
 
 .task-edit-field[readonly="true"] {
     color: GrayText;
 }
 
+#task-text-filter-field {
+    margin: 4px 5px;
+}
+
 #calendar-task-details-title {
     font-weight: bold;
 }
 
 #unifinder-task-edit-field {
     margin: 3px;
 }
 
--- a/calendar/base/themes/winstripe/calendar-task-view.css
+++ b/calendar/base/themes/winstripe/calendar-task-view.css
@@ -130,16 +130,24 @@
 #view-task-edit-field {
     margin: 5px;
 }
 
 .task-edit-field[readonly="true"] {
     color: GrayText;
 }
 
+#task-text-filter-field {
+    margin: 5px;
+}
+
+#task-text-filter-field .textbox-search-icons {
+    margin-bottom: -1px;
+}
+
 #calendar-task-details-title {
     font-weight: bold;
 }
 
 #unifinder-task-edit-field {
     margin: 3px;
 }
 
--- a/calendar/base/themes/winstripe/win-aero/calendar.css
+++ b/calendar/base/themes/winstripe/win-aero/calendar.css
@@ -60,26 +60,28 @@
 }
 
 #todaypane-new-event-button {
   list-style-image: url(chrome://calendar/skin/toolbar-small-aero.png);
 }
 
 #unifinder-search-field,
 #view-task-edit-field,
+#task-text-filter-field,
 #unifinder-task-edit-field {
   -moz-appearance: none;
   background-clip: padding-box;
   border: 1px solid ThreeDDarkShadow;
   border-radius: 3.5px;
 }
 
 @media all and (-moz-windows-default-theme) {
   #unifinder-search-field,
   #view-task-edit-field,
+  #task-text-filter-field,
   #unifinder-task-edit-field {
     border-color: rgba(0, 0, 0, .32);
   }
 
   #bottom-events-box > #unifinder-searchBox,
   #task-addition-box {
     background-color: #f8f8f8;
   }
--- a/calendar/locales/en-US/chrome/calendar/calendar.dtd
+++ b/calendar/locales/en-US/chrome/calendar/calendar.dtd
@@ -174,16 +174,20 @@
 <!ENTITY calendar.task-details.attachments.label     "attachments">
 <!ENTITY calendar.task-details.start.label           "start date">
 <!ENTITY calendar.task-details.due.label             "due date">
 
 <!ENTITY calendar.task.category.button.tooltip  "Categorize tasks">
 <!ENTITY calendar.task.complete.button.tooltip  "Mark selected tasks completed">
 <!ENTITY calendar.task.priority.button.tooltip  "Change the priority">
 
+<!ENTITY calendar.task.text-filter.textbox.emptytext.base            "Filter tasks… #1">
+<!ENTITY calendar.task.text-filter.textbox.emptytext.keylabel.nonmac "&lt;Ctrl+Shift+K&gt;">
+<!ENTITY calendar.task.text-filter.textbox.emptytext.keylabel.mac    "&lt;&#x21E7;&#x2318;K&gt;">
+
 <!-- Statusbar -->
 <!ENTITY statusText.label            "Document: Done">
 
 <!-- Context Menu -->
 <!ENTITY calendar.context.modifyorviewitem.label      "Open">
 <!ENTITY calendar.context.modifyorviewitem.accesskey  "O">
 <!ENTITY calendar.context.modifyorviewtask.label      "Open Task…">
 <!ENTITY calendar.context.modifyorviewtask.accesskey  "O">