Back to index

unity  6.0.0
HomeLens.cpp
Go to the documentation of this file.
00001 // -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
00002 /*
00003  * Copyright (C) 2012 Canonical Ltd
00004  *
00005  * This program is free software: you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License version 3 as
00007  * published by the Free Software Foundation.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016  *
00017  * Authored by: Mikkel Kamstrup Erlandsen <mikkel.kamstrup@canonical.com>
00018  */
00019 
00020 #include <glib.h>
00021 #include <string>
00022 #include <stdexcept>
00023 #include <map>
00024 
00025 #include "GLibSignal.h"
00026 #include "HomeLens.h"
00027 #include "Lens.h"
00028 #include "Model.h"
00029 
00030 #include "config.h"
00031 
00032 namespace unity
00033 {
00034 namespace dash
00035 {
00036 
00037 namespace
00038 {
00039 
00040 nux::logging::Logger logger("unity.dash.homelens");
00041 
00042 }
00043 
00044 /*
00045  * Helper class that maps category offsets between the merged lens and
00046  * source lenses. We also use it to merge categories from different lenses
00047  * with the same display name into the same category.
00048  *
00049  * NOTE: The model pointers passed in are expected to be pointers to the
00050  *       result source models - and not the category source models!
00051  */
00052 class HomeLens::CategoryRegistry
00053 {
00054 public:
00055   CategoryRegistry(HomeLens* owner)
00056     : is_dirty_(false)
00057     , owner_(owner) {}
00058 
00059   int FindCategoryOffset(DeeModel* model, unsigned int source_cat_offset)
00060   {
00061     glib::String c_id(g_strdup_printf("%u+%p", source_cat_offset, model));
00062     std::map<std::string,unsigned int>::iterator i = reg_by_id_.find(c_id);
00063 
00064     if (i != reg_by_id_.end())
00065       return i->second;
00066 
00067     return -1;
00068   }
00069 
00070   int FindCategoryOffset(const gchar* display_name)
00071     {
00072       std::map<std::string,unsigned int>::iterator i =
00073                                         reg_by_display_name_.find(display_name);
00074       if (i != reg_by_display_name_.end())
00075         return i->second;
00076 
00077       return -1;
00078     }
00079 
00080   /* Register a new category */
00081   void RegisterCategoryOffset(DeeModel*     model,
00082                                  unsigned int source_cat_offset,
00083                                  const gchar*  display_name,
00084                                  unsigned int target_cat_offset)
00085   {
00086     glib::String c_id(g_strdup_printf("%u+%p", source_cat_offset, model));
00087 
00088     std::map<std::string,unsigned int>::iterator i = reg_by_id_.find(c_id);
00089     if (i != reg_by_id_.end())
00090     {
00091       LOG_ERROR(logger) << "Category '" << c_id << "' already registered!";
00092       return;
00093     }
00094 
00095     if (display_name != NULL)
00096     {
00097       i = reg_by_display_name_.find(display_name);
00098       if (i != reg_by_display_name_.end())
00099       {
00100         LOG_ERROR(logger) << "Category '" << display_name << "' already registered!";
00101         return;
00102       }
00103     }
00104 
00105     /* Any existing categories with offsets >= target_cat_offset must be
00106      * pushed up. Update both maps by id and display name */
00107     std::map<std::string,unsigned int>::iterator end = reg_by_id_.end();
00108     for (i = reg_by_id_.begin(); i != end; i++)
00109     {
00110       if (i->second >= target_cat_offset)
00111       {
00112         i->second = i->second + 1;
00113         is_dirty_ = true;
00114       }
00115     }
00116 
00117     for (i = reg_by_display_name_.begin(), end = reg_by_display_name_.end(); i != end; i++)
00118     {
00119       if (i->second >= target_cat_offset)
00120       {
00121         i->second = i->second + 1;
00122         is_dirty_ = true;
00123       }
00124     }
00125 
00126     reg_by_id_[c_id] = target_cat_offset;
00127 
00128     /* Callers pass a NULL display_name when they already have a category
00129      * with the right display registered */
00130     if (display_name != NULL)
00131     {
00132       reg_by_display_name_[display_name] = target_cat_offset;
00133       LOG_DEBUG(logger) << "Registered category '" << display_name
00134                         << "' with source offset " << source_cat_offset
00135                         << " and target offset " << target_cat_offset
00136                         << ". Id " << c_id;
00137     }
00138     else
00139     {
00140       LOG_DEBUG(logger) << "Registered category with source offset "
00141                         << source_cat_offset << " and target offset "
00142                         << target_cat_offset << ". Id " << c_id;
00143     }
00144   }
00145 
00146   /* Associate a source results model and category offset with an existing
00147    * target category offset */
00148   void AssociateCategoryOffset(DeeModel*     model,
00149                                unsigned int source_cat_offset,
00150                                unsigned int target_cat_offset)
00151   {
00152     glib::String c_id(g_strdup_printf("%u+%p", source_cat_offset, model));
00153 
00154     std::map<std::string,unsigned int>::iterator i = reg_by_id_.find(c_id);
00155     if (i != reg_by_id_.end())
00156     {
00157       LOG_ERROR(logger) << "Category '" << c_id << "' already registered!";
00158       return;
00159     }
00160 
00161     reg_by_id_[c_id] = target_cat_offset;
00162   }
00163 
00170   bool CheckDirty()
00171   {
00172     return is_dirty_ ? (is_dirty_ = false, true) : false;
00173   }
00174 
00175 private:
00176   std::map<std::string,unsigned int> reg_by_id_;
00177   std::map<std::string,unsigned int> reg_by_display_name_;
00178   bool is_dirty_;
00179   HomeLens* owner_;
00180 };
00181 
00182 /*
00183  * Helper class that merges a set of DeeModels into one super model
00184  */
00185 class HomeLens::ModelMerger : public sigc::trackable
00186 {
00187 public:
00188   ModelMerger(glib::Object<DeeModel> target);
00189   virtual ~ModelMerger();
00190 
00191   void AddSource(glib::Object<DeeModel> source);
00192 
00193 protected:
00194   virtual void OnSourceRowAdded(DeeModel *model, DeeModelIter *iter);
00195   virtual void OnSourceRowRemoved(DeeModel* model, DeeModelIter* iter);
00196   virtual void OnSourceRowChanged(DeeModel* model, DeeModelIter* iter);
00197   void EnsureRowBuf(DeeModel *source);
00198 
00199   /* The merge tag lives on the source models, pointing to the mapped
00200    * row in the target model */
00201   DeeModelTag* FindSourceToTargetTag(DeeModel *model);
00202 
00203 protected:
00204   glib::SignalManager sig_manager_;
00205   GVariant** row_buf_;
00206   unsigned int n_cols_;
00207   glib::Object<DeeModel> target_;
00208   std::map<DeeModel*,DeeModelTag*> source_to_target_tags_;
00209 };
00210 
00211 /*
00212  * Specialized ModelMerger that takes care merging results models.
00213  * We need special handling here because rows in each lens' results model
00214  * specifies an offset into the lens' categories model where the display
00215  * name of the category is defined.
00216  *
00217  * This class converts the offset of the source lens' categories into
00218  * offsets into the merged category model.
00219  *
00220  * Each row added to the target is tagged with a pointer to the Lens instance
00221  * from which it came
00222  */
00223 class HomeLens::ResultsMerger : public ModelMerger
00224 {
00225 public:
00226   ResultsMerger(glib::Object<DeeModel> target,
00227                   HomeLens::CategoryRegistry* cat_registry);
00228 
00229 protected:
00230   void OnSourceRowAdded(DeeModel *model, DeeModelIter *iter);
00231   void OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter);
00232   void OnSourceRowChanged(DeeModel *model, DeeModelIter *iter);
00233   void CheckCategoryRegistryDirty();
00234 
00235 private:
00236   HomeLens::CategoryRegistry* cat_registry_;
00237 };
00238 
00239 /*
00240  * Specialized ModelMerger that takes care merging category models.
00241  * We need special handling here because rows in each lens' results model
00242  * specifies an offset into the lens' categories model where the display
00243  * name of the category is defined.
00244  *
00245  * This class records a map of the offsets from the original source category
00246  * models to the offsets in the combined categories model.
00247  */
00248 class HomeLens::CategoryMerger : public ModelMerger
00249 {
00250 public:
00251   CategoryMerger(glib::Object<DeeModel> target,
00252                    HomeLens::CategoryRegistry* cat_registry);
00253 
00254   void OnSourceRowAdded(DeeModel *model, DeeModelIter *iter);
00255   void OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter);
00256 
00257 private:
00258   HomeLens::CategoryRegistry* cat_registry_;
00259   DeeModelTag* priority_tag_;
00260 };
00261 
00262 /*
00263  * Specialized ModelMerger that is reponsible for filters, currently ignores
00264  * everything.
00265  */
00266 class HomeLens::FiltersMerger : public ModelMerger
00267 {
00268 public:
00269   FiltersMerger(glib::Object<DeeModel> target);
00270 
00271   void OnSourceRowAdded(DeeModel *model, DeeModelIter *iter);
00272   void OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter);
00273   void OnSourceRowChanged(DeeModel *model, DeeModelIter *iter);
00274 };
00275 
00276 /*
00277  * Pimpl for HomeLens
00278  */
00279 class HomeLens::Impl : public sigc::trackable
00280 {
00281 public:
00282   Impl(HomeLens* owner);
00283   ~Impl();
00284 
00285   void OnLensAdded(Lens::Ptr& lens);
00286   gsize FindLensPriority (Lens::Ptr& lens);
00287   void EnsureCategoryAnnotation(Lens::Ptr& lens, DeeModel* results, DeeModel* categories);
00288   Lens::Ptr FindLensForUri(std::string const& uri);
00289 
00290   HomeLens* owner_;
00291   Lenses::LensList lenses_;
00292   HomeLens::CategoryRegistry cat_registry_;
00293   HomeLens::ResultsMerger results_merger_;
00294   HomeLens::CategoryMerger categories_merger_;
00295   HomeLens::FiltersMerger filters_merger_;
00296   int running_searches_;
00297   glib::Object<GSettings> settings_;
00298 };
00299 
00300 /*
00301  * IMPLEMENTATION
00302  */
00303 
00304 HomeLens::ModelMerger::ModelMerger(glib::Object<DeeModel> target)
00305   : row_buf_(NULL)
00306   , n_cols_(0)
00307   , target_(target)
00308 {}
00309 
00310 HomeLens::ResultsMerger::ResultsMerger(glib::Object<DeeModel> target,
00311                                             CategoryRegistry *cat_registry)
00312   : HomeLens::ModelMerger::ModelMerger(target)
00313   , cat_registry_(cat_registry)
00314 {}
00315 
00316 HomeLens::CategoryMerger::CategoryMerger(glib::Object<DeeModel> target,
00317                                               CategoryRegistry *cat_registry)
00318   : HomeLens::ModelMerger::ModelMerger(target)
00319   , cat_registry_(cat_registry)
00320   , priority_tag_(dee_model_register_tag(target, NULL))
00321 {}
00322 
00323 HomeLens::FiltersMerger::FiltersMerger(glib::Object<DeeModel> target)
00324   : HomeLens::ModelMerger::ModelMerger(target)
00325 {}
00326 
00327 HomeLens::ModelMerger::~ModelMerger()
00328 {
00329   if (row_buf_)
00330     g_free(row_buf_);
00331 }
00332 
00333 void HomeLens::ModelMerger::AddSource(glib::Object<DeeModel> source)
00334 {
00335   typedef glib::Signal<void, DeeModel*, DeeModelIter*> RowSignalType;
00336 
00337   if (!source)
00338   {
00339     LOG_ERROR(logger) << "Trying to add NULL source to ModelMerger";
00340     return;
00341   }
00342 
00343   DeeModelTag* merger_tag = dee_model_register_tag(source, NULL);
00344   source_to_target_tags_[source.RawPtr()] = merger_tag;
00345 
00346   sig_manager_.Add(new RowSignalType(source.RawPtr(),
00347                                      "row-added",
00348                                      sigc::mem_fun(this, &HomeLens::ModelMerger::OnSourceRowAdded)));
00349 
00350   sig_manager_.Add(new RowSignalType(source.RawPtr(),
00351                                        "row-removed",
00352                                        sigc::mem_fun(this, &HomeLens::ModelMerger::OnSourceRowRemoved)));
00353 
00354   sig_manager_.Add(new RowSignalType(source.RawPtr(),
00355                                        "row-changed",
00356                                        sigc::mem_fun(this, &HomeLens::ModelMerger::OnSourceRowChanged)));
00357 }
00358 
00359 void HomeLens::ModelMerger::OnSourceRowAdded(DeeModel *model, DeeModelIter *iter)
00360 {
00361   // Default impl. does nothing.
00362 }
00363 
00364 void HomeLens::ResultsMerger::OnSourceRowAdded(DeeModel *model, DeeModelIter *iter)
00365 {
00366   DeeModelIter* target_iter;
00367   DeeModelTag*  target_tag;
00368   int target_cat_offset, source_cat_offset;
00369   const unsigned int CATEGORY_COLUMN = 2;
00370 
00371   EnsureRowBuf(model);
00372   CheckCategoryRegistryDirty();
00373 
00374   dee_model_get_row (model, iter, row_buf_);
00375   target_tag = FindSourceToTargetTag(model);
00376 
00377   /* Update the row with the corrected category offset */
00378   source_cat_offset = dee_model_get_uint32(model, iter, CATEGORY_COLUMN);
00379   target_cat_offset = cat_registry_->FindCategoryOffset(model, source_cat_offset);
00380 
00381   if (target_cat_offset >= 0)
00382   {
00383     g_variant_unref (row_buf_[CATEGORY_COLUMN]);
00384     row_buf_[CATEGORY_COLUMN] = g_variant_new_uint32(target_cat_offset);
00385 
00386     /* Sink the ref on the new row member. By Dee API contract they must all
00387      * be strong refs, not floating */
00388     g_variant_ref_sink(row_buf_[CATEGORY_COLUMN]);
00389 
00390     target_iter = dee_model_append_row (target_, row_buf_);
00391     dee_model_set_tag(model, iter, target_tag, target_iter);
00392 
00393     LOG_DEBUG(logger) << "Found " << dee_model_get_string(model, iter, 0)
00394                       << " (source cat " << source_cat_offset << ", target cat "
00395                       << target_cat_offset << ")";
00396   }
00397   else
00398   {
00399     LOG_ERROR(logger) << "No category registered for model "
00400                       << model << ", source offset " << source_cat_offset
00401                       << ": " << dee_model_get_string(model, iter, 0);
00402   }
00403 
00404   for (unsigned int i = 0; i < n_cols_; i++) g_variant_unref(row_buf_[i]);
00405 }
00406 
00407 void HomeLens::CategoryMerger::OnSourceRowAdded(DeeModel *model, DeeModelIter *iter)
00408 {
00409   DeeModel* results_model;
00410   DeeModelIter* target_iter;
00411   DeeModelIter* target_end;
00412   DeeModelTag*  target_tag;
00413   int target_cat_offset, source_cat_offset;
00414   const gchar* display_name;
00415   const unsigned int DISPLAY_NAME_COLUMN = 0;
00416   gsize lens_priority, prio;
00417 
00418   EnsureRowBuf(model);
00419 
00420   results_model = static_cast<DeeModel*>(g_object_get_data(
00421                               G_OBJECT(model), "unity-homelens-results-model"));
00422   if (results_model == NULL)
00423   {
00424     LOG_DEBUG(logger) << "Category model " << model
00425                       << " does not have a results model yet";
00426     return;
00427   }
00428 
00429   dee_model_get_row (model, iter, row_buf_);
00430   target_tag = FindSourceToTargetTag(model);
00431   source_cat_offset = dee_model_get_position(model, iter);
00432 
00433   /* If we already have a category registered with the same display name
00434    * then we just use that. Otherwise register a new category for it */
00435   display_name = dee_model_get_string(model, iter, DISPLAY_NAME_COLUMN);
00436   target_cat_offset = cat_registry_->FindCategoryOffset(display_name);
00437   if (target_cat_offset >= 0)
00438   {
00439     cat_registry_->AssociateCategoryOffset(results_model, source_cat_offset,
00440                                            target_cat_offset);
00441     goto cleanup;
00442   }
00443 
00444   /*
00445    * Below we can assume that we have a genuinely new category.
00446    *
00447    * Our goal is to insert the category at a position suitable for its
00448    * priority. We insert it as the last item in the set of items which
00449    * have equal priority.
00450    *
00451    * We allow our selves to do linear inserts as we wont expect a lot
00452    * of categories.
00453    */
00454 
00455   lens_priority = GPOINTER_TO_SIZE(g_object_get_data(
00456                               G_OBJECT(model), "unity-homelens-priority"));
00457 
00458   /* Seek correct position in the merged category model */
00459   target_iter = dee_model_get_first_iter(target_);
00460   target_end = dee_model_get_last_iter(target_);
00461   while (target_iter != target_end)
00462   {
00463     prio = GPOINTER_TO_SIZE(dee_model_get_tag(target_, target_iter, priority_tag_));
00464     if (lens_priority > prio)
00465       break;
00466     target_iter = dee_model_next(target_, target_iter);
00467   }
00468 
00469   /* Add the row to the merged categories model and store required metadata */
00470   target_iter = dee_model_insert_row_before(target_, target_iter, row_buf_);
00471   dee_model_set_tag(model, iter, target_tag, target_iter);
00472   dee_model_set_tag(target_, target_iter, priority_tag_, GSIZE_TO_POINTER(lens_priority));
00473   target_cat_offset = dee_model_get_position(target_, target_iter);
00474   cat_registry_->RegisterCategoryOffset(results_model, source_cat_offset,
00475                                         display_name, target_cat_offset);
00476 
00477   cleanup:
00478     for (unsigned int i = 0; i < n_cols_; i++) g_variant_unref(row_buf_[i]);
00479 }
00480 
00481 void HomeLens::FiltersMerger::OnSourceRowAdded(DeeModel *model, DeeModelIter *iter)
00482 {
00483   /* Supporting filters on the home screen is possible, but *quite* tricky.
00484    * So... Discard ALL the rows!
00485    */
00486 }
00487 
00488 void HomeLens::CategoryMerger::OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter)
00489 {
00490   /* We don't support removals of categories.
00491    * You can check out any time you like, but you can never leave
00492    *
00493    * The category registry code is spaghettified enough already.
00494    * No more please.
00495    */
00496   LOG_DEBUG(logger) << "Removal of categories not supported.";
00497 }
00498 
00499 void HomeLens::ModelMerger::OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter)
00500 {
00501   DeeModelIter* target_iter;
00502   DeeModelTag*  target_tag;
00503 
00504   EnsureRowBuf(model);
00505 
00506   target_tag = FindSourceToTargetTag(model);
00507   target_iter = static_cast<DeeModelIter*>(dee_model_get_tag(model,
00508                                                                 iter,
00509                                                                 target_tag));
00510 
00511   /* We might not have registered a target iter for the row.
00512    * This fx. happens if we re-used a category based on display_name */
00513   if (target_iter != NULL)
00514     dee_model_remove(target_, target_iter);
00515 }
00516 
00517 void HomeLens::ResultsMerger::OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter)
00518 {
00519   CheckCategoryRegistryDirty();
00520   ModelMerger::OnSourceRowRemoved(model, iter);
00521 }
00522 
00523 void HomeLens::FiltersMerger::OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter)
00524 {
00525   /* We aren't adding any rows to the merged model, so nothing to do here */
00526 }
00527 
00528 void HomeLens::ModelMerger::OnSourceRowChanged(DeeModel *model, DeeModelIter *iter)
00529 {
00530   DeeModelIter* target_iter;
00531   DeeModelTag*  target_tag;
00532 
00533   EnsureRowBuf(model);
00534 
00535   dee_model_get_row (model, iter, row_buf_);
00536   target_tag = FindSourceToTargetTag(model);
00537   target_iter = static_cast<DeeModelIter*>(dee_model_get_tag(model,
00538                                                                  iter,
00539                                                                  target_tag));
00540 
00541   dee_model_set_row (target_, target_iter, row_buf_);
00542 
00543   for (unsigned int i = 0; i < n_cols_; i++) g_variant_unref(row_buf_[i]);
00544 }
00545 
00546 void HomeLens::ResultsMerger::OnSourceRowChanged(DeeModel *model, DeeModelIter *iter)
00547 {
00548   // FIXME: We can support this, but we need to re-calculate the category offset
00549   LOG_WARN(logger) << "In-line changing of results not supported in the home lens. Sorry.";
00550 }
00551 
00552 void HomeLens::FiltersMerger::OnSourceRowChanged(DeeModel *model, DeeModelIter *iter)
00553 {
00554   /* We aren't adding any rows to the merged model, so nothing to do here */
00555 }
00556 
00557 void HomeLens::ModelMerger::EnsureRowBuf(DeeModel *model)
00558 {
00559   if (G_UNLIKELY (n_cols_ == 0))
00560   {
00561     /* We have two things to accomplish here.
00562      * 1) Allocate the row_buf_, and
00563      * 2) Make sure that the target model has the correct schema set.
00564      *
00565      * INVARIANT: n_cols_ == 0 iff row_buf_ == NULL.
00566      */
00567 
00568     n_cols_ = dee_model_get_n_columns(model);
00569 
00570     if (n_cols_ == 0)
00571     {
00572       LOG_ERROR(logger) << "Source model has not provided a schema for the model merger!";
00573       return;
00574     }
00575 
00576     /* Lazily adopt schema from source if we don't have one.
00577      * If we do have a schema let's validate that they match the source */
00578     if (dee_model_get_n_columns(target_) == 0)
00579     {
00580       dee_model_set_schema_full(target_,
00581                                 dee_model_get_schema(model, NULL),
00582                                 n_cols_);
00583     }
00584     else
00585     {
00586       unsigned int n_cols1;
00587       const gchar* const *schema1 = dee_model_get_schema(target_, &n_cols1);
00588       const gchar* const *schema2 = dee_model_get_schema(model, NULL);
00589 
00590       /* At the very least we should have an equal number of rows */
00591       if (n_cols_ != n_cols1)
00592       {
00593         LOG_ERROR(logger) << "Schema mismatch between source and target model. Expected "
00594                           << n_cols1 << " columns, but found "
00595                           << n_cols_ << ".";
00596         n_cols_ = 0;
00597         return;
00598       }
00599 
00600       /* Compare schemas */
00601       for (unsigned int i = 0; i < n_cols_; i++)
00602       {
00603         if (g_strcmp0(schema1[i], schema2[i]) != 0)
00604         {
00605           LOG_ERROR(logger) << "Schema mismatch between source and target model. Expected column "
00606                             << i << " to be '" << schema1[i] << "', but found '"
00607                             << schema2[i] << "'.";
00608           n_cols_ = 0;
00609           return;
00610         }
00611       }
00612     }
00613 
00614     row_buf_ = g_new0 (GVariant*, n_cols_);
00615   }
00616 }
00617 
00618 DeeModelTag* HomeLens::ModelMerger::FindSourceToTargetTag(DeeModel *model)
00619 {
00620   return source_to_target_tags_[model];
00621 }
00622 
00623 void HomeLens::ResultsMerger::CheckCategoryRegistryDirty()
00624 {
00625   DeeModel* source;
00626   DeeModelTag* target_tag;
00627   const unsigned int CATEGORY_COLUMN = 2;
00628   std::map<DeeModel*,DeeModelTag*>::iterator i, end;
00629 
00630   if (G_LIKELY(!cat_registry_->CheckDirty()))
00631     return;
00632 
00633   LOG_DEBUG(logger) << "Category registry marked dirty. Fixing category offsets.";
00634 
00635   /*
00636    * Iterate over all results in each source model and re-calculate the
00637    * the category offset in the corresponding rows in the target model
00638    */
00639   for (i = source_to_target_tags_.begin(), end = source_to_target_tags_.end();
00640        i != end; i++)
00641   {
00642     source = i->first;
00643     target_tag = i->second;
00644 
00645     DeeModelIter* source_iter = dee_model_get_first_iter(source);
00646     DeeModelIter* source_end = dee_model_get_last_iter(source);
00647 
00648     for (source_iter = dee_model_get_first_iter(source), source_end = dee_model_get_last_iter(source);
00649          source_iter != source_end;
00650          source_iter = dee_model_next(source, source_iter))
00651     {
00652       DeeModelIter* target_iter = static_cast<DeeModelIter*>(dee_model_get_tag(source, source_iter, target_tag));
00653 
00654       /* No guarantee that rows in the source are mapped to the target */
00655       if (target_iter == NULL)
00656         continue;
00657 
00658       unsigned int source_cat_offset = dee_model_get_uint32(source, source_iter, CATEGORY_COLUMN);
00659       int cat_offset = cat_registry_->FindCategoryOffset(source, source_cat_offset);
00660 
00661       if (G_LIKELY(cat_offset >= 0))
00662       {
00663         dee_model_set_value(target_, target_iter, CATEGORY_COLUMN,
00664                             g_variant_new_uint32(cat_offset));
00665       }
00666       else
00667       {
00668         LOG_ERROR(logger) << "No registered category id for category "
00669             << source_cat_offset << " on result source model "
00670             << source << ".";
00671         /* We can't really recover from this :-( */
00672       }
00673     }
00674   }
00675 }
00676 
00677 HomeLens::Impl::Impl(HomeLens *owner)
00678   : owner_(owner)
00679   , cat_registry_(owner)
00680   , results_merger_(owner->results()->model(), &cat_registry_)
00681   , categories_merger_(owner->categories()->model(), &cat_registry_)
00682   , filters_merger_(owner->filters()->model())
00683   , running_searches_(0)
00684   , settings_(g_settings_new("com.canonical.Unity.Dash"))
00685 {
00686   DeeModel* results = owner->results()->model();
00687   if (dee_model_get_n_columns(results) == 0)
00688   {
00689     dee_model_set_schema(results, "s", "s", "u", "s", "s", "s", "s", NULL);
00690   }
00691 
00692   DeeModel* categories = owner->categories()->model();
00693   if (dee_model_get_n_columns(categories) == 0)
00694   {
00695     dee_model_set_schema(categories, "s", "s", "s", "a{sv}", NULL);
00696   }
00697 
00698   DeeModel* filters = owner->filters()->model();
00699   if (dee_model_get_n_columns(filters) == 0)
00700   {
00701     dee_model_set_schema(filters, "s", "s", "s", "s", "a{sv}", "b", "b", "b", NULL);
00702   }
00703 }
00704 
00705 HomeLens::Impl::~Impl()
00706 {
00707 
00708 }
00709 
00710 /*void HomeLens::Impl::CheckCategories()
00711 {
00712 
00713 }*/
00714 
00715 gsize HomeLens::Impl::FindLensPriority (Lens::Ptr& lens)
00716 {
00717   gchar** lenses = g_settings_get_strv(settings_, "home-lens-ordering");
00718   gsize pos = 0, len = g_strv_length(lenses);
00719 
00720   for (pos = 0; pos < len; pos++)
00721   {
00722     if (g_strcmp0(lenses[pos], lens->id().c_str()) == 0)
00723       break;
00724   }
00725 
00726   g_strfreev(lenses);
00727 
00728   return len - pos;
00729 }
00730 
00731 void HomeLens::Impl::EnsureCategoryAnnotation (Lens::Ptr& lens,
00732                                                      DeeModel* categories,
00733                                                      DeeModel* results)
00734 {
00735   if (categories && results)
00736   {
00737     if (!(DEE_IS_MODEL(results) && DEE_IS_MODEL(categories)))
00738     {
00739       LOG_ERROR(logger) << "The "
00740                         << std::string(DEE_IS_MODEL(results) ? "categories" : "results")
00741                         << " model is not a valid DeeModel. ("
00742                         << lens->id() << ")";
00743       return;
00744     }
00745 
00746     g_object_set_data(G_OBJECT(categories),
00747                       "unity-homelens-results-model",
00748                       results);
00749 
00750     gsize lens_priority = FindLensPriority(lens);
00751     g_object_set_data(G_OBJECT(categories),
00752                       "unity-homelens-priority",
00753                       GSIZE_TO_POINTER(lens_priority));
00754 
00755     LOG_DEBUG(logger) << "Registering results model "  << results
00756                       << " and lens priority " << lens_priority
00757                       << " on category model " << categories << ". ("
00758                       << lens->id() << ")";
00759   }
00760 }
00761 
00762 Lens::Ptr HomeLens::Impl::FindLensForUri(std::string const& uri)
00763 {
00764   /* We iterate over all lenses looking for the given uri in their
00765    * global results. This might seem like a sucky approach, but it
00766    * saves us from a ship load of book keeping */
00767 
00768   for (auto lens : lenses_)
00769   {
00770     DeeModel* results = lens->global_results()->model();
00771     DeeModelIter* iter = dee_model_get_first_iter(results);
00772     DeeModelIter* end = dee_model_get_last_iter(results);
00773     const int URI_COLUMN = 0;
00774 
00775     while (iter != end)
00776       {
00777         if (g_strcmp0(uri.c_str(), dee_model_get_string(results, iter, URI_COLUMN)) == 0)
00778         {
00779           return lens;
00780         }
00781         iter = dee_model_next(results, iter);
00782       }
00783   }
00784 
00785   return Lens::Ptr();
00786 }
00787 
00788 // FIXME: Coordinated sorting between the lens bar and home screen categories. Make void FilesystemLenses::Impl::DecrementAndCheckChildrenWaiting() use the gsettings key
00789 // FIXME: on no results https://bugs.launchpad.net/unity/+bug/711199
00790 
00791 void HomeLens::Impl::OnLensAdded (Lens::Ptr& lens)
00792 {
00793   lenses_.push_back (lens);
00794   owner_->lens_added.emit(lens);
00795 
00796   /* When we dispatch a search we inc the search count and when we finish
00797    * one we decrease it. When we reach 0 we'll emit search_finished. */
00798   lens->global_search_finished.connect([&] (Hints const& hints) {
00799       running_searches_--;
00800 
00801       if (running_searches_ <= 0)
00802       {
00803         owner_->search_finished.emit(Hints());
00804         LOG_INFO(logger) << "Search finished";
00805       }
00806   });
00807 
00808   nux::ROProperty<glib::Object<DeeModel>>& results_prop = lens->global_results()->model;
00809   nux::ROProperty<glib::Object<DeeModel>>& categories_prop = lens->categories()->model;
00810   nux::ROProperty<glib::Object<DeeModel>>& filters_prop = lens->filters()->model;
00811 
00812   /*
00813    * Important: We must ensure that the categories model is annotated
00814    *            with the results model in the "unity-homelens-results-model"
00815    *            data slot. We need it later to compute the transfermed offsets
00816    *            of the categories in the merged category model.
00817    */
00818 
00819   /* Most lenses add models lazily, but we can't know that;
00820    * so try to see if we can add them up front */
00821   if (results_prop().RawPtr())
00822   {
00823     EnsureCategoryAnnotation(lens, categories_prop(), results_prop());
00824     results_merger_.AddSource(results_prop());
00825   }
00826 
00827   if (categories_prop().RawPtr())
00828   {
00829     EnsureCategoryAnnotation(lens, categories_prop(), results_prop());
00830     categories_merger_.AddSource(categories_prop());
00831   }
00832 
00833   if (filters_prop().RawPtr())
00834     filters_merger_.AddSource(filters_prop());
00835 
00836   /*
00837    * Pick it up when the lens set models lazily.
00838    */
00839   results_prop.changed.connect([&] (glib::Object<DeeModel> model)
00840   {
00841     EnsureCategoryAnnotation(lens, lens->categories()->model(), model);
00842     results_merger_.AddSource(model);
00843   });
00844 
00845   categories_prop.changed.connect([&] (glib::Object<DeeModel> model)
00846   {
00847     EnsureCategoryAnnotation(lens, model, lens->global_results()->model());
00848     categories_merger_.AddSource(model);
00849   });
00850 
00851   filters_prop.changed.connect([&] (glib::Object<DeeModel> model)
00852   {
00853     filters_merger_.AddSource(model);
00854   });
00855 
00856   /*
00857    * Register pre-existing categories up front
00858    * FIXME: Do the same for results?
00859    */
00860   DeeModel* cats = categories_prop();
00861   if (cats)
00862   {
00863     DeeModelIter* cats_iter;
00864     DeeModelIter* cats_end;
00865     for (cats_iter = dee_model_get_first_iter(cats), cats_end = dee_model_get_last_iter(cats);
00866         cats_iter != cats_end;
00867         cats_iter = dee_model_next(cats, cats_iter))
00868     {
00869       categories_merger_.OnSourceRowAdded(cats, cats_iter);
00870     }
00871   }
00872 }
00873 
00874 HomeLens::HomeLens(std::string const& name, std::string const& description, std::string const& search_hint)
00875   : Lens("home.lens", "", "", name, PKGDATADIR"/lens-nav-home.svg",
00876          description, search_hint, true, "",
00877          ModelType::LOCAL)
00878   , pimpl(new Impl(this))
00879 {
00880   count.SetGetterFunction(sigc::mem_fun(&pimpl->lenses_, &Lenses::LensList::size));
00881   search_in_global = false;
00882 }
00883 
00884 HomeLens::~HomeLens()
00885 {
00886   delete pimpl;
00887 }
00888 
00889 void HomeLens::AddLenses(Lenses& lenses)
00890 {
00891   for (auto lens : lenses.GetLenses())
00892   {
00893     pimpl->OnLensAdded(lens);
00894   }
00895 
00896   lenses.lens_added.connect(sigc::mem_fun(pimpl, &HomeLens::Impl::OnLensAdded));
00897 }
00898 
00899 Lenses::LensList HomeLens::GetLenses() const
00900 {
00901   return pimpl->lenses_;
00902 }
00903 
00904 Lens::Ptr HomeLens::GetLens(std::string const& lens_id) const
00905 {
00906   for (auto lens: pimpl->lenses_)
00907   {
00908     if (lens->id == lens_id)
00909     {
00910       return lens;
00911     }
00912   }
00913 
00914   return Lens::Ptr();
00915 }
00916 
00917 Lens::Ptr HomeLens::GetLensAtIndex(std::size_t index) const
00918 {
00919   try
00920   {
00921     return pimpl->lenses_.at(index);
00922   }
00923   catch (std::out_of_range& error)
00924   {
00925     LOG_WARN(logger) << error.what();
00926   }
00927 
00928   return Lens::Ptr();
00929 }
00930 
00931 void HomeLens::GlobalSearch(std::string const& search_string)
00932 {
00933   LOG_WARN(logger) << "Global search not enabled for HomeLens class."
00934                    << " Ignoring query '" << search_string << "'";
00935 }
00936 
00937 void HomeLens::Search(std::string const& search_string)
00938 {
00939   LOG_DEBUG(logger) << "Search '" << search_string << "'";
00940 
00941   /* Reset running search counter */
00942   pimpl->running_searches_ = 0;
00943 
00944   for (auto lens: pimpl->lenses_)
00945   {
00946     if (lens->search_in_global())
00947     {
00948       LOG_INFO(logger) << " - Global search on '" << lens->id() << "' for '"
00949           << search_string << "'";
00950       lens->view_type = ViewType::HOME_VIEW;
00951       lens->GlobalSearch(search_string);
00952       pimpl->running_searches_++;
00953     }
00954   }
00955 }
00956 
00957 void HomeLens::Activate(std::string const& uri)
00958 {
00959   LOG_DEBUG(logger) << "Activate '" << uri << "'";
00960 
00961   Lens::Ptr lens = pimpl->FindLensForUri(uri);
00962 
00963   /* Fall back to default handling of URIs if no lens is found.
00964    *  - Although, this shouldn't really happen */
00965   if (lens)
00966   {
00967     LOG_DEBUG(logger) << "Activation request passed to '" << lens->id() << "'";
00968     lens->Activate(uri);
00969   }
00970   else
00971   {
00972     LOG_WARN(logger) << "Unable to find a lens for activating '" << uri
00973                      << "'. Using fallback activation.";
00974     activated.emit(uri, HandledType::NOT_HANDLED, Hints());
00975   }
00976 }
00977 
00978 void HomeLens::Preview(std::string const& uri)
00979 {
00980   LOG_DEBUG(logger) << "Preview '" << uri << "'";
00981 
00982   Lens::Ptr lens = pimpl->FindLensForUri(uri);
00983 
00984   if (lens)
00985     lens->Preview(uri);
00986   else
00987     LOG_WARN(logger) << "Unable to find a lens for previewing '" << uri << "'";
00988 }
00989 
00990 }
00991 }