Bug 455733 - Consolidate filter mechanisms in different views and trees; r=berend
authorFred Jendrzejewski <fred.jen@web.de>
Thu, 09 Oct 2008 18:39:17 +0200
changeset 568 cffc7b7eb6cfaf2131374d1fd2a54ac5374bd948
parent 567 b79b9a6e329bf65190b4fa02ae75fbf4673ad180
child 569 a3db346b2e2a95a44961fb254cfbc15e69bf4aee
push idunknown
push userunknown
push dateunknown
reviewersberend
bugs455733
Bug 455733 - Consolidate filter mechanisms in different views and trees; r=berend
calendar/base/content/calendar-task-tree.xml
calendar/base/content/calendar-task-view.js
calendar/base/content/calendar-task-view.xul
calendar/base/content/calendar-unifinder-todo.xul
calendar/base/content/calendar-unifinder.js
calendar/base/content/calendar-unifinder.xul
calendar/base/jar.mn
calendar/base/src/Makefile.in
calendar/base/src/calFilter.js
--- a/calendar/base/content/calendar-task-tree.xml
+++ b/calendar/base/content/calendar-task-tree.xml
@@ -140,17 +140,17 @@
     <implementation>
       
       <field name="mLoadCount">0</field>
       <field name="mTaskArray">[]</field>
       <field name="mHash2Index"><![CDATA[({})]]></field>
       <field name="mRefreshQueue">[]</field>
       <field name="mPendingRefresh">null</field>
       <field name="mShowCompletedTasks">true</field>
-      <field name="mFilterFunction">null</field>
+      <field name="mFilter">null</field>
       <field name="mStartDate">null</field>
       <field name="mEndDate">null</field>
 
       <property name="currentIndex">
         <getter><![CDATA[
           var tree = document.getAnonymousElementByAttribute(
               this, "anonid", "calendar-task-tree");
           return tree.currentIndex;
@@ -193,44 +193,16 @@
         <getter><![CDATA[
           return this.mShowCompletedTasks;
         ]]></getter>
         <setter><![CDATA[
           this.mShowCompletedTasks = val;
           return val;
         ]]></setter>
       </property>
-      
-      <property name="filterFunction">
-        <setter><![CDATA[
-          this.mFilterFunction = val;
-          return val;
-        ]]></setter>
-      </property>
-      
-      <property name="startDate">
-        <getter><![CDATA[
-          return this.mStartDate;
-        ]]></getter>
-        <setter><![CDATA[
-          this.mStartDate = val;
-          return val;
-        ]]></setter>
-      </property>
-
-      <property name="endDate">
-        <getter><![CDATA[
-          return this.mEndDate;
-        ]]></getter>
-        <setter><![CDATA[
-          this.mEndDate = val;
-          return val;
-        ]]></setter>
-      </property>
-      
       <method name="duration">
         <parameter name="aTask"/>
         <body><![CDATA[
           if (aTask && aTask.dueDate && aTask.dueDate.isValid){
               var dur = aTask.dueDate.subtractDate(now());
               if (!dur.isNegative) {
                   // TODO MOZILLA_1_8_BRANCH We should use Pluralform.jsm on
                   // trunk here to provide better localization. Use different
@@ -351,18 +323,17 @@
 
            modifyItem: function tTV_modifyItem(aNewItem, aOldItem, aDontSort) {
               var index = this.binding.mHash2Index[aOldItem.hashId];
               if (index != undefined) {
                   // if a filter is installed we need to make sure that
                   // the item still belongs to the set of valid items before
                   // moving forward. if the filter cuts this item off, we
                   // need to act accordingly.
-                  if (this.binding.mFilterFunction &&
-                      !this.binding.mFilterFunction(aNewItem)) {
+                  if (!this.binding.mFilter.isItemInFilters(aNewItem)) {
                       this.removeItem(aNewItem);
                       return;
                   }
                   // same holds true for the completed filter, which is
                   // currently modeled as an explicit boolean.
                   if (aNewItem.isCompleted != aOldItem.isCompleted) {
                       if (aNewItem.isCompleted && !this.binding.showCompleted) {
                           this.removeItem(aNewItem);
@@ -750,18 +721,20 @@
           
           onLoad: function tTO_onLoad() {
               if (!this.mInBatch) {
                   this.binding.refresh();
               }
           },
           
           onAddItem: function tTO_onAddItem(aItem) {
-              // XXX: we have to use a filter here
-              if (isToDo(aItem) && !this.mInBatch) {
+              // XXX: We have to filter here
+              if (isToDo(aItem)  &&
+                  !this.mInBatch &&
+                  this.binding.mFilter.isItemInFilters(aItem)) {
                   this.binding.mTreeView.addItem(aItem);
               }
           },
           
           onModifyItem: function tTO_onModifyItem(aNewItem, aOldItem) {
               if ((isToDo(aNewItem) || isToDo(aOldItem)) &&
                   !this.mInBatch) {
                   
@@ -866,16 +839,19 @@
             }
         }
       ]]></constructor>
 
       <method name="onLoad">
         <body><![CDATA[
           if (!this.mLoadCount++) {
 
+              // set up the tree filter
+              this.mFilter = new calFilter();
+
               // set up the custom tree view
               var tree = document.getAnonymousElementByAttribute(this, "anonid", "calendar-task-tree");
               this.mTreeView.tree = tree;
               tree.view = this.mTreeView;
           
               // set up our calendar event observer
               var composite = getCompositeCalendar();
               composite.addObserver(this.mTaskTreeObserver);
@@ -921,46 +897,50 @@
           }
         ]]></body>
       </method>
 
       <!-- Called by event observers to update the display -->
       <method name="refresh">
         <parameter name="aFilter"/>
         <body><![CDATA[
+          // XXX: why do I need this ?
+          if(!this.mFilter) {
+            this.mFilter = new calFilter();
+          }
           var savedThis = this;
           var refreshListener = {
               mTaskArray: [],
 
               onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDateTime) {
-                  savedThis.mTaskArray = this.mTaskArray;
+              savedThis.mTaskArray = this.mTaskArray;
                   savedThis.onOperationComplete();
              },
 
              onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
-                 for (var i=0; i<aCount; i++) {
-                     if (!savedThis.mFilterFunction ||
-                         savedThis.mFilterFunction(aItems[i])) {
+             for (var i=0; i<aCount; i++) {
+                     if (savedThis.mFilter.isItemInFilters(aItems[i])) {
                          refreshListener.mTaskArray.push(aItems[i]);
                      }
                  }
              }
           };
 
           var refreshJob = {
               execute: function() {
                   var composite = getCompositeCalendar();
                   var filter = aFilter || !savedThis.mShowCompletedTasks ?
                       composite.ITEM_FILTER_COMPLETED_NO :
                       composite.ITEM_FILTER_COMPLETED_ALL;
                   filter |= composite.ITEM_FILTER_TYPE_TODO;
-                  if (savedThis.startDate && savedThis.endDate) {
+
+                  if (savedThis.mFilter.startDate && savedThis.mFilter.endDate) {
                       filter |= composite.ITEM_FILTER_CLASS_OCCURRENCES;
                   }
-                  composite.getItems(filter, 0, savedThis.startDate, savedThis.endDate, refreshListener);
+                  composite.getItems(filter, 0, savedThis.mFilter.startDate, savedThis.mFilter.endDate, refreshListener);
               }
           };
           this.mRefreshQueue.push(refreshJob);
           this.popRefreshQueue();
         ]]></body>
       </method>
 
       <method name="onCalendarAdded">
@@ -970,35 +950,34 @@
           var savedThis = this;
           var refreshListener = {
               onOperationComplete: function (aCalendar, aStatus, aOperationType, aId, aDateTime) {
                   savedThis.onOperationComplete();
              },
 
              onGetResult: function (aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
                  for (var i=0; i<aCount; i++) {
-                     if (!savedThis.mFilterFunction ||
-                         savedThis.mFilterFunction(aItems[i])) {
+                   if (savedThis.mFilter.isItemInFilters(aItems[i])) {
                          savedThis.mTaskArray.push(aItems[i]);
                      }
                  }
              }
           };
           
           var refreshJob = {
               execute: function() {
                   var composite = getCompositeCalendar();
                   var filter = aFilter || !savedThis.mShowCompletedTasks ?
                       composite.ITEM_FILTER_COMPLETED_NO :
                       composite.ITEM_FILTER_COMPLETED_ALL;
                   filter |= composite.ITEM_FILTER_TYPE_TODO;
-                  if (savedThis.startDate && savedThis.endDate) {
+                  if (savedThis.mFilter.startDate && savedThis.mFilter.endDate) {
                       filter |= composite.ITEM_FILTER_CLASS_OCCURRENCES;
                   }
-                  aCalendar.getItems(filter, 0, savedThis.startDate, savedThis.endDate, refreshListener);
+                  aCalendar.getItems(filter, 0, savedThis.mFilter.startDate, savedThis.mFilter.endDate, refreshListener);
               }
           };
           this.mRefreshQueue.push(refreshJob);
           this.popRefreshQueue();
         ]]></body>
       </method>
       
       <method name="onCalendarRemoved">
--- a/calendar/base/content/calendar-task-view.js
+++ b/calendar/base/content/calendar-task-view.js
@@ -16,16 +16,17 @@
  * The Initial Developer of the Original Code is Sun Microsystems.
  * 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>
  *
  * 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
@@ -160,79 +161,23 @@ var taskDetailsView = {
                 urlLabel.setAttribute("tooltiptext", gURL);
             }
         }
     }
 };
 
 function taskViewUpdate(filter) {
     document.getElementById("filterBroadcaster").setAttribute("value", filter);
-    var percentCompleted = function(item) {
-        var percent = 0;
-        var property = item.getProperty("PERCENT-COMPLETE");
-        if (property != null) {
-            var percent = parseInt(property);
-        }
-        return percent;
-    }
-
-    var filterFunctions = {
-        notstarted: function filterNotStarted(item) {
-            return (percentCompleted(item) <= 0);
-        },
-        overdue: function filterOverdue(item) {
-          // in case the item has no due date
-          // it can't be overdue by definition
-          if (item.dueDate == null) {
-              return false;
-          }
-          return (percentCompleted(item) < 100) &&
-                 !(item.dueDate.compare(now()) > 0);
-        },
-        open: function filterCompleted(item) {
-            return (percentCompleted(item) < 100);
-        },
-        completed: function filterCompleted(item) {
-            return (percentCompleted(item) >= 100);
-        }
-    }
 
     var tree = document.getElementById("calendar-task-tree");
-    tree.filterFunction = filterFunctions[filter] || null;
 
-    var todayDate = new Date();
-    var startDate = new Date(todayDate.getFullYear(),
-                             todayDate.getMonth(),
-                             todayDate.getDate(),
-                             0, 0, 0);
-
-    var rangeFunctions = {
-        today: function rangeToday() {
-            tree.startDate = jsDateToDateTime(startDate)
-                .getInTimezone(calendarDefaultTimezone());
-            tree.endDate = jsDateToDateTime(
-                new Date(startDate.getTime() + (1000 * 60 * 60 * 24) - 1))
-                    .getInTimezone(calendarDefaultTimezone());
-        },
-        next7days: function rangeNext7Days() {
-            tree.startDate = jsDateToDateTime(startDate)
-                .getInTimezone(calendarDefaultTimezone());
-            tree.endDate = jsDateToDateTime(
-                new Date(startDate.getTime() + (1000 * 60 * 60 * 24 * 8)))
-                    .getInTimezone(calendarDefaultTimezone());
-        }
-    }
+    // set the filters
+    tree.mFilter.propertyFilter  = filter;
+    tree.mFilter.setDateFilter(filter);
     
-    if (rangeFunctions[filter]) {
-      rangeFunctions[filter]();
-    } else {
-      tree.startDate = null;
-      tree.endDate = null;
-    }
-
     tree.refresh();
 }
 
 function sendMailToOrganizer() {
     var item = document.getElementById("calendar-task-tree").currentTask;
     if (item != null) {
         var organizer = item.organizer;
         if (organizer) {
--- a/calendar/base/content/calendar-task-view.xul
+++ b/calendar/base/content/calendar-task-view.xul
@@ -46,16 +46,17 @@
   <!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/sun-calendar-event-dialog.dtd" > %dtd2;
 ]>
 
 <overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script type="application/javascript" src="chrome://calendar/content/calendar-task-tree.js"/>
   <script type="application/javascript" src="chrome://calendar/content/calendar-task-view.js"/>
   <script type="application/javascript" src="chrome://calendar/content/calendar-dialog-utils.js"/>
   <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);">
       <textbox id="view-task-edit-field"
                class="task-edit-field"
                onfocus="taskEdit.onFocus(event)"
--- a/calendar/base/content/calendar-unifinder-todo.xul
+++ b/calendar/base/content/calendar-unifinder-todo.xul
@@ -46,17 +46,17 @@
 ]>
 
 <?xml-stylesheet type="text/css" href="chrome://calendar/content/calendar-bindings.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/skin/calendar-task-view.css"?>
 
 <overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript" src="chrome://calendar/content/calendar-task-tree.js"/>
-  <script type="application/javascript" src="chrome://calendar/content/calendar-unifinder-todo.js"/>
+  <script type="application/javascript" src="chrome://calendar/content/calFilter.js"/>  <script type="application/javascript" src="chrome://calendar/content/calendar-unifinder-todo.js"/>
   <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
   
   <vbox id="todo-tab-panel" persist="height,collapsed" flex="1">
     <box id="todo-label" align="left" collapsed="true">
       <label flex="1" crop="end" style="font-weight: bold" value="&calendar.unifinder.todoitems.label;" control="unifinder-todo-tree"/>
     </box>
     <box align="center">
       <checkbox id="show-completed-checkbox"
--- a/calendar/base/content/calendar-unifinder.js
+++ b/calendar/base/content/calendar-unifinder.js
@@ -26,16 +26,17 @@
  *   ArentJan Banck <ajbanck@planet.nl>
  *   Eric Belhaire <eric.belhaire@ief.u-psud.fr>
  *   Matthew Willis <mattwillis@gmail.com>
  *   Michiel van Leeuwen <mvl@exedo.nl>
  *   Joey Minta <jminta@gmail.com>
  *   Dan Mosedale <dan.mosedale@oracle.com>
  *   Michael Buettner <michael.buettner@sun.com>
  *   Philipp Kewisch <mozilla@kewis.ch>
+ *   Fred Jendrzejewski <fred.jen@web.de>
  *
  * 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
@@ -56,36 +57,24 @@
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 
 // Set this to true when the calendar event tree is clicked to allow for
 // multiple selection
 var gCalendarEventTreeClicked = false;
 
 // Store the start and enddate, because the providers can't be trusted when
 // dealing with all-day events. So we need to filter later. See bug 306157
-var gStartDate;
-var gEndDate;
 
 var kDefaultTimezone;
 var gUnifinderNeedsRefresh = true;
 
 function isUnifinderHidden() {
     return document.getElementById("bottom-events-box").hidden;
 }
 
-// Extra check to see if the events are in the daterange. Some providers
-// are broken when looking at all-day events.
-function fixAlldayDates(aItem) {
-    // Using .compare on the views start and end, not on the events dates,
-    // because .compare uses the timezone of the datetime it is called on.
-    // The view's timezone is what is important here.
-    return ((!gEndDate || gEndDate.compare(aItem.startDate) >= 0) &&
-            (!gStartDate || gStartDate.compare(aItem.endDate) < 0));
-}
-
 function getCurrentUnifinderFilter() {
     return document.getElementById("event-filter-menulist").selectedItem.value;
 }
 
 /**
  * Observer for the calendar event data source. This keeps the unifinder
  * display up to date when the calendar event data is changed
  */
@@ -126,17 +115,18 @@ var unifinderObserver = {
             refreshEventTree();
         }
     },
 
     onAddItem: function uO_onAddItem(aItem) {
         if (isEvent(aItem) &&
             !this.mInBatch &&
             !gUnifinderNeedsRefresh &&
-            isItemInFilter(aItem)) {
+            unifinderTreeView.mFilter.isItemInFilters(aItem)
+            ) {
             this.addItemToTree(aItem);
         }
     },
 
     onModifyItem: function uO_onModifyItem(aNewItem, aOldItem) {
         this.onDeleteItem(aOldItem);
         this.onAddItem(aNewItem);
     },
@@ -146,31 +136,35 @@ var unifinderObserver = {
             this.removeItemFromTree(aDeletedItem);
         }
     },
 
     // It is safe to call these for any event.  The functions will determine
     // whether or not anything actually needs to be done to the tree
     addItemToTree: function uO_addItemToTree(aItem) {
         var items;
-        if (gStartDate && gEndDate) {
-            items = aItem.getOccurrencesBetween(gStartDate, gEndDate, {});
+        var filter = unifinderTreeView.mFilter;
+
+        if (filter.startDate && filter.endDate) {
+            items = aItem.getOccurrencesBetween(filter.startDate, filter.endDate, {});
         } else {
             items = [aItem];
         }
-        unifinderTreeView.addItems(items.filter(fixAlldayDates));
+        unifinderTreeView.addItems(items.filter(filter.isItemInFilters, filter));
     },
     removeItemFromTree: function uO_removeItemFromTree(aItem) {
         var items;
-        if (gStartDate && gEndDate && (aItem.parentItem == aItem)) {
-            items = aItem.getOccurrencesBetween(gStartDate, gEndDate, {});
+        var filter = unifinderTreeView.mFilter;
+        if (filter.startDate && filter.endDate && (aItem.parentItem == aItem)) {
+            items = aItem.getOccurrencesBetween(filter.startDate, filter.endDate, {});
         } else {
             items = [aItem];
         }
-        unifinderTreeView.removeItems(items.filter(fixAlldayDates));
+        // XXX: do we really still need this, we are always checking it in the refreshInternal
+        unifinderTreeView.removeItems(items.filter(filter.isItemInFilters, filter));
     },
 
     onError: function uO_onError(aCalendar, aErrNo, aMessage) {},
 
     onPropertyChanged: function uO_onPropertyChanged(aCalendar, aName, aValue, aOldValue) {
         switch (aName) {
             case "disabled":
                 refreshEventTree();
@@ -210,16 +204,20 @@ function prepareCalendarUnifinder() {
     // Check if this is not the hidden window, which has no UI elements
     if (unifinderTree) {
         // set up our calendar event observer
         var ccalendar = getCompositeCalendar();
         ccalendar.addObserver(unifinderObserver);
 
         kDefaultTimezone = calendarDefaultTimezone();
 
+        // Set up the filter
+        unifinderTreeView.mFilter = new calFilter();
+        unifinderTreeView.mFilter.propertyFilter = "unifinder-search-field";
+
         // Set up the unifinder views.
         unifinderTreeView.treeElement = unifinderTree;
         unifinderTree.view = unifinderTreeView;
 
         // Listen for changes in the selected day, so we can update if need be
         var viewDeck = getViewDeck();
         viewDeck.addEventListener("dayselect", unifinderDaySelect, false);
         viewDeck.addEventListener("itemselect", unifinderItemSelect, true);
@@ -400,16 +398,18 @@ function unifinderKeyPress(aEvent) {
 /**
  * Tree controller for unifinder search results
  */
 var unifinderTreeView = {
 
     tree: null,
     treeElement: null,
     doingSelection: false,
+    mFilter: null,
+    
 
     mSelectedColumn: null,
     sortDirection: null,
 
     get selectedColumn uTV_getSelectedColumn() {
         return this.mSelectedColumn;
     },
 
@@ -788,124 +788,48 @@ function refreshEventTree() {
 
 
     var ccalendar = getCompositeCalendar();
     var filter = 0;
 
     filter |= ccalendar.ITEM_FILTER_TYPE_EVENT;
 
     // Not all xul might be there yet...
-    if (!document.getElementById("event-filter-menulist")) {
+    if (!document.getElementById(unifinderTreeView.mFilter.textFilterField)) {
         return;
     }
-    var [StartDate, EndDate] = getDatesForFilter(getCurrentUnifinderFilter());
+    unifinderTreeView.mFilter.setDateFilter(getCurrentUnifinderFilter());
 
-    gStartDate = StartDate  ? jsDateToDateTime(StartDate, calendarDefaultTimezone()) : null;
-    gEndDate = EndDate ? jsDateToDateTime(EndDate, calendarDefaultTimezone()) : null;
-    if (StartDate && EndDate) {
+    if (unifinderTreeView.mFilter.startDate && unifinderTreeView.mFilter.endDate) {
         filter |= ccalendar.ITEM_FILTER_CLASS_OCCURRENCES;
     }
 
-    ccalendar.getItems(filter, 0, gStartDate, gEndDate, refreshListener);
+    ccalendar.getItems(filter, 0, unifinderTreeView.mFilter.startDate, unifinderTreeView.mFilter.endDate, refreshListener);
 }
 
 /**
  * Get the dates for a certain filter. This function makes it easy to extend the
  * unifinder. To add a new view, just overwrite this function with your own. Be
  * sure to call this function afterwards though.
  */
-function getDatesForFilter(aFilter) {
-    var Today = new Date();
-    // Do this to allow all day events to show up all day long.
-    var StartDate = new Date(Today.getFullYear(),
-                             Today.getMonth(),
-                             Today.getDate(),
-                             0, 0, 0);
-    var EndDate;
-    switch (aFilter) {
-        case "all":
-            StartDate = null;
-            EndDate = null;
-            break;
-
-        case "today":
-            EndDate = new Date(StartDate.getTime() + (1000 * 60 * 60 * 24) - 1);
-            break;
-
-        case "next7Days":
-            EndDate = new Date(StartDate.getTime() + (1000 * 60 * 60 * 24 * 8));
-            break;
-
-        case "next14Days":
-            EndDate = new Date(StartDate.getTime() + (1000 * 60 * 60 * 24 * 15));
-            break;
-
-        case "next31Days":
-            EndDate = new Date(StartDate.getTime() + (1000 * 60 * 60 * 24 * 32));
-            break;
-
-        case "thisCalendarMonth":
-            // midnight on first day of this month
-            var startOfMonth = new Date(Today.getFullYear(), Today.getMonth(), 1, 0, 0, 0);
-            // midnight on first day of next month
-            var startOfNextMonth = new Date(Today.getFullYear(), (Today.getMonth() + 1), 1, 0, 0, 0);
-            // 23:59:59 on last day of this month
-            EndDate = new Date(startOfNextMonth.getTime() - 1000);
-            StartDate = startOfMonth;
-            break;
-
-        case "future":
-            EndDate = null;
-            break;
-
-        case "current":
-            var SelectedDate = currentView().selectedDay.jsDate;
-            StartDate = new Date(SelectedDate.getFullYear(), SelectedDate.getMonth(), SelectedDate.getDate(), 0, 0, 0);
-            EndDate = new Date(StartDate.getTime() + (1000 * 60 * 60 * 24) - 1000);
-            break;
-    }
-    return [StartDate, EndDate];
-}
 
 function refreshEventTreeInternal(eventArray) {
-    var searchText = document.getElementById("unifinder-search-field").value;
 
-    unifinderTreeView.setItems(eventArray.filter(isItemInFilter));
+    unifinderTreeView.setItems(eventArray.filter(unifinderTreeView
+                                                .mFilter
+                                                .isItemInFilters
+                                                , unifinderTreeView
+                                                .mFilter
+                                                ));
 
     // Select selected events in the tree. Not passing the argument gets the
     // items from the view.
     unifinderTreeView.setSelectedItems();
 }
 
-function isItemInFilter(aItem) {
-    var searchText = document.getElementById("unifinder-search-field")
-                             .value.toLowerCase();
-
-    if (!searchText.length || searchText.match(/^\s*$/)) {
-        return true;
-    }
-
-    const fieldsToSearch = ["SUMMARY", "DESCRIPTION", "LOCATION", "URL"];
-    if (!fixAlldayDates(aItem)) {
-        return false;
-    }
-
-    for each (var field in fieldsToSearch) {
-        var val = aItem.getProperty(field);
-        if (val && val.toLowerCase().indexOf(searchText) != -1) {
-            return true;
-        }
-    }
-
-    return aItem.getCategories({}).some(
-        function someFunc(cat) {
-            return (cat.toLowerCase().indexOf(searchText) != -1);
-        });
-}
-
 function focusSearch() {
     document.getElementById("unifinder-search-field").focus();
 }
 
 function toggleUnifinder() {
     // Toggle the elements
     goToggleToolbar('bottom-events-box', 'calendar_show_unifinder_command');
     goToggleToolbar('calendar-view-splitter');
--- a/calendar/base/content/calendar-unifinder.xul
+++ b/calendar/base/content/calendar-unifinder.xul
@@ -55,33 +55,33 @@
 <overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script type="application/javascript" src="chrome://calendar/content/calendar-unifinder.js"/>
   <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
 
   <vbox id="calendar-view-box">
     <vbox id="bottom-events-box" insertbefore="view-deck" persist="height">
       <hbox id="unifinder-searchBox" persist="collapsed">
         <box align="center">
-          <menulist id="event-filter-menulist" value="next7Days" persist="value">
+          <menulist id="event-filter-menulist" value="next7days" persist="value">
             <menupopup id="event-filter-menupopup" oncommand="refreshEventTree()">
               <menuitem id="event-filter-all"
                         label="&calendar.events.filter.all.label;"
                         value="all"/>
               <menuitem id="event-filter-today"
                         label="&calendar.events.filter.today.label;"
                         value="today"/>
-              <menuitem id="event-filter-next7Days"
+              <menuitem id="event-filter-next7days"
                         label="&calendar.events.filter.next7Days.label;"
-                        value="next7Days"/>
+                        value="next7days"/>
               <menuitem id="event-filter-next14Days"
                         label="&calendar.events.filter.next14Days.label;"
-                        value="next14Days"/>
+                        value="next14days"/>
               <menuitem id="event-filter-next31Days"
                         label="&calendar.events.filter.next31Days.label;"
-                        value="next31Days"/>
+                        value="next31days"/>
               <menuitem id="event-filter-thisCalendarMonth"
                         label="&calendar.events.filter.thisCalendarMonth.label;"
                         value="thisCalendarMonth"/>
               <menuitem id="event-filter-future"
                         label="&calendar.events.filter.future.label;"
                         value="future"/>
               <menuitem id="event-filter-current"
                         label="&calendar.events.filter.current.label;"
--- a/calendar/base/jar.mn
+++ b/calendar/base/jar.mn
@@ -55,16 +55,17 @@ calendar.jar:
     content/calendar/widgets/minimonth.xml     (content/widgets/minimonth.xml)
     content/calendar/widgets/calendar-widget-bindings.css    (content/widgets/calendar-widget-bindings.css)
     content/calendar/calendar-minimonth-busy.js  (content/calendar-minimonth-busy.js)
     content/calendar/calendar-view-bindings.css  (content/calendar-view-bindings.css)
     content/calendar/calendar-view-core.xml      (content/calendar-view-core.xml)
     content/calendar/calendar-views.js           (content/calendar-views.js)
 *   content/calendar/calApplicationUtils.js      (src/calApplicationUtils.js)
     content/calendar/calUtils.js                 (src/calUtils.js)
+    content/calendar/calFilter.js                 (src/calFilter.js)
     content/calendar/Windows98ToZoneInfoTZId.properties (src/Windows98ToZoneInfoTZId.properties)
     content/calendar/WindowsNTToZoneInfoTZId.properties (src/WindowsNTToZoneInfoTZId.properties)
     content/calendar/calErrorPrompt.xul          (content/calErrorPrompt.xul)
     content/calendar/chooseCalendarDialog.xul    (content/chooseCalendarDialog.xul)
     content/calendar/import-export.js            (content/import-export.js)
 *   content/calendar/migration.js                (content/migration.js)
     content/calendar/migration.xul               (content/migration.xul)
     content/calendar/widgets/calendar-widgets.xml  (content/widgets/calendar-widgets.xml)
--- a/calendar/base/src/Makefile.in
+++ b/calendar/base/src/Makefile.in
@@ -84,16 +84,17 @@ EXTRA_SCRIPTS = \
     calAlarmService.js \
     calAlarmMonitor.js \
     calAttachment.js \
     calAttendee.js \
     calCalendarManager.js \
     calCachedCalendar.js \
     calDateTimeFormatter.js \
     calEvent.js \
+    calFilter.js \
     calIcsParser.js \
     calIcsSerializer.js \
     calItemBase.js \
     calItipItem.js \
     calItipProcessor.js \
     calProtocolHandler.js \
     calRecurrenceInfo.js \
     calRelation.js \
new file mode 100644
--- /dev/null
+++ b/calendar/base/src/calFilter.js
@@ -0,0 +1,342 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Calendar code.
+ * 
+ * The Initial Developer of the Original Code is
+ *   Fred Jendrzejewski <fred.jen@web.de>
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * 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
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * This object gives you the possibility to filter items for its properties.
+ * It has the following properties:
+ *
+ *
+ * private Attributes:
+ *  mStartDate
+ *  mEndDate            - the limits for the item to be in the filter
+ *  mPropertyFilter     - a boolean function to filter items in a costumized way
+ *  mPropertyFilterBag  - an object with a number of predefined filterFunctions
+ *  mTextFilterField    - the id of a text - field that is associated with the filter
+ *
+ *  setting up the dateFilter:
+ *  setDateFilter       - this function takes a string and sets the start and endDate of
+ *                        the filter by using the getDatesForFilter function, please look
+ *                        on the documentation for this function for further informations
+ *
+ * setting up the propertyFilter:
+ *  you can associate a textFilter, a built-in filter or a custom function with the 
+ *  propertyFilter. It is always set up by using calFilter.propertyFilter = aFilter
+ *    1.) Use a built-in filter:
+ *        - aFilter is a string 
+ *        - the string has to be the name of one of the members of mPropertyFilterBag
+ *        the propertyFilter is set to this built-in function
+ *    2.) Use a text filter:
+ *        - aFilter is a string
+ *        - the string is a id of a text-field
+ *        the propertyFilter will always filter out by regular expression items whose
+ *        properties contain the value of the text-field
+ *    3.) Use a costumized filter:
+ *        - aFilter is a function
+ *        propertyFilter is set this function
+ * 
+ * using the filter:
+ * isItemInFilters        - this takes an item and returns the result of
+ *                          checkIfInRange and propertyFilter
+ */
+
+function calFilter() {
+  this.wrappedJSObject = this;
+}
+
+calFilter.prototype = {
+    mStartDate: null,
+    mEndDate: null,
+    mTextFilterField: null,
+    mPropertyFilter: filterAll,
+
+    // a number of prefined Filters for properties
+    mPropertyFilterBag: { 
+        all: filterAll,
+        notstarted: function cF_filterNotStarted(item) {
+            return (percentCompleted(item) <= 0);
+        },
+        overdue: function cF_filterOverdue(item) {
+            // in case the item has no due date
+            // it can't be overdue by definition
+            if (item.dueDate == null) {
+                return false;
+            }
+            return (percentCompleted(item) < 100) &&
+            !(item.dueDate.compare(now()) > 0);
+        },
+        open: function cF_filterCompleted(item) {
+            return (percentCompleted(item) < 100);
+        },
+        completed: function cF_filterCompleted(item) {
+            return (percentCompleted(item) >= 100);
+        }
+    },
+
+    get startDate cF_get_startDate() {
+        return this.mStartDate;
+    },
+
+    set startDate cF_set_startDate(aStartDate) {
+        return (this.mStartDate = aStartDate);
+    },
+
+    get endDate cF_get_endDate() {
+        return this.mEndDate;
+    },
+
+    set endDate cF_set_endDate(aEndDate) {
+        return (this.mEndDate = aEndDate);
+    },    
+
+    set textFilterField cF_setTextFilterField(aId) {
+        return (this.mTextFilterField = aId);
+    },
+
+    get textFilterField cF_getTextFilterField() {
+        return this.mTextFilterField;
+    },
+
+    // checks if the item contains the text of mTextFilterField
+    textFilter: function cF_filterByText(aItem) {
+        filterByText.mTextFilterField = this.mTextFilterField;
+        var inIt = filterByText(aItem);
+        return inIt;
+    },
+
+    get propertyFilter cF_get_propertyFilter() {
+        if(!this.mPropertyFilter) {
+            this.mPropertyFilter = filterAll;
+        }
+
+        return this.mPropertyFilter;
+    },
+
+    /*
+     * @param aFilter if -  aFilter is a string, propertyFilter is textFilter
+     *                      if aFilter has to be the id of the textFilterField then
+     *                      if aFilter is the number of a number of predefined 
+     *                          propertyFilters, propertyFilters is set to be this
+     *                   -  aFilter is a function, the propertyFilter is set to be this
+     *                      function
+     */
+    set propertyFilter cF_set_propertyFilter(aFilter) {
+        if (typeof(aFilter) == "string") {
+            // check if it is one of the build in filters
+            if (this.mPropertyFilterBag[aFilter]) {
+                return (this.mPropertyFilter = this.mPropertyFilterBag[aFilter]);
+            }
+            // check if the aFilter is the id of an item, otherwise 
+            // return set the filter to all
+            if (document.getElementById(aFilter)) {
+                this.mTextFilterField = aFilter;
+                return (this.mPropertyFilter = this.textFilter);
+            }
+        } else if (typeof(aFilter) == "function") {
+          return (this.mPropertyFilter = aFilter);
+        }  
+        
+        return (this.mPropertyFilter = filterAll);
+    },
+
+    // set's the startDate and the endDate by using getDatesForFilter 
+    setDateFilter: function cF_setDateFilter(aFilter) {
+      var [startDate, endDate] = getDatesForFilter(aFilter);
+      this.mStartDate = startDate;
+      this.mEndDate = endDate;
+      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
+    isItemInFilters: function cF_isItemInFilters(aItem) {
+        return (this.isItemWithinRange(aItem) && this.propertyFilter(aItem));
+    }
+};
+
+/**
+ * @param aFilter a String describing the filter, it should met a regEx to call 
+ *                duration from filter otherwise a costumized filter is called
+ * @return        [startDate, endDate]
+ */
+
+function getDatesForFilter(aFilter) {
+    var EndDate = createDateTime();
+    var StartDate = createDateTime();
+    var Duration = createDuration();
+    var oneDay = createDuration();
+    oneDay.days = 1;
+
+    var durFilterReg = /next|last\d+\D+$/
+    if (durFilterReg.exec(aFilter)) {
+        Duration =  durationFromFilter(aFilter);
+        if (!Duration) {
+          EndDate = null;
+          StartDate = null;
+        } else {
+          StartDate = now();
+          EndDate = now();
+          EndDate.addDuration(Duration);
+        }
+    } else {
+      switch (aFilter) {
+        case "all":
+            StartDate = null;
+            EndDate = null;
+            break;
+
+        case "today":
+            StartDate = now();
+            StartDate.isDate = true;
+            EndDate = now();
+            EndDate.isDate = true;
+            EndDate.addDuration(oneDay);
+            break;
+
+        case "thisCalendarMonth":
+            StartDate = now().startOfMonth;
+            EndDate = now().endOfMonth;
+            EndDate.addDuration(oneDay);
+            break;
+
+        case "future":
+            StartDate = now();
+            EndDate = null;
+            break;
+
+        case "current":
+            var SelectedDate = currentView().selectedDay;
+            StartDate = SelectedDate.clone();
+            StartDate.isDate = true;
+            EndDate = StartDate.clone();
+            EndDate.addDuration(oneDay);
+            break;
+        default:
+            StartDate = null;
+            EndDate = null;
+            break;
+      }
+    }
+    return [StartDate, EndDate];
+}
+
+/**
+ * Functions to create a duration based on a filter string.
+ * 
+ * @param aFilter the filter string, it has has to match the pattern
+ *                [last|next] Period [UnitOfThePeriod]
+ * @return        the duration or null
+ */
+function durationFromFilter(aFilter) {
+  var durReg = /(last|next)/
+  var periodReg = /(\d+)/
+  var unitReg = /\d+(\D+)$/
+  try {
+    // Create the direction of the duration
+    var modifier = (durReg.exec(aFilter)[1] == "next") ? 1 : -1;
+
+    // Get the numerical value
+    var period = periodReg.exec(aFilter)[1];
+
+    //Get the unit
+    var duration = createDuration();
+    switch( unitReg.exec(aFilter)[1]) {
+      case "weeks": 
+        duration.weeks = modifier*period;
+        break;
+      case "days": 
+        duration.days = modifier*period;
+        break;
+      case "hours": 
+        duration.hours = modifier*period;
+        break;
+      default:
+        return null;
+    }
+    return duration;
+  } catch(e) {
+    dump(e);
+    return null;
+  }
+}
+
+/**
+ * @param aItem             Is a normal calIItemBase
+ * @param mTextFilterField  Has to be set from the outside of the function
+ * @return                  Filters if the item is contains the searchText
+ */
+function filterByText(aItem) {
+    var searchText = document.getElementById(filterByText.mTextFilterField)
+                             .value.toLowerCase();
+
+    if (!searchText.length || searchText.match(/^\s*$/)) {
+        return true;
+    }
+
+    const fieldsToSearch = ["SUMMARY", "DESCRIPTION", "LOCATION", "URL"];
+    if (!fixAlldayDates(aItem)) {
+        return false;
+    }
+
+    for each (var field in fieldsToSearch) {
+        var val = aItem.getProperty(field);
+        if (val && val.toLowerCase().indexOf(searchText) != -1) {
+            return true;
+        }
+    }
+
+    return aItem.getCategories({}).some(
+        function someFunc(cat) {
+            return (cat.toLowerCase().indexOf(searchText) != -1);
+        });
+}
+
+function percentCompleted(item) {
+    var percent = 0;
+    var property = item.getProperty("PERCENT-COMPLETE");
+    if (property != null) {
+        var percent = parseInt(property);
+    }
+    return percent;
+} 
+
+function filterAll(aItem) {
+    return true;
+}
+