[gnome-documents/wip/rishi/split-view: 8/9] Rework the application accommodate multiple views



commit 4c7ffba09ac512c2814d4518cc2a63b61f942334
Author: Debarshi Ray <debarshir gnome org>
Date:   Fri Oct 17 13:52:47 2014 +0200

    Rework the application accommodate multiple views
    
    The objective is to have separate views for documents, collections and
    search results. The first two views will always show only documents and
    collections arranged in reverse chronological order. Whenever search
    constraints are applied the results are supposed to show up in the
    search view.
    
    Each view will have its separate TrackerController and OffsetController
    implementations to decide when to refresh its contents and what the
    current offsets are. The OffsetController is no longer a part of the
    search context because the different views need to use their own
    implementations. The search provider does not have a way to change the
    offsets, so it can just use the initial value of [0, 50).
    
    New flags corresponding to each of the views have been added to
    QueryFlags. These are passed around to the classes that play a part in
    constructing the queries.
    
    The meaning of the NONE and UNFILTERED flags have also changed.
    Earlier, UNFILTERED was used to completely turn off search categories,
    search matches, search types and collections, while NONE had them
    enabled. Now, NONE will disable the search type WHERE clauses but keep
    everything else enabled so that it can be used to create queries for
    browsing the contents of a collection.
    
    Since the getFilter method of the various BaseManager classes are now
    affected by their roles in the individual views, it no longer makes
    sense to have a generic implementation in the parent. Instead,
    getAllFilter has been made public so that the children can use it to
    construct their query snippets.
    
    ViewModel has grown a filtering mechanism to allow each view to only
    have what is needed.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=686461

 src/application.js         |   52 +++++++++----
 src/documents.js           |    1 +
 src/embed.js               |  178 ++++++++++++++++++++++++++++++++++++++------
 src/mainWindow.js          |    8 ++-
 src/manager.js             |   14 +---
 src/query.js               |   47 ++++++++----
 src/search.js              |   92 ++++++++++++++++++++---
 src/shellSearchProvider.js |    3 +-
 src/trackerController.js   |   70 ++++++++++++++++--
 src/view.js                |  143 +++++++++++++++++++++++++++--------
 src/windowMode.js          |    6 +-
 11 files changed, 494 insertions(+), 120 deletions(-)
---
diff --git a/src/application.js b/src/application.js
index e5ee0ec..02e8627 100644
--- a/src/application.js
+++ b/src/application.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2012 Red Hat, Inc.
+ * Copyright (c) 2011, 2012, 2014 Red Hat, Inc.
  *
  * Gnome Documents is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by the
@@ -77,7 +77,9 @@ let cssProvider = null;
 let documentManager = null;
 let modeController = null;
 let notificationManager = null;
-let offsetController = null;
+let offsetCollectionsController = null;
+let offsetDocumentsController = null;
+let offsetSearchController = null;
 let queryBuilder = null;
 let searchCategoryManager = null;
 let searchController = null;
@@ -85,7 +87,9 @@ let searchMatchManager = null;
 let searchTypeManager = null;
 let selectionController = null;
 let sourceManager = null;
-let trackerController = null;
+let trackerCollectionsController = null;
+let trackerDocumentsController = null;
+let trackerSearchController = null;
 
 const TrackerExtractPriorityIface = '<node> \
 <interface name="org.freedesktop.Tracker1.Extract.Priority"> \
@@ -488,7 +492,12 @@ const Application = new Lang.Class({
         Search.initSearch(imports.shellSearchProvider);
 
         modeController = new WindowMode.ModeController();
-        trackerController = new TrackerController.TrackerOverviewController();
+        offsetCollectionsController = new Search.OffsetCollectionsController();
+        offsetDocumentsController = new Search.OffsetDocumentsController();
+        offsetSearchController = new Search.OffsetSearchController();
+        trackerCollectionsController = new TrackerController.TrackerCollectionsController();
+        trackerDocumentsController = new TrackerController.TrackerDocumentsController();
+        trackerSearchController = new TrackerController.TrackerSearchController();
         selectionController = new Selections.SelectionController();
 
         this._actionEntries = [
@@ -518,7 +527,9 @@ const Application = new Lang.Class({
               create_hook: this._viewAsCreateHook,
               parameter_type: 's',
               state: settings.get_value('view-as'),
-              window_mode: WindowMode.WindowMode.OVERVIEW },
+              window_modes: [WindowMode.WindowMode.COLLECTIONS,
+                             WindowMode.WindowMode.DOCUMENTS,
+                             WindowMode.WindowMode.SEARCH] },
             { name: 'open-current',
               callback: this._onActionOpenCurrent,
               window_mode: WindowMode.WindowMode.PREVIEW },
@@ -550,9 +561,13 @@ const Application = new Lang.Class({
             { name: 'rotate-right', accel: '<Primary>Right',
               window_mode: WindowMode.WindowMode.PREVIEW },
             { name: 'select-all', accel: '<Primary>a',
-              window_mode: WindowMode.WindowMode.OVERVIEW },
+              window_modes: [WindowMode.WindowMode.COLLECTIONS,
+                             WindowMode.WindowMode.DOCUMENTS,
+                             WindowMode.WindowMode.SEARCH] },
             { name: 'select-none',
-              window_mode: WindowMode.WindowMode.OVERVIEW },
+              window_modes: [WindowMode.WindowMode.COLLECTIONS,
+                             WindowMode.WindowMode.DOCUMENTS,
+                             WindowMode.WindowMode.SEARCH] },
             { name: 'properties',
               callback: this._onActionProperties,
               window_mode: WindowMode.WindowMode.PREVIEW },
@@ -570,15 +585,21 @@ const Application = new Lang.Class({
             { name: 'search-source',
               parameter_type: 's',
               state: GLib.Variant.new('s', Search.SearchSourceStock.ALL),
-              window_mode: WindowMode.WindowMode.OVERVIEW },
+              window_modes: [WindowMode.WindowMode.COLLECTIONS,
+                             WindowMode.WindowMode.DOCUMENTS,
+                             WindowMode.WindowMode.SEARCH] },
             { name: 'search-type',
               parameter_type: 's',
               state: GLib.Variant.new('s', Search.SearchTypeStock.ALL),
-              window_mode: WindowMode.WindowMode.OVERVIEW },
+              window_modes: [WindowMode.WindowMode.COLLECTIONS,
+                             WindowMode.WindowMode.DOCUMENTS,
+                             WindowMode.WindowMode.SEARCH] },
             { name: 'search-match',
               parameter_type: 's',
               state: GLib.Variant.new('s', Search.SearchMatchStock.ALL),
-              window_mode: WindowMode.WindowMode.OVERVIEW }
+              window_modes: [WindowMode.WindowMode.COLLECTIONS,
+                             WindowMode.WindowMode.DOCUMENTS,
+                             WindowMode.WindowMode.SEARCH] }
         ];
 
         this.gdataMiner = new Miners.GDataMiner();
@@ -628,7 +649,7 @@ const Application = new Lang.Class({
     vfunc_activate: function() {
         if (!this._mainWindow) {
             this._createWindow();
-            modeController.setWindowMode(WindowMode.WindowMode.OVERVIEW);
+            modeController.setWindowMode(WindowMode.WindowMode.DOCUMENTS);
         }
 
         this._mainWindow.window.present_with_time(this._activationTimestamp);
@@ -639,7 +660,9 @@ const Application = new Lang.Class({
         // clean up signals
         changeMonitor.disconnectAll();
         documentManager.disconnectAll();
-        trackerController.disconnectAll();
+        trackerCollectionsController.disconnectAll();
+        trackerDocumentsController.disconnectAll();
+        trackerSearchController.disconnectAll();
         selectionController.disconnectAll();
         modeController.disconnectAll();
         this.disconnectAllJS();
@@ -692,7 +715,8 @@ const Application = new Lang.Class({
             // forward the search terms next time we enter the overview
             let modeChangeId = modeController.connect('window-mode-changed', Lang.bind(this,
                 function(object, newMode) {
-                    if (newMode != WindowMode.WindowMode.OVERVIEW)
+                    if (newMode == WindowMode.WindowMode.EDIT
+                        || newMode == WindowMode.WindowMode.PREVIEW)
                         return;
 
                     modeController.disconnect(modeChangeId);
@@ -705,7 +729,7 @@ const Application = new Lang.Class({
 
     _onLaunchSearch: function(provider, terms, timestamp) {
         this._createWindow();
-        modeController.setWindowMode(WindowMode.WindowMode.OVERVIEW);
+        modeController.setWindowMode(WindowMode.WindowMode.DOCUMENTS);
         searchController.setString(terms.join(' '));
         this.change_action_state('search', GLib.Variant.new('b', true));
 
diff --git a/src/documents.js b/src/documents.js
index 7ac12a5..b40a068 100644
--- a/src/documents.js
+++ b/src/documents.js
@@ -241,6 +241,7 @@ const DocCommon = new Lang.Class({
         this.typeDescription = null;
         this.sourceName = null;
 
+        this.rowRefs = {};
         this.shared = false;
 
         this.collection = false;
diff --git a/src/embed.js b/src/embed.js
index 41aa8c4..7c07428 100644
--- a/src/embed.js
+++ b/src/embed.js
@@ -28,6 +28,7 @@ const Notifications = imports.notifications;
 const Password = imports.password;
 const Preview = imports.preview;
 const Edit = imports.edit;
+const Search = imports.search;
 const Selections = imports.selections;
 const View = imports.view;
 const WindowMode = imports.windowMode;
@@ -104,8 +105,14 @@ const Embed = new Lang.Class({
         this._stackOverlay.add_overlay(Application.notificationManager.widget);
 
         // now create the actual content widgets
-        this._view = new View.ViewContainer();
-        this._stack.add_named(this._view.widget, 'view');
+        this._documents = new View.ViewContainer(WindowMode.WindowMode.DOCUMENTS);
+        this._stack.add_titled(this._documents.widget, 'documents', _("Recent"));
+
+        this._collections = new View.ViewContainer(WindowMode.WindowMode.COLLECTIONS);
+        this._stack.add_titled(this._collections.widget, 'collections', _("Collections"));
+
+        this._search = new View.ViewContainer(WindowMode.WindowMode.SEARCH);
+        this._stack.add_named(this._search.widget, 'search');
 
         this._preview = new Preview.PreviewView(this._stackOverlay);
         this._stack.add_named(this._preview.widget, 'preview');
@@ -116,13 +123,16 @@ const Embed = new Lang.Class({
         this._spinnerBox = new SpinnerBox();
         this._stack.add_named(this._spinnerBox.widget, 'spinner');
 
+        this._stack.connect('notify::visible-child',
+                            Lang.bind(this, this._onVisibleChildChanged));
+
         Application.modeController.connect('window-mode-changed',
                                            Lang.bind(this, this._onWindowModeChanged));
 
         Application.modeController.connect('fullscreen-changed',
                                            Lang.bind(this, this._onFullscreenChanged));
-        Application.trackerController.connect('query-status-changed',
-                                              Lang.bind(this, this._onQueryStatusChanged));
+        Application.trackerDocumentsController.connect('query-status-changed',
+                                                       Lang.bind(this, this._onQueryStatusChanged));
 
         Application.documentManager.connect('active-changed',
                                             Lang.bind(this, this._onActiveItemChanged));
@@ -135,6 +145,14 @@ const Embed = new Lang.Class({
         Application.documentManager.connect('password-needed',
                                             Lang.bind(this, this._onPasswordNeeded));
 
+        Application.searchTypeManager.connect('active-changed',
+                                              Lang.bind(this, this._onSearchChanged));
+        Application.sourceManager.connect('active-changed',
+                                          Lang.bind(this, this._onSearchChanged));
+
+        Application.searchController.connect('search-string-changed',
+                                             Lang.bind(this, this._onSearchChanged));
+
         this._onQueryStatusChanged();
 
         let windowMode = Application.modeController.getWindowMode();
@@ -142,28 +160,70 @@ const Embed = new Lang.Class({
             this._onWindowModeChanged(Application.modeController, windowMode, WindowMode.WindowMode.NONE);
     },
 
+    _getViewFromMode: function(windowMode) {
+        let view;
+
+        switch (windowMode) {
+        case WindowMode.WindowMode.COLLECTIONS:
+            view = this._collections;
+            break;
+        case WindowMode.WindowMode.DOCUMENTS:
+            view = this._documents;
+            break;
+        case WindowMode.WindowMode.PREVIEW:
+            view = this._preview;
+            break;
+        case WindowMode.WindowMode.SEARCH:
+            view = this._search;
+            break;
+        default:
+            throw(new Error('Not handled'));
+            break;
+        }
+
+        return view;
+    },
+
     _onActivateResult: function() {
         let windowMode = Application.modeController.getWindowMode();
-
-        if (windowMode == WindowMode.WindowMode.OVERVIEW)
-            this._view.activateResult();
-        else if (windowMode == WindowMode.WindowMode.PREVIEW)
-            this._preview.activateResult();
+        let view = this._getViewFromMode(mode);
+        view.activateResult();
     },
 
-    _onQueryStatusChanged: function() {
+    _restoreLastPage: function() {
+        let page;
         let windowMode = Application.modeController.getWindowMode();
-        if (windowMode != WindowMode.WindowMode.OVERVIEW)
-            return;
 
-        let queryStatus = Application.trackerController.getQueryStatus();
+        switch (windowMode) {
+        case WindowMode.WindowMode.COLLECTIONS:
+            page = 'collections';
+            break;
+        case WindowMode.WindowMode.DOCUMENTS:
+            page = 'documents';
+            break;
+        case WindowMode.WindowMode.PREVIEW:
+            page = 'preview';
+            break;
+        case WindowMode.WindowMode.SEARCH:
+            page = 'search';
+            break;
+        default:
+            throw(new Error('Not handled'));
+            break;
+        }
+
+        this._stack.set_visible_child_name(page);
+    },
+
+    _onQueryStatusChanged: function() {
+        let queryStatus = Application.trackerDocumentsController.getQueryStatus();
 
         if (queryStatus) {
             this._spinnerBox.start();
             this._stack.set_visible_child_name('spinner');
         } else {
             this._spinnerBox.stop();
-            this._stack.set_visible_child_name('view');
+            this._restoreLastPage();
         }
     },
 
@@ -172,10 +232,58 @@ const Embed = new Lang.Class({
         this._toolbar.widget.sensitive = !fullscreen;
     },
 
+    _onSearchChanged: function() {
+        // Whenever a search constraint is specified we want to switch to
+        // the search mode, and when all constraints have been lifted we
+        // want to go back to the previous mode which can be either
+        // collections or documents.
+        //
+        // However there are some exceptions, which are taken care of
+        // elsewhere:
+        //  - when moving from search to preview or collection view
+        //  - when in preview or coming out of it
+
+        let doc = Application.documentManager.getActiveItem();
+        let windowMode = Application.modeController.getWindowMode();
+        if (windowMode == WindowMode.WindowMode.SEARCH && doc)
+            return;
+        if (windowMode == WindowMode.WindowMode.PREVIEW)
+            return;
+
+        let searchType = Application.searchTypeManager.getActiveItem();
+        let source = Application.sourceManager.getActiveItem();
+        let str = Application.searchController.getString();
+
+        if (searchType.id == Search.SearchTypeStock.ALL &&
+            source.id == Search.SearchSourceStock.ALL &&
+            (!str || str == '')) {
+            Application.modeController.goBack();
+        } else {
+            Application.modeController.setWindowMode(WindowMode.WindowMode.SEARCH);
+        }
+    },
+
+    _onVisibleChildChanged: function() {
+        let visibleChild = this._stack.visible_child;
+        let windowMode = WindowMode.WindowMode.NONE;
+
+        if (visibleChild == this._collections.widget)
+            windowMode = WindowMode.WindowMode.COLLECTIONS;
+        else if (visibleChild == this._documents.widget)
+            windowMode = WindowMode.WindowMode.DOCUMENTS;
+
+        if (windowMode == WindowMode.WindowMode.NONE)
+            return;
+
+        Application.modeController.setWindowMode(windowMode);
+    },
+
     _onWindowModeChanged: function(object, newMode, oldMode) {
         switch (newMode) {
-        case WindowMode.WindowMode.OVERVIEW:
-            this._prepareForOverview();
+        case WindowMode.WindowMode.COLLECTIONS:
+        case WindowMode.WindowMode.DOCUMENTS:
+        case WindowMode.WindowMode.SEARCH:
+            this._prepareForOverview(newMode, oldMode);
             break;
         case WindowMode.WindowMode.PREVIEW:
             if (oldMode == WindowMode.WindowMode.EDIT)
@@ -255,20 +363,44 @@ const Embed = new Lang.Class({
             }));
     },
 
-    _prepareForOverview: function() {
+    _prepareForOverview: function(newMode, oldMode) {
+        let createToolbar = (oldMode != WindowMode.WindowMode.COLLECTIONS &&
+                             oldMode != WindowMode.WindowMode.DOCUMENTS &&
+                             oldMode != WindowMode.WindowMode.SEARCH);
+
+        let visibleChildName;
+
+        switch (newMode) {
+        case WindowMode.WindowMode.COLLECTIONS:
+            visibleChildName = 'collections';
+            break;
+        case WindowMode.WindowMode.DOCUMENTS:
+            visibleChildName = 'documents';
+            break;
+        case WindowMode.WindowMode.SEARCH:
+            visibleChildName = 'search';
+            break;
+        default:
+            throw(new Error('Not handled'));
+            break;
+        }
+
         if (this._preview)
             this._preview.reset();
         if (this._edit)
             this._edit.setUri(null);
-        if (this._toolbar)
-            this._toolbar.widget.destroy();
 
-        // pack the toolbar
-        this._toolbar = new MainToolbar.OverviewToolbar(this._stackOverlay, this._stack);
-        this._titlebar.add(this._toolbar.widget);
+        if (createToolbar) {
+            if (this._toolbar)
+                this._toolbar.widget.destroy();
+
+            // pack the toolbar
+            this._toolbar = new MainToolbar.OverviewToolbar(this._stackOverlay, this._stack);
+            this._titlebar.add(this._toolbar.widget);
+        }
 
         this._spinnerBox.stop();
-        this._stack.set_visible_child_name('view');
+        this._stack.set_visible_child_name(visibleChildName);
     },
 
     _prepareForPreview: function() {
diff --git a/src/mainWindow.js b/src/mainWindow.js
index e1ac236..1c4f80e 100644
--- a/src/mainWindow.js
+++ b/src/mainWindow.js
@@ -154,6 +154,7 @@ const MainWindow = new Lang.Class({
 
         switch (windowMode) {
         case WindowMode.WindowMode.NONE:
+        case WindowMode.WindowMode.DOCUMENTS:
             handled = false;
             break;
         case WindowMode.WindowMode.EDIT:
@@ -161,7 +162,8 @@ const MainWindow = new Lang.Class({
             Application.documentManager.setActiveItem(null);
             Application.modeController.goBack();
             break;
-        case WindowMode.WindowMode.OVERVIEW:
+        case WindowMode.WindowMode.COLLECTIONS:
+        case WindowMode.WindowMode.SEARCH:
             if (activeCollection)
                 Application.documentManager.activatePreviousCollection();
             break;
@@ -200,7 +202,9 @@ const MainWindow = new Lang.Class({
             return false;
         case WindowMode.WindowMode.PREVIEW:
             return this._handleKeyPreview(event);
-        case WindowMode.WindowMode.OVERVIEW:
+        case WindowMode.WindowMode.COLLECTIONS:
+        case WindowMode.WindowMode.DOCUMENTS:
+        case WindowMode.WindowMode.SEARCH:
             return this._handleKeyOverview(event);
         case WindowMode.WindowMode.EDIT:
             return false;
diff --git a/src/manager.js b/src/manager.js
index 03bbe84..9dac51b 100644
--- a/src/manager.js
+++ b/src/manager.js
@@ -124,16 +124,8 @@ const BaseManager = new Lang.Class({
         this.emit('clear');
     },
 
-    getFilter: function() {
-        let item = this.getActiveItem();
-        let retval = '';
-
-        if (item.id == 'all')
-            retval = this._getAllFilter();
-        else if (item && item.getFilter)
-            retval = item.getFilter();
-
-        return retval;
+    getFilter: function(flags) {
+        log('Error: BaseManager implementations must override getFilter');
     },
 
     getWhere: function() {
@@ -151,7 +143,7 @@ const BaseManager = new Lang.Class({
             func(this._items[idx]);
     },
 
-    _getAllFilter: function() {
+    getAllFilter: function() {
         let filters = [];
 
         this.forEachItem(function(item) {
diff --git a/src/query.js b/src/query.js
index 467a556..6543152 100644
--- a/src/query.js
+++ b/src/query.js
@@ -23,6 +23,7 @@ const GdPrivate = imports.gi.GdPrivate;
 const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
 const Lang = imports.lang;
+const Search = imports.search;
 
 const QueryColumns = {
     URN: 0,
@@ -41,7 +42,10 @@ const QueryColumns = {
 
 const QueryFlags = {
     NONE: 0,
-    UNFILTERED: 1 << 0
+    UNFILTERED: 1 << 0,
+    COLLECTIONS: 1 << 1,
+    DOCUMENTS: 1 << 2,
+    SEARCH: 1 << 3
 };
 
 const LOCAL_DOCUMENTS_COLLECTIONS_IDENTIFIER = 'gd:collection:local:';
@@ -59,12 +63,12 @@ const QueryBuilder = new Lang.Class({
                  activeSource: this._context.sourceManager.getActiveItem() };
     },
 
-    _buildFilterString: function(currentType) {
+    _buildFilterString: function(currentType, flags) {
         let filters = [];
 
-        filters.push(this._context.searchMatchManager.getFilter());
-        filters.push(this._context.sourceManager.getFilter());
-        filters.push(this._context.searchCategoryManager.getFilter());
+        filters.push(this._context.searchMatchManager.getFilter(flags));
+        filters.push(this._context.sourceManager.getFilter(flags));
+        filters.push(this._context.searchCategoryManager.getFilter(flags));
 
         if (currentType) {
             filters.push(currentType.getFilter());
@@ -86,10 +90,14 @@ const QueryBuilder = new Lang.Class({
         let whereParts = [];
         let searchTypes = [];
 
-        if (flags & QueryFlags.UNFILTERED)
-            searchTypes = this._context.searchTypeManager.getAllTypes();
-        else
+        if (flags & QueryFlags.COLLECTIONS)
+            searchTypes = [this._context.searchTypeManager.getItemById(Search.SearchTypeStock.COLLECTIONS)];
+        else if (flags & QueryFlags.DOCUMENTS)
+            searchTypes = this._context.searchTypeManager.getDocumentTypes();
+        else if (flags & QueryFlags.SEARCH)
             searchTypes = this._context.searchTypeManager.getCurrentTypes();
+        else
+            searchTypes = this._context.searchTypeManager.getAllTypes();
 
         // build an array of WHERE clauses; each clause maps to one
         // type of resource we're looking for.
@@ -102,7 +110,7 @@ const QueryBuilder = new Lang.Class({
                         part += this._context.searchCategoryManager.getWhere() +
                                 this._context.documentManager.getWhere();
 
-                    part += this._buildFilterString(currentType);
+                    part += this._buildFilterString(currentType, flags);
                 }
 
                 part += ' }';
@@ -116,16 +124,23 @@ const QueryBuilder = new Lang.Class({
         return whereSparql;
     },
 
-    _buildQueryInternal: function(global, flags) {
+    _buildQueryInternal: function(global, flags, offsetController) {
         let whereSparql = this._buildWhere(global, flags);
         let tailSparql = '';
 
         // order results by mtime
         if (global) {
+            let offset = 0;
+            let step = Search.OFFSET_STEP;
+
+            if (offsetController) {
+                offset = offsetController.getOffset();
+                step = offsetController.getOffsetStep();
+            }
+
             tailSparql +=
                 'ORDER BY DESC (?mtime)' +
-                ('LIMIT %d OFFSET %d').format(this._context.offsetController.getOffsetStep(),
-                                              this._context.offsetController.getOffset());
+                ('LIMIT %d OFFSET %d').format(step, offset);
         }
 
         let sparql =
@@ -153,13 +168,13 @@ const QueryBuilder = new Lang.Class({
         return this._createQuery(sparql);
     },
 
-    buildGlobalQuery: function() {
-        return this._createQuery(this._buildQueryInternal(true, QueryFlags.NONE));
+    buildGlobalQuery: function(flags, offsetController) {
+        return this._createQuery(this._buildQueryInternal(true, flags, offsetController));
     },
 
-    buildCountQuery: function() {
+    buildCountQuery: function(flags) {
         let sparql = 'SELECT DISTINCT COUNT(?urn) ' +
-            this._buildWhere(true, QueryFlags.NONE);
+            this._buildWhere(true, flags);
 
         return this._createQuery(sparql);
     },
diff --git a/src/search.js b/src/search.js
index af5c096..557ffeb 100644
--- a/src/search.js
+++ b/src/search.js
@@ -41,7 +41,6 @@ function initSearch(context) {
     context.searchMatchManager = new SearchMatchManager(context);
     context.searchTypeManager = new SearchTypeManager(context);
     context.searchController = new SearchController(context);
-    context.offsetController = new OffsetOverviewController(context);
     context.queryBuilder = new Query.QueryBuilder(context);
 };
 
@@ -138,6 +137,12 @@ const SearchCategoryManager = new Lang.Class({
         // this._categories[category.id] = category;
 
         this.setActiveItem(recent);
+    },
+
+    getFilter: function(flags) {
+        // Since we don't expose the SearchCategoryManager in the UI,
+        // this is a placeholder for the moment.
+        return '(true)';
     }
 });
 
@@ -320,7 +325,10 @@ const SearchMatchManager = new Lang.Class({
         this.setActiveItemById(SearchMatchStock.ALL);
     },
 
-    getFilter: function() {
+    getFilter: function(flags) {
+        if ((flags & Query.QueryFlags.SEARCH) == 0)
+            return '(true)';
+
         let terms = this.context.searchController.getTerms();
         let filters = [];
 
@@ -328,7 +336,16 @@ const SearchMatchManager = new Lang.Class({
             this.forEachItem(function(item) {
                 item.setFilterTerm(terms[i]);
             });
-            filters.push(this.parent());
+
+            let filter;
+            let item = this.getActiveItem();
+
+            if (item.id == SearchMatchStock.ALL)
+                filter = this.getAllFilter();
+            else
+                filter = item.getFilter();
+
+            filters.push(filter);
         }
         return filters.length ? '( ' + filters.join(' && ') + ')' : '(true)';
     }
@@ -507,6 +524,24 @@ const SourceManager = new Lang.Class({
         this.processNewItems(newItems);
     },
 
+    getFilter: function(flags) {
+        let item;
+
+        if (flags & Query.QueryFlags.SEARCH)
+            item = this.getActiveItem();
+        else
+            item = this.getItemById(SearchSourceStock.ALL);
+
+        let filter;
+
+        if (item.id == SearchSourceStock.ALL)
+            filter = this.getAllFilter();
+        else
+            filter = item.getFilter();
+
+        return filter;
+    },
+
     getFilterNotLocal: function() {
         let sources = this.getItems();
         let filters = [];
@@ -555,7 +590,7 @@ const SourceManager = new Lang.Class({
     }
 });
 
-const _OFFSET_STEP = 50;
+const OFFSET_STEP = 50;
 
 const OffsetController = new Lang.Class({
     Name: 'OffsetController',
@@ -567,7 +602,7 @@ const OffsetController = new Lang.Class({
 
     // to be called by the view
     increaseOffset: function() {
-        this._offset += _OFFSET_STEP;
+        this._offset += OFFSET_STEP;
         this.emit('offset-changed', this._offset);
     },
 
@@ -614,11 +649,11 @@ const OffsetController = new Lang.Class({
     },
 
     getRemainingDocs: function() {
-        return (this._itemCount - (this._offset + _OFFSET_STEP));
+        return (this._itemCount - (this._offset + OFFSET_STEP));
     },
 
     getOffsetStep: function() {
-        return _OFFSET_STEP;
+        return OFFSET_STEP;
     },
 
     getOffset: function() {
@@ -627,16 +662,49 @@ const OffsetController = new Lang.Class({
 });
 Signals.addSignalMethods(OffsetController.prototype);
 
-const OffsetOverviewController = new Lang.Class({
-    Name: 'OffsetOverviewController',
+const OffsetCollectionsController = new Lang.Class({
+    Name: 'OffsetCollectionsController',
     Extends: OffsetController,
 
-    _init: function(context) {
+    _init: function() {
+        this.parent();
+    },
+
+    getQuery: function() {
+        let activeCollection = Application.documentManager.getActiveCollection();
+        let flags;
+
+        if (activeCollection)
+            flags = Query.QueryFlags.NONE;
+        else
+            flags = Query.QueryFlags.COLLECTIONS;
+
+        return Application.queryBuilder.buildCountQuery(flags);
+    }
+});
+
+const OffsetDocumentsController = new Lang.Class({
+    Name: 'OffsetDocumentsController',
+    Extends: OffsetController,
+
+    _init: function() {
+        this.parent();
+    },
+
+    getQuery: function() {
+        return Application.queryBuilder.buildCountQuery(Query.QueryFlags.DOCUMENTS);
+    }
+});
+
+const OffsetSearchController = new Lang.Class({
+    Name: 'OffsetSearchController',
+    Extends: OffsetController,
+
+    _init: function() {
         this.parent();
-        this._context = context;
     },
 
     getQuery: function() {
-        return this._context.queryBuilder.buildCountQuery();
+        return Application.queryBuilder.buildCountQuery(Query.QueryFlags.SEARCH);
     }
 });
diff --git a/src/shellSearchProvider.js b/src/shellSearchProvider.js
index 2711714..6f77a24 100644
--- a/src/shellSearchProvider.js
+++ b/src/shellSearchProvider.js
@@ -39,7 +39,6 @@ const TrackerUtils = imports.trackerUtils;
 const Utils = imports.utils;
 
 let documentManager = null;
-let offsetController = null;
 let queryBuilder = null;
 let searchCategoryManager = null;
 let searchMatchManager = null;
@@ -319,7 +318,7 @@ const FetchIdsJob = new Lang.Class({
         this._cancellable = cancellable;
         searchController.setString(this._terms.join(' '));
 
-        let query = queryBuilder.buildGlobalQuery();
+        let query = queryBuilder.buildGlobalQuery(Query.QueryFlags.SEARCH, null);
         Application.connectionQueue.add(query.sparql, this._cancellable, Lang.bind(this,
             function(object, res) {
                 let cursor = null;
diff --git a/src/trackerController.js b/src/trackerController.js
index 38e374c..3a71a4c 100644
--- a/src/trackerController.js
+++ b/src/trackerController.js
@@ -281,15 +281,72 @@ const TrackerController = new Lang.Class({
 });
 Signals.addSignalMethods(TrackerController.prototype);
 
-const TrackerOverviewController = new Lang.Class({
-    Name: 'TrackerOverviewController',
+const TrackerCollectionsController = new Lang.Class({
+    Name: 'TrackerCollectionsController',
     Extends: TrackerController,
 
     _init: function() {
-        this.parent(WindowMode.WindowMode.OVERVIEW);
+        this.parent(WindowMode.WindowMode.COLLECTIONS);
+
+        Application.documentManager.connect('active-collection-changed', Lang.bind(this,
+            function() {
+                let windowMode = Application.modeController.getWindowMode();
+                if (windowMode == WindowMode.WindowMode.COLLECTIONS)
+                    this.refreshForObject();
+            }));
+    },
+
+    getOffsetController: function() {
+        return Application.offsetCollectionsController;
+    },
+
+    getQuery: function() {
+        let flags;
+        let activeCollection = Application.documentManager.getActiveCollection();
+
+        if (activeCollection)
+            flags = Query.QueryFlags.NONE;
+        else
+            flags = Query.QueryFlags.COLLECTIONS;
+
+        return Application.queryBuilder.buildGlobalQuery(flags,
+                                                         Application.offsetCollectionsController);
+    },
+});
+
+const TrackerDocumentsController = new Lang.Class({
+    Name: 'TrackerDocumentsController',
+    Extends: TrackerController,
+
+    _init: function() {
+        this.parent(WindowMode.WindowMode.DOCUMENTS);
+    },
+
+    getOffsetController: function() {
+        return Application.offsetDocumentsController;
+    },
+
+    getQuery: function() {
+        return Application.queryBuilder.buildGlobalQuery(Query.QueryFlags.DOCUMENTS,
+                                                         Application.offsetDocumentsController);
+    },
+});
+
+const TrackerSearchController = new Lang.Class({
+    Name: 'TrackerSearchController',
+    Extends: TrackerController,
+
+    _init: function() {
+        this.parent(WindowMode.WindowMode.SEARCH);
+
+        Application.documentManager.connect('active-collection-changed', Lang.bind(this,
+            function() {
+                let windowMode = Application.modeController.getWindowMode();
+                if (windowMode == WindowMode.WindowMode.SEARCH)
+                    this.refreshForObject();
+            }));
 
         Application.sourceManager.connect('active-changed', Lang.bind(this, this.refreshForObject));
-        Application.documentManager.connect('active-collection-changed', Lang.bind(this, 
this.refreshForObject));
         Application.searchController.connect('search-string-changed', Lang.bind(this, 
this.refreshForObject));
         Application.searchCategoryManager.connect('active-changed', Lang.bind(this, this.refreshForObject));
         Application.searchTypeManager.connect('active-changed', Lang.bind(this, this.refreshForObject));
@@ -305,10 +362,11 @@ const TrackerOverviewController = new Lang.Class({
     },
 
     getOffsetController: function() {
-        return Application.offsetController;
+        return Application.offsetSearchController;
     },
 
     getQuery: function() {
-        return Application.queryBuilder.buildGlobalQuery();
+        return Application.queryBuilder.buildGlobalQuery(Query.QueryFlags.SEARCH,
+                                                         Application.offsetSearchController);
     },
 });
diff --git a/src/view.js b/src/view.js
index 628e74b..8bb45b5 100644
--- a/src/view.js
+++ b/src/view.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Red Hat, Inc.
+ * Copyright (c) 2011, 2015 Red Hat, Inc.
  *
  * Gnome Documents is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by the
@@ -37,12 +37,37 @@ const TrackerUtils = imports.trackerUtils;
 const WindowMode = imports.windowMode;
 const Utils = imports.utils;
 
+function getController(windowMode) {
+    let offsetController;
+    let trackerController;
+
+    switch (windowMode) {
+    case WindowMode.WindowMode.COLLECTIONS:
+        offsetController = Application.offsetCollectionsController;
+        trackerController = Application.trackerCollectionsController;
+        break;
+    case WindowMode.WindowMode.DOCUMENTS:
+        offsetController = Application.offsetDocumentsController;
+        trackerController = Application.trackerDocumentsController;
+        break;
+    case WindowMode.WindowMode.SEARCH:
+        offsetController = Application.offsetSearchController;
+        trackerController = Application.trackerSearchController;
+        break;
+    default:
+        throw(new Error('Not handled'));
+        break;
+    }
+
+    return [ offsetController, trackerController ];
+}
+
 const _RESET_COUNT_TIMEOUT = 500; // msecs
 
 const ViewModel = new Lang.Class({
     Name: 'ViewModel',
 
-    _init: function() {
+    _init: function(windowMode) {
         this.model = Gtk.ListStore.new(
             [ GObject.TYPE_STRING,
               GObject.TYPE_STRING,
@@ -57,30 +82,34 @@ const ViewModel = new Lang.Class({
 
         this._resetCountId = 0;
 
+        this._mode = windowMode;
+        this._rowRefKey = "row-ref-" + this._mode;
+
         Application.documentManager.connect('item-added',
             Lang.bind(this, this._onItemAdded));
         Application.documentManager.connect('item-removed',
             Lang.bind(this, this._onItemRemoved));
 
-        Application.trackerController.connect('query-status-changed', Lang.bind(this,
+        [ this._offsetController, this._trackerController ] = getController(this._mode);
+        this._trackerController.connect('query-status-changed', Lang.bind(this,
             function(o, status) {
                 if (!status)
                     return;
                 this._clear();
             }));
+    },
 
-        // populate with the intial items
+    _clear: function() {
         let items = Application.documentManager.getItems();
         for (let idx in items) {
-            this._onItemAdded(null, items[idx]);
+            let doc = items[idx];
+            doc.rowRefs[this._rowRefKey] = null;
         }
-    },
 
-    _clear: function() {
         this.model.clear();
     },
 
-    _onItemAdded: function(source, doc) {
+    _addItem: function(doc) {
         // Update the count so that OffsetController has the correct
         // values. Otherwise things like loading more items and "No
         // Results" page will not work correctly.
@@ -94,23 +123,12 @@ const ViewModel = new Lang.Class({
 
         let treePath = this.model.get_path(iter);
         let treeRowRef = Gtk.TreeRowReference.new(this.model, treePath);
+        doc.rowRefs[this._rowRefKey] = treeRowRef;
 
-        doc.connect('info-updated', Lang.bind(this,
-            function() {
-                let objectPath = treeRowRef.get_path();
-                if (!objectPath)
-                    return;
-
-                let objectIter = this.model.get_iter(objectPath)[1];
-                if (objectIter)
-                    this.model.set(objectIter,
-                        [ 0, 1, 2, 3, 4, 5 ],
-                        [ doc.id, doc.uri, doc.name,
-                          doc.author, doc.surface, doc.mtime ]);
-            }));
+        doc.connect('info-updated', Lang.bind(this, this._onInfoUpdated));
     },
 
-    _onItemRemoved: function(source, doc) {
+    _removeItem: function(doc) {
         // Update the count so that OffsetController has the correct
         // values. Otherwise things like loading more items and "No
         // Results" page will not work correctly.
@@ -127,6 +145,64 @@ const ViewModel = new Lang.Class({
 
                 return false;
             }));
+
+        doc.rowRefs[this._rowRefKey] = null;
+    },
+
+    _onInfoUpdated: function(doc) {
+        let activeCollection = Application.documentManager.getActiveCollection();
+        let treeRowRef = doc.rowRefs[this._rowRefKey];
+
+        if (this._mode == WindowMode.WindowMode.COLLECTIONS) {
+            if (!doc.collection && treeRowRef && !activeCollection) {
+                ;
+            } else if (doc.collection && !treeRowRef && !activeCollection) {
+                this._addItem(doc);
+            }
+        } else if (this._mode == WindowMode.WindowMode.DOCUMENTS) {
+            if (doc.collection && treeRowRef) {
+                ;
+            } else if (!doc.collection && !treeRowRef) {
+                this._addItem(doc);
+            }
+        }
+
+        treeRowRef = doc.rowRefs[this._rowRefKey];
+        if (treeRowRef) {
+            let objectPath = treeRowRef.get_path();
+            if (!objectPath)
+                return;
+
+            let objectIter = this.model.get_iter(objectPath)[1];
+            if (objectIter)
+                this.model.set(objectIter,
+                    [ 0, 1, 2, 3, 4, 5 ],
+                    [ doc.id, doc.uri, doc.name,
+                      doc.author, doc.surface, doc.mtime ]);
+        }
+    },
+
+    _onItemAdded: function(source, doc) {
+        if (doc.rowRefs[this._rowRefKey])
+            return;
+
+        let activeCollection = Application.documentManager.getActiveCollection();
+        let windowMode = Application.modeController.getWindowMode();
+
+        if (!activeCollection || this._mode != windowMode) {
+            if (this._mode == WindowMode.WindowMode.COLLECTIONS && !doc.collection
+                || this._mode == WindowMode.WindowMode.DOCUMENTS && doc.collection) {
+                doc.connect('info-updated', Lang.bind(this, this._onInfoUpdated));
+                return;
+            }
+        }
+
+        this._addItem(doc);
+        doc.connect('info-updated', Lang.bind(this, this._onInfoUpdated));
+    },
+
+    _onItemRemoved: function(source, doc) {
+        this._removeItem(doc);
     },
 
     _resetCount: function() {
@@ -134,7 +210,7 @@ const ViewModel = new Lang.Class({
             this._resetCountId = Mainloop.timeout_add(_RESET_COUNT_TIMEOUT, Lang.bind(this,
                 function() {
                     this._resetCountId = 0;
-                    Application.offsetController.resetItemCount();
+                    this._offsetController.resetItemCount();
                     return false;
                 }));
         }
@@ -276,10 +352,11 @@ const ErrorBox = new Lang.Class({
 const ViewContainer = new Lang.Class({
     Name: 'ViewContainer',
 
-    _init: function() {
+    _init: function(windowMode) {
         this._edgeHitId = 0;
+        this._mode = windowMode;
 
-        this._model = new ViewModel();
+        this._model = new ViewModel(this._mode);
 
         this.widget = new Gtk.Stack({ homogeneous: true,
                                       transition_type: Gtk.StackTransitionType.CROSSFADE });
@@ -332,7 +409,9 @@ const ViewContainer = new Lang.Class({
                 this.view.unselect_all();
             }));
 
-        Application.offsetController.connect('item-count-changed', Lang.bind(this,
+        [ this._offsetController, this._trackerController ] = getController(this._mode);
+
+        this._offsetController.connect('item-count-changed', Lang.bind(this,
             function(controller, count) {
                 if (count == 0)
                     this.widget.set_visible_child_name('no-results');
@@ -340,12 +419,12 @@ const ViewContainer = new Lang.Class({
                     this.widget.set_visible_child_name('view');
             }));
 
-        Application.trackerController.connect('query-error',
+        this._trackerController.connect('query-error',
             Lang.bind(this, this._onQueryError));
-        this._queryId = Application.trackerController.connect('query-status-changed',
+        this._queryId = this._trackerController.connect('query-status-changed',
             Lang.bind(this, this._onQueryStatusChanged));
         // ensure the tracker controller is started
-        Application.trackerController.start();
+        this._trackerController.start();
 
         // this will create the model if we're done querying
         this._onQueryStatusChanged();
@@ -461,7 +540,7 @@ const ViewContainer = new Lang.Class({
     },
 
     _onQueryStatusChanged: function() {
-        let status = Application.trackerController.getQueryStatus();
+        let status = this._trackerController.getQueryStatus();
 
         if (!status) {
             // setup a model if we're not querying
@@ -532,7 +611,7 @@ const ViewContainer = new Lang.Class({
 
     _onWindowModeChanged: function() {
         let mode = Application.modeController.getWindowMode();
-        if (mode == WindowMode.WindowMode.OVERVIEW)
+        if (mode == this._mode)
             this._connectView();
         else
             this._disconnectView();
@@ -542,7 +621,7 @@ const ViewContainer = new Lang.Class({
         this._edgeHitId = this.view.connect('edge-reached', Lang.bind(this,
             function (view, pos) {
                 if (pos == Gtk.PositionType.BOTTOM)
-                    Application.offsetController.increaseOffset();
+                    this._offsetController.increaseOffset();
             }));
     },
 
diff --git a/src/windowMode.js b/src/windowMode.js
index 6959386..4dfa9aa 100644
--- a/src/windowMode.js
+++ b/src/windowMode.js
@@ -28,9 +28,11 @@ const Application = imports.application;
 
 const WindowMode = {
     NONE: 0,
-    OVERVIEW: 1,
+    DOCUMENTS: 1,
     PREVIEW: 2,
-    EDIT: 3
+    EDIT: 3,
+    COLLECTIONS: 4,
+    SEARCH: 5
 };
 
 const ModeController = new Lang.Class({



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]