diff options
62 files changed, 3004 insertions, 1023 deletions
@@ -39,5 +39,12 @@ or if you have a specific client you care about you can build that client explic Testing is handled through GTest, googles test system, we currently have three test targets; test-gtest, test-gtest-xless, test-gtest-dbus. tests that require Nux should be in test-gtest as they require X, tests that can run headless should be in test-gtest-xless and tests that require dbus should be run with test-gtest-dbus, which tests run with our test service. +To enable debug logging you must set the environment variable UNITY_LOG_SEVERITY to the log domain you +want to watch and the log level for that domain. For example: + + UNITY_LOG_SEVERITY="unity.dash.lens=DEBUG" standalone-clients/dash + + + for more information see the README file diff --git a/UnityCore/CMakeLists.txt b/UnityCore/CMakeLists.txt index 9a3e822cc..0242eecab 100644 --- a/UnityCore/CMakeLists.txt +++ b/UnityCore/CMakeLists.txt @@ -22,6 +22,7 @@ set (CORE_HEADERS GLibWrapper.h GLibWrapper-inl.h Hud.h + HomeLens.h IndicatorEntry.h Indicator.h Indicators.h @@ -55,6 +56,7 @@ set (CORE_SOURCES GLibSignal.cpp GLibWrapper.cpp Hud.cpp + HomeLens.cpp Indicator.cpp IndicatorEntry.cpp Indicators.cpp diff --git a/UnityCore/Categories.cpp b/UnityCore/Categories.cpp index 247e513e1..ffb9a4910 100644 --- a/UnityCore/Categories.cpp +++ b/UnityCore/Categories.cpp @@ -31,6 +31,14 @@ Categories::Categories() row_removed.connect(sigc::mem_fun(this, &Categories::OnRowRemoved)); } +Categories::Categories(ModelType model_type) + : Model<Category>::Model(model_type) +{ + row_added.connect(sigc::mem_fun(this, &Categories::OnRowAdded)); + row_changed.connect(sigc::mem_fun(this, &Categories::OnRowChanged)); + row_removed.connect(sigc::mem_fun(this, &Categories::OnRowRemoved)); +} + void Categories::OnRowAdded(Category& category) { category_added.emit(category); diff --git a/UnityCore/Categories.h b/UnityCore/Categories.h index df26dbb37..562b0389e 100644 --- a/UnityCore/Categories.h +++ b/UnityCore/Categories.h @@ -36,6 +36,7 @@ public: typedef std::shared_ptr<Categories> Ptr; Categories(); + Categories(ModelType model_type); sigc::signal<void, Category const&> category_added; sigc::signal<void, Category const&> category_changed; diff --git a/UnityCore/Filters.cpp b/UnityCore/Filters.cpp index a56ab88cf..3eaeba59d 100644 --- a/UnityCore/Filters.cpp +++ b/UnityCore/Filters.cpp @@ -56,6 +56,14 @@ Filters::Filters() row_removed.connect(sigc::mem_fun(this, &Filters::OnRowRemoved)); } +Filters::Filters(ModelType model_type) + : Model<FilterAdaptor>::Model(model_type) +{ + row_added.connect(sigc::mem_fun(this, &Filters::OnRowAdded)); + row_changed.connect(sigc::mem_fun(this, &Filters::OnRowChanged)); + row_removed.connect(sigc::mem_fun(this, &Filters::OnRowRemoved)); +} + Filters::~Filters() {} diff --git a/UnityCore/Filters.h b/UnityCore/Filters.h index d0412385b..7b2e2b68a 100644 --- a/UnityCore/Filters.h +++ b/UnityCore/Filters.h @@ -51,6 +51,7 @@ public: typedef std::map<DeeModelIter*, Filter::Ptr> FilterMap; Filters(); + Filters(ModelType model_type); ~Filters(); Filter::Ptr FilterAtIndex(std::size_t index); diff --git a/UnityCore/GLibDBusProxy.cpp b/UnityCore/GLibDBusProxy.cpp index eaa9f92cd..6c765ad29 100644 --- a/UnityCore/GLibDBusProxy.cpp +++ b/UnityCore/GLibDBusProxy.cpp @@ -72,6 +72,7 @@ public: int timeout_msec); void Connect(string const& signal_name, ReplyCallback callback); + bool IsConnected(); static void OnNameAppeared(GDBusConnection* connection, const char* name, const char* name_owner, gpointer impl); @@ -191,6 +192,11 @@ void DBusProxy::Impl::Connect() this); } +bool DBusProxy::Impl::IsConnected() +{ + return connected_; +} + void DBusProxy::Impl::OnProxyConnectCallback(GObject* source, GAsyncResult* res, gpointer impl) @@ -319,5 +325,10 @@ void DBusProxy::Connect(std::string const& signal_name, ReplyCallback callback) pimpl->Connect(signal_name, callback); } +bool DBusProxy::IsConnected() +{ + return pimpl->IsConnected(); +} + } } diff --git a/UnityCore/GLibDBusProxy.h b/UnityCore/GLibDBusProxy.h index cb70da243..de363fe5b 100644 --- a/UnityCore/GLibDBusProxy.h +++ b/UnityCore/GLibDBusProxy.h @@ -54,6 +54,7 @@ public: int timeout_msec = -1); void Connect(std::string const& signal_name, ReplyCallback callback); + bool IsConnected(); sigc::signal<void> connected; sigc::signal<void> disconnected; diff --git a/UnityCore/GLibWrapper.cpp b/UnityCore/GLibWrapper.cpp index 4ed398a94..05f5f4f7b 100644 --- a/UnityCore/GLibWrapper.cpp +++ b/UnityCore/GLibWrapper.cpp @@ -104,6 +104,11 @@ String::operator char*() return string_; } +String::operator std::string() +{ + return Str(); +} + String::operator bool() const { return bool(string_); diff --git a/UnityCore/GLibWrapper.h b/UnityCore/GLibWrapper.h index 2710d2880..9eb33c538 100644 --- a/UnityCore/GLibWrapper.h +++ b/UnityCore/GLibWrapper.h @@ -115,6 +115,7 @@ public: operator bool() const; operator char*(); + operator std::string(); gchar* Value(); std::string Str() const; diff --git a/UnityCore/HomeLens.cpp b/UnityCore/HomeLens.cpp new file mode 100644 index 000000000..b14f29b33 --- /dev/null +++ b/UnityCore/HomeLens.cpp @@ -0,0 +1,957 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* + * Copyright (C) 2012 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authored by: Mikkel Kamstrup Erlandsen <mikkel.kamstrup@canonical.com> + */ + +#include <glib.h> +#include <string> +#include <stdexcept> +#include <map> + +#include "GLibSignal.h" +#include "HomeLens.h" +#include "Lens.h" +#include "Model.h" + +#include "config.h" + +namespace unity +{ +namespace dash +{ + +namespace +{ + +nux::logging::Logger logger("unity.dash.homelens"); + +} + +/* + * Helper class that maps category offsets between the merged lens and + * source lenses. We also use it to merge categories from different lenses + * with the same display name into the same category. + * + * NOTE: The model pointers passed in are expected to be pointers to the + * result source models - and not the category source models! + */ +class HomeLens::CategoryRegistry +{ +public: + CategoryRegistry(HomeLens* owner) + : is_dirty_(false) + , owner_(owner) {} + + int FindCategoryOffset(DeeModel* model, unsigned int source_cat_offset) + { + glib::String c_id(g_strdup_printf("%u+%p", source_cat_offset, model)); + std::map<std::string,unsigned int>::iterator i = reg_by_id_.find(c_id); + + if (i != reg_by_id_.end()) + return i->second; + + return -1; + } + + int FindCategoryOffset(const gchar* display_name) + { + std::map<std::string,unsigned int>::iterator i = + reg_by_display_name_.find(display_name); + if (i != reg_by_display_name_.end()) + return i->second; + + return -1; + } + + /* Register a new category */ + void RegisterCategoryOffset(DeeModel* model, + unsigned int source_cat_offset, + const gchar* display_name, + unsigned int target_cat_offset) + { + glib::String c_id(g_strdup_printf("%u+%p", source_cat_offset, model)); + + std::map<std::string,unsigned int>::iterator i = reg_by_id_.find(c_id); + if (i != reg_by_id_.end()) + { + LOG_ERROR(logger) << "Category '" << c_id << "' already registered!"; + return; + } + + if (display_name != NULL) + { + i = reg_by_display_name_.find(display_name); + if (i != reg_by_display_name_.end()) + { + LOG_ERROR(logger) << "Category '" << display_name << "' already registered!"; + return; + } + } + + /* Any existing categories with offsets >= target_cat_offset must be + * pushed up. Update both maps by id and display name */ + std::map<std::string,unsigned int>::iterator end = reg_by_id_.end(); + for (i = reg_by_id_.begin(); i != end; i++) + { + if (i->second >= target_cat_offset) + { + i->second = i->second + 1; + is_dirty_ = true; + } + } + + for (i = reg_by_display_name_.begin(), end = reg_by_display_name_.end(); i != end; i++) + { + if (i->second >= target_cat_offset) + { + i->second = i->second + 1; + is_dirty_ = true; + } + } + + reg_by_id_[c_id] = target_cat_offset; + + /* Callers pass a NULL display_name when they already have a category + * with the right display registered */ + if (display_name != NULL) + { + reg_by_display_name_[display_name] = target_cat_offset; + LOG_DEBUG(logger) << "Registered category '" << display_name + << "' with source offset " << source_cat_offset + << " and target offset " << target_cat_offset + << ". Id " << c_id; + } + else + { + LOG_DEBUG(logger) << "Registered category with source offset " + << source_cat_offset << " and target offset " + << target_cat_offset << ". Id " << c_id; + } + } + + /* Associate a source results model and category offset with an existing + * target category offset */ + void AssociateCategoryOffset(DeeModel* model, + unsigned int source_cat_offset, + unsigned int target_cat_offset) + { + glib::String c_id(g_strdup_printf("%u+%p", source_cat_offset, model)); + + std::map<std::string,unsigned int>::iterator i = reg_by_id_.find(c_id); + if (i != reg_by_id_.end()) + { + LOG_ERROR(logger) << "Category '" << c_id << "' already registered!"; + return; + } + + reg_by_id_[c_id] = target_cat_offset; + } + + /** + * Returns true and resets the dirty state if the registry was dirty. + * When you've checked a dirty registry you must either clear the + * merged results model or recalibrate all category offset in it + * (and Unity probably wont support the latter?). + */ + bool CheckDirty() + { + return is_dirty_ ? (is_dirty_ = false, true) : false; + } + +private: + std::map<std::string,unsigned int> reg_by_id_; + std::map<std::string,unsigned int> reg_by_display_name_; + bool is_dirty_; + HomeLens* owner_; +}; + +/* + * Helper class that merges a set of DeeModels into one super model + */ +class HomeLens::ModelMerger : public sigc::trackable +{ +public: + ModelMerger(glib::Object<DeeModel> target); + virtual ~ModelMerger(); + + void AddSource(glib::Object<DeeModel> source); + +protected: + virtual void OnSourceRowAdded(DeeModel *model, DeeModelIter *iter); + virtual void OnSourceRowRemoved(DeeModel* model, DeeModelIter* iter); + virtual void OnSourceRowChanged(DeeModel* model, DeeModelIter* iter); + void EnsureRowBuf(DeeModel *source); + + /* The merge tag lives on the source models, pointing to the mapped + * row in the target model */ + DeeModelTag* FindSourceToTargetTag(DeeModel *model); + +protected: + glib::SignalManager sig_manager_; + GVariant** row_buf_; + unsigned int n_cols_; + glib::Object<DeeModel> target_; + std::map<DeeModel*,DeeModelTag*> source_to_target_tags_; +}; + +/* + * Specialized ModelMerger that takes care merging results models. + * We need special handling here because rows in each lens' results model + * specifies an offset into the lens' categories model where the display + * name of the category is defined. + * + * This class converts the offset of the source lens' categories into + * offsets into the merged category model. + * + * Each row added to the target is tagged with a pointer to the Lens instance + * from which it came + */ +class HomeLens::ResultsMerger : public ModelMerger +{ +public: + ResultsMerger(glib::Object<DeeModel> target, + HomeLens::CategoryRegistry* cat_registry); + +protected: + void OnSourceRowAdded(DeeModel *model, DeeModelIter *iter); + void OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter); + void OnSourceRowChanged(DeeModel *model, DeeModelIter *iter); + void CheckCategoryRegistryDirty(); + +private: + HomeLens::CategoryRegistry* cat_registry_; +}; + +/* + * Specialized ModelMerger that takes care merging category models. + * We need special handling here because rows in each lens' results model + * specifies an offset into the lens' categories model where the display + * name of the category is defined. + * + * This class records a map of the offsets from the original source category + * models to the offsets in the combined categories model. + */ +class HomeLens::CategoryMerger : public ModelMerger +{ +public: + CategoryMerger(glib::Object<DeeModel> target, + HomeLens::CategoryRegistry* cat_registry); + + void OnSourceRowAdded(DeeModel *model, DeeModelIter *iter); + void OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter); + +private: + HomeLens::CategoryRegistry* cat_registry_; + DeeModelTag* priority_tag_; +}; + +/* + * Pimpl for HomeLens + */ +class HomeLens::Impl : public sigc::trackable +{ +public: + Impl(HomeLens* owner); + ~Impl(); + + void OnLensAdded(Lens::Ptr& lens); + gsize FindLensPriority (Lens::Ptr& lens); + void EnsureCategoryAnnotation(Lens::Ptr& lens, DeeModel* results, DeeModel* categories); + Lens::Ptr FindLensForUri(std::string const& uri); + + HomeLens* owner_; + Lenses::LensList lenses_; + HomeLens::CategoryRegistry cat_registry_; + HomeLens::ResultsMerger results_merger_; + HomeLens::CategoryMerger categories_merger_; + HomeLens::ModelMerger filters_merger_; + int running_searches_; + glib::Object<GSettings> settings_; +}; + +/* + * IMPLEMENTATION + */ + +HomeLens::ModelMerger::ModelMerger(glib::Object<DeeModel> target) + : row_buf_(NULL) + , n_cols_(0) + , target_(target) +{} + +HomeLens::ResultsMerger::ResultsMerger(glib::Object<DeeModel> target, + CategoryRegistry *cat_registry) + : HomeLens::ModelMerger::ModelMerger(target) + , cat_registry_(cat_registry) +{} + +HomeLens::CategoryMerger::CategoryMerger(glib::Object<DeeModel> target, + CategoryRegistry *cat_registry) + : HomeLens::ModelMerger::ModelMerger(target) + , cat_registry_(cat_registry) + , priority_tag_(dee_model_register_tag(target, NULL)) +{} + +HomeLens::ModelMerger::~ModelMerger() +{ + if (row_buf_) + delete row_buf_; +} + +void HomeLens::ModelMerger::AddSource(glib::Object<DeeModel> source) +{ + typedef glib::Signal<void, DeeModel*, DeeModelIter*> RowSignalType; + + if (!source) + { + LOG_ERROR(logger) << "Trying to add NULL source to ModelMerger"; + return; + } + + DeeModelTag* merger_tag = dee_model_register_tag(source, NULL); + source_to_target_tags_[source.RawPtr()] = merger_tag; + + sig_manager_.Add(new RowSignalType(source.RawPtr(), + "row-added", + sigc::mem_fun(this, &HomeLens::ModelMerger::OnSourceRowAdded))); + + sig_manager_.Add(new RowSignalType(source.RawPtr(), + "row-removed", + sigc::mem_fun(this, &HomeLens::ModelMerger::OnSourceRowRemoved))); + + sig_manager_.Add(new RowSignalType(source.RawPtr(), + "row-changed", + sigc::mem_fun(this, &HomeLens::ModelMerger::OnSourceRowChanged))); +} + +void HomeLens::ModelMerger::OnSourceRowAdded(DeeModel *model, DeeModelIter *iter) +{ + // Default impl. does nothing. + // Note that the filters_merger_ relies on this behavior. Supporting + // filters on the home screen is possible, but *quite* tricky. + // So... + // Discard ALL the rows! +} + +void HomeLens::ResultsMerger::OnSourceRowAdded(DeeModel *model, DeeModelIter *iter) +{ + DeeModelIter* target_iter; + DeeModelTag* target_tag; + int target_cat_offset, source_cat_offset; + const unsigned int CATEGORY_COLUMN = 2; + + EnsureRowBuf(model); + CheckCategoryRegistryDirty(); + + dee_model_get_row (model, iter, row_buf_); + target_tag = FindSourceToTargetTag(model); + + /* Update the row with the corrected category offset */ + source_cat_offset = dee_model_get_uint32(model, iter, CATEGORY_COLUMN); + target_cat_offset = cat_registry_->FindCategoryOffset(model, source_cat_offset); + + if (target_cat_offset >= 0) + { + g_variant_unref (row_buf_[CATEGORY_COLUMN]); + row_buf_[CATEGORY_COLUMN] = g_variant_new_uint32(target_cat_offset); + + /* Sink the ref on the new row member. By Dee API contract they must all + * be strong refs, not floating */ + g_variant_ref_sink(row_buf_[CATEGORY_COLUMN]); + + target_iter = dee_model_append_row (target_, row_buf_); + dee_model_set_tag(model, iter, target_tag, target_iter); + + LOG_DEBUG(logger) << "Found " << dee_model_get_string(model, iter, 0) + << " (source cat " << source_cat_offset << ", target cat " + << target_cat_offset << ")"; + } + else + { + LOG_ERROR(logger) << "No category registered for model " + << model << ", source offset " << source_cat_offset + << ": " << dee_model_get_string(model, iter, 0); + } + + for (unsigned int i = 0; i < n_cols_; i++) g_variant_unref(row_buf_[i]); +} + +void HomeLens::CategoryMerger::OnSourceRowAdded(DeeModel *model, DeeModelIter *iter) +{ + DeeModel* results_model; + DeeModelIter* target_iter; + DeeModelIter* target_end; + DeeModelTag* target_tag; + int target_cat_offset, source_cat_offset; + const gchar* display_name; + const unsigned int DISPLAY_NAME_COLUMN = 0; + gsize lens_priority, prio; + + EnsureRowBuf(model); + + results_model = static_cast<DeeModel*>(g_object_get_data( + G_OBJECT(model), "unity-homelens-results-model")); + if (results_model == NULL) + { + LOG_DEBUG(logger) << "Category model " << model + << " does not have a results model yet"; + return; + } + + dee_model_get_row (model, iter, row_buf_); + target_tag = FindSourceToTargetTag(model); + source_cat_offset = dee_model_get_position(model, iter); + + /* If we already have a category registered with the same display name + * then we just use that. Otherwise register a new category for it */ + display_name = dee_model_get_string(model, iter, DISPLAY_NAME_COLUMN); + target_cat_offset = cat_registry_->FindCategoryOffset(display_name); + if (target_cat_offset >= 0) + { + cat_registry_->AssociateCategoryOffset(results_model, source_cat_offset, + target_cat_offset); + goto cleanup; + } + + /* + * Below we can assume that we have a genuinely new category. + * + * Our goal is to insert the category at a position suitable for its + * priority. We insert it as the last item in the set of items which + * have equal priority. + * + * We allow our selves to do linear inserts as we wont expect a lot + * of categories. + */ + + lens_priority = GPOINTER_TO_SIZE(g_object_get_data( + G_OBJECT(model), "unity-homelens-priority")); + + /* Seek correct position in the merged category model */ + target_iter = dee_model_get_first_iter(target_); + target_end = dee_model_get_last_iter(target_); + while (target_iter != target_end) + { + prio = GPOINTER_TO_SIZE(dee_model_get_tag(target_, target_iter, priority_tag_)); + if (lens_priority > prio) + break; + target_iter = dee_model_next(target_, target_iter); + } + + /* Add the row to the merged categories model and store required metadata */ + target_iter = dee_model_insert_row_before(target_, target_iter, row_buf_); + dee_model_set_tag(model, iter, target_tag, target_iter); + dee_model_set_tag(target_, target_iter, priority_tag_, GSIZE_TO_POINTER(lens_priority)); + target_cat_offset = dee_model_get_position(target_, target_iter); + cat_registry_->RegisterCategoryOffset(results_model, source_cat_offset, + display_name, target_cat_offset); + + cleanup: + for (unsigned int i = 0; i < n_cols_; i++) g_variant_unref(row_buf_[i]); +} + +void HomeLens::CategoryMerger::OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter) +{ + /* We don't support removals of categories. + * You can check out any time you like, but you can never leave + * + * The category registry code is spaghettified enough already. + * No more please. + */ + LOG_DEBUG(logger) << "Removal of categories not supported."; +} + +void HomeLens::ModelMerger::OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter) +{ + DeeModelIter* target_iter; + DeeModelTag* target_tag; + + EnsureRowBuf(model); + + target_tag = FindSourceToTargetTag(model); + target_iter = static_cast<DeeModelIter*>(dee_model_get_tag(model, + iter, + target_tag)); + + /* We might not have registered a target iter for the row. + * This fx. happens if we re-used a category based on display_name */ + if (target_iter != NULL) + dee_model_remove(target_, target_iter); +} + +void HomeLens::ResultsMerger::OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter) +{ + CheckCategoryRegistryDirty(); + ModelMerger::OnSourceRowRemoved(model, iter); +} + +void HomeLens::ModelMerger::OnSourceRowChanged(DeeModel *model, DeeModelIter *iter) +{ + DeeModelIter* target_iter; + DeeModelTag* target_tag; + + EnsureRowBuf(model); + + dee_model_get_row (model, iter, row_buf_); + target_tag = FindSourceToTargetTag(model); + target_iter = static_cast<DeeModelIter*>(dee_model_get_tag(model, + iter, + target_tag)); + + dee_model_set_row (target_, target_iter, row_buf_); + + for (unsigned int i = 0; i < n_cols_; i++) g_variant_unref(row_buf_[i]); +} + +void HomeLens::ResultsMerger::OnSourceRowChanged(DeeModel *model, DeeModelIter *iter) +{ + // FIXME: We can support this, but we need to re-calculate the category offset + LOG_WARN(logger) << "In-line changing of results not supported in the home lens. Sorry."; +} + +void HomeLens::ModelMerger::EnsureRowBuf(DeeModel *model) +{ + if (G_UNLIKELY (n_cols_ == 0)) + { + /* We have two things to accomplish here. + * 1) Allocate the row_buf_, and + * 2) Make sure that the target model has the correct schema set. + * + * INVARIANT: n_cols_ == 0 iff row_buf_ == NULL. + */ + + n_cols_ = dee_model_get_n_columns(model); + + if (n_cols_ == 0) + { + LOG_ERROR(logger) << "Source model has not provided a schema for the model merger!"; + return; + } + + /* Lazily adopt schema from source if we don't have one. + * If we do have a schema let's validate that they match the source */ + if (dee_model_get_n_columns(target_) == 0) + { + dee_model_set_schema_full(target_, + dee_model_get_schema(model, NULL), + n_cols_); + } + else + { + unsigned int n_cols1; + const gchar* const *schema1 = dee_model_get_schema(target_, &n_cols1); + const gchar* const *schema2 = dee_model_get_schema(model, NULL); + + /* At the very least we should have an equal number of rows */ + if (n_cols_ != n_cols1) + { + LOG_ERROR(logger) << "Schema mismatch between source and target model. Expected " + << n_cols1 << " columns, but found " + << n_cols_ << "."; + n_cols_ = 0; + return; + } + + /* Compare schemas */ + for (unsigned int i = 0; i < n_cols_; i++) + { + if (g_strcmp0(schema1[i], schema2[i]) != 0) + { + LOG_ERROR(logger) << "Schema mismatch between source and target model. Expected column " + << i << " to be '" << schema1[i] << "', but found '" + << schema2[i] << "'."; + n_cols_ = 0; + return; + } + } + } + + row_buf_ = g_new0 (GVariant*, n_cols_); + } +} + +DeeModelTag* HomeLens::ModelMerger::FindSourceToTargetTag(DeeModel *model) +{ + return source_to_target_tags_[model]; +} + +void HomeLens::ResultsMerger::CheckCategoryRegistryDirty() +{ + DeeModel* source; + DeeModelTag* target_tag; + const unsigned int CATEGORY_COLUMN = 2; + std::map<DeeModel*,DeeModelTag*>::iterator i, end; + + if (G_LIKELY(!cat_registry_->CheckDirty())) + return; + + LOG_DEBUG(logger) << "Category registry marked dirty. Fixing category offsets."; + + /* + * Iterate over all results in each source model and re-calculate the + * the category offset in the corresponding rows in the target model + */ + for (i = source_to_target_tags_.begin(), end = source_to_target_tags_.end(); + i != end; i++) + { + source = i->first; + target_tag = i->second; + + DeeModelIter* source_iter = dee_model_get_first_iter(source); + DeeModelIter* source_end = dee_model_get_last_iter(source); + + for (source_iter = dee_model_get_first_iter(source), source_end = dee_model_get_last_iter(source); + source_iter != source_end; + source_iter = dee_model_next(source, source_iter)) + { + DeeModelIter* target_iter = static_cast<DeeModelIter*>(dee_model_get_tag(source, source_iter, target_tag)); + + /* No guarantee that rows in the source are mapped to the target */ + if (target_iter == NULL) + continue; + + unsigned int source_cat_offset = dee_model_get_uint32(source, source_iter, CATEGORY_COLUMN); + int cat_offset = cat_registry_->FindCategoryOffset(source, source_cat_offset); + + if (G_LIKELY(cat_offset >= 0)) + { + dee_model_set_value(target_, target_iter, CATEGORY_COLUMN, + g_variant_new_uint32(cat_offset)); + } + else + { + LOG_ERROR(logger) << "No registered category id for category " + << source_cat_offset << " on result source model " + << source << "."; + /* We can't really recover from this :-( */ + } + } + } +} + +HomeLens::Impl::Impl(HomeLens *owner) + : owner_(owner) + , cat_registry_(owner) + , results_merger_(owner->results()->model(), &cat_registry_) + , categories_merger_(owner->categories()->model(), &cat_registry_) + , filters_merger_(owner->filters()->model()) + , running_searches_(0) + , settings_(g_settings_new("com.canonical.Unity.Dash")) +{ + DeeModel* results = owner->results()->model(); + if (dee_model_get_n_columns(results) == 0) + { + dee_model_set_schema(results, "s", "s", "u", "s", "s", "s", "s", NULL); + } + + DeeModel* categories = owner->categories()->model(); + if (dee_model_get_n_columns(categories) == 0) + { + dee_model_set_schema(categories, "s", "s", "s", "a{sv}", NULL); + } + + DeeModel* filters = owner->filters()->model(); + if (dee_model_get_n_columns(filters) == 0) + { + dee_model_set_schema(filters, "s", "s", "s", "s", "a{sv}", "b", "b", "b", NULL); + } +} + +HomeLens::Impl::~Impl() +{ + +} + +/*void HomeLens::Impl::CheckCategories() +{ + +}*/ + +gsize HomeLens::Impl::FindLensPriority (Lens::Ptr& lens) +{ + gchar** lenses = g_settings_get_strv(settings_, "home-lens-ordering"); + gsize pos = 0, len = g_strv_length(lenses); + + for (pos = 0; pos < len; pos++) + { + if (g_strcmp0(lenses[pos], lens->id().c_str()) == 0) + break; + } + + g_strfreev(lenses); + + return len - pos; +} + +void HomeLens::Impl::EnsureCategoryAnnotation (Lens::Ptr& lens, + DeeModel* categories, + DeeModel* results) +{ + if (categories && results) + { + if (!(DEE_IS_MODEL(results) && DEE_IS_MODEL(categories))) + { + LOG_ERROR(logger) << "The " + << std::string(DEE_IS_MODEL(results) ? "categories" : "results") + << " model is not a valid DeeModel. (" + << lens->id() << ")"; + return; + } + + g_object_set_data(G_OBJECT(categories), + "unity-homelens-results-model", + results); + + gsize lens_priority = FindLensPriority(lens); + g_object_set_data(G_OBJECT(categories), + "unity-homelens-priority", + GSIZE_TO_POINTER(lens_priority)); + + LOG_DEBUG(logger) << "Registering results model " << results + << " and lens priority " << lens_priority + << " on category model " << categories << ". (" + << lens->id() << ")"; + } +} + +Lens::Ptr HomeLens::Impl::FindLensForUri(std::string const& uri) +{ + /* We iterate over all lenses looking for the given uri in their + * global results. This might seem like a sucky approach, but it + * saves us from a ship load of book keeping */ + + for (auto lens : lenses_) + { + DeeModel* results = lens->global_results()->model(); + DeeModelIter* iter = dee_model_get_first_iter(results); + DeeModelIter* end = dee_model_get_last_iter(results); + const int URI_COLUMN = 0; + + while (iter != end) + { + if (g_strcmp0(uri.c_str(), dee_model_get_string(results, iter, URI_COLUMN)) == 0) + { + return lens; + } + iter = dee_model_next(results, iter); + } + } + + return Lens::Ptr(); +} + +// FIXME: Coordinated sorting between the lens bar and home screen categories. Make void FilesystemLenses::Impl::DecrementAndCheckChildrenWaiting() use the gsettings key +// FIXME: on no results https://bugs.launchpad.net/unity/+bug/711199 + +void HomeLens::Impl::OnLensAdded (Lens::Ptr& lens) +{ + lenses_.push_back (lens); + owner_->lens_added.emit(lens); + + /* When we dispatch a search we inc the search count and when we finish + * one we decrease it. When we reach 0 we'll emit search_finished. */ + lens->global_search_finished.connect([&] (Hints const& hints) { + running_searches_--; + + if (running_searches_ <= 0) + { + owner_->search_finished.emit(Hints()); + LOG_DEBUG(logger) << "Search finished"; + } + }); + + nux::ROProperty<glib::Object<DeeModel>>& results_prop = lens->global_results()->model; + nux::ROProperty<glib::Object<DeeModel>>& categories_prop = lens->categories()->model; + nux::ROProperty<glib::Object<DeeModel>>& filters_prop = lens->filters()->model; + + /* + * Important: We must ensure that the categories model is annotated + * with the results model in the "unity-homelens-results-model" + * data slot. We need it later to compute the transfermed offsets + * of the categories in the merged category model. + */ + + /* Most lenses add models lazily, but we can't know that; + * so try to see if we can add them up front */ + if (results_prop().RawPtr()) + { + EnsureCategoryAnnotation(lens, categories_prop(), results_prop()); + results_merger_.AddSource(results_prop()); + } + + if (categories_prop().RawPtr()) + { + EnsureCategoryAnnotation(lens, categories_prop(), results_prop()); + categories_merger_.AddSource(categories_prop()); + } + + if (filters_prop().RawPtr()) + filters_merger_.AddSource(filters_prop()); + + /* + * Pick it up when the lens set models lazily. + */ + results_prop.changed.connect([&] (glib::Object<DeeModel> model) + { + EnsureCategoryAnnotation(lens, lens->categories()->model(), model); + results_merger_.AddSource(model); + }); + + categories_prop.changed.connect([&] (glib::Object<DeeModel> model) + { + EnsureCategoryAnnotation(lens, model, lens->global_results()->model()); + categories_merger_.AddSource(model); + }); + + filters_prop.changed.connect([&] (glib::Object<DeeModel> model) + { + filters_merger_.AddSource(model); + }); + + /* + * Register pre-existing categories up front + * FIXME: Do the same for results? + */ + DeeModel* cats = categories_prop(); + DeeModelIter* cats_iter; + DeeModelIter* cats_end; + for (cats_iter = dee_model_get_first_iter(cats), cats_end = dee_model_get_last_iter(cats); + cats_iter != cats_end; + cats_iter = dee_model_next(cats, cats_iter)) + { + categories_merger_.OnSourceRowAdded(cats, cats_iter); + } +} + +HomeLens::HomeLens(std::string const& name, std::string const& description, std::string const& search_hint) + : Lens("home.lens", "", "", name, PKGDATADIR"/lens-nav-home.svg", + description, search_hint, true, "", + ModelType::LOCAL) + , pimpl(new Impl(this)) +{ + count.SetGetterFunction(sigc::mem_fun(&pimpl->lenses_, &Lenses::LensList::size)); + search_in_global = false; +} + +HomeLens::~HomeLens() +{ + delete pimpl; +} + +void HomeLens::AddLenses(Lenses& lenses) +{ + for (auto lens : lenses.GetLenses()) + { + pimpl->OnLensAdded(lens); + } + + lenses.lens_added.connect(sigc::mem_fun(pimpl, &HomeLens::Impl::OnLensAdded)); +} + +Lenses::LensList HomeLens::GetLenses() const +{ + return pimpl->lenses_; +} + +Lens::Ptr HomeLens::GetLens(std::string const& lens_id) const +{ + for (auto lens: pimpl->lenses_) + { + if (lens->id == lens_id) + { + return lens; + } + } + + return Lens::Ptr(); +} + +Lens::Ptr HomeLens::GetLensAtIndex(std::size_t index) const +{ + try + { + return pimpl->lenses_.at(index); + } + catch (std::out_of_range& error) + { + LOG_WARN(logger) << error.what(); + } + + return Lens::Ptr(); +} + +void HomeLens::GlobalSearch(std::string const& search_string) +{ + LOG_WARN(logger) << "Global search not enabled for HomeLens class." + << " Ignoring query '" << search_string << "'"; +} + +void HomeLens::Search(std::string const& search_string) +{ + LOG_DEBUG(logger) << "Search '" << search_string << "'"; + + /* Reset running search counter */ + pimpl->running_searches_ = 0; + + for (auto lens: pimpl->lenses_) + { + if (lens->search_in_global()) + { + LOG_DEBUG(logger) << " - Global search on '" << lens->id() << "' for '" + << search_string << "'"; + lens->view_type = ViewType::HOME_VIEW; + lens->GlobalSearch(search_string); + pimpl->running_searches_++; + } + } +} + +void HomeLens::Activate(std::string const& uri) +{ + LOG_DEBUG(logger) << "Activate '" << uri << "'"; + + Lens::Ptr lens = pimpl->FindLensForUri(uri); + + /* Fall back to default handling of URIs if no lens is found. + * - Although, this shouldn't really happen */ + if (lens) + { + LOG_DEBUG(logger) << "Activation request passed to '" << lens->id() << "'"; + lens->Activate(uri); + } + else + { + LOG_WARN(logger) << "Unable to find a lens for activating '" << uri + << "'. Using fallback activation."; + activated.emit(uri, HandledType::NOT_HANDLED, Hints()); + } +} + +void HomeLens::Preview(std::string const& uri) +{ + LOG_DEBUG(logger) << "Preview '" << uri << "'"; + + Lens::Ptr lens = pimpl->FindLensForUri(uri); + + if (lens) + lens->Preview(uri); + else + LOG_WARN(logger) << "Unable to find a lens for previewing '" << uri << "'"; +} + +} +} diff --git a/UnityCore/HomeLens.h b/UnityCore/HomeLens.h new file mode 100644 index 000000000..258318875 --- /dev/null +++ b/UnityCore/HomeLens.h @@ -0,0 +1,79 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* + * Copyright (C) 2012 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authored by: Mikkel Kamstrup Erlandsen <mikkel.kamstrup@canonical.com> + */ + +#ifndef UNITY_HOME_LENS_H +#define UNITY_HOME_LENS_H + +#include <vector> +#include <memory> +#include <sigc++/signal.h> +#include <sigc++/trackable.h> + +#include "Lenses.h" +#include "Lens.h" + +namespace unity +{ +namespace dash +{ + +/** + * A special Lens implementation that merges together a set of source Lens + * instances. + * + * NOTE: Changes in the filter models are currently not propagated back to the + * the source lenses. If we want to support filters on the dash home + * screen this needs to be addressed. + */ +class HomeLens : public Lens, public Lenses +{ +public: + typedef std::shared_ptr<HomeLens> Ptr; + + /** + * Should be constructed with i18n arguments: + * _("Home"), _("Home screen"), _("Search") + */ + HomeLens(std::string const& name, std::string const& description, std::string const& search_hint); + virtual ~HomeLens(); + + void AddLenses(Lenses& lenses); + + Lenses::LensList GetLenses() const; + Lens::Ptr GetLens(std::string const& lens_id) const; + Lens::Ptr GetLensAtIndex(std::size_t index) const; + + void GlobalSearch(std::string const& search_string); + void Search(std::string const& search_string); + void Activate(std::string const& uri); + void Preview(std::string const& uri); + +private: + class Impl; + class ModelMerger; + class ResultsMerger; + class CategoryMerger; + class CategoryRegistry; + Impl* pimpl; +}; + +} +} + +#endif diff --git a/UnityCore/Hud.cpp b/UnityCore/Hud.cpp index f9a164adc..b4a879c38 100644 --- a/UnityCore/Hud.cpp +++ b/UnityCore/Hud.cpp @@ -151,11 +151,11 @@ void HudImpl::BuildQueries(GVariant* query_array) while (g_variant_iter_loop(&iter, "(sssssv)", &formatted_text, &icon, &item_icon, &completion_text, &shortcut, &key)) { - queries_.push_back(Query::Ptr(new Query(std::string(formatted_text), - std::string(icon), - std::string(item_icon), - std::string(completion_text), - std::string(shortcut), + queries_.push_back(Query::Ptr(new Query(formatted_text, + icon, + item_icon, + completion_text, + shortcut, key))); } } diff --git a/UnityCore/Lens.cpp b/UnityCore/Lens.cpp index c3d75459a..b55f16ab4 100644 --- a/UnityCore/Lens.cpp +++ b/UnityCore/Lens.cpp @@ -51,11 +51,12 @@ public: string const& description, string const& search_hint, bool visible, - string const& shortcut); + string const& shortcut, + ModelType model_type); ~Impl(); - void OnProxyConnected(); + void OnProxyConnectionChanged(); void OnProxyDisconnected(); void ResultsModelUpdated(unsigned long long begin_seqnum, @@ -94,6 +95,7 @@ public: string const& search_hint() const; bool visible() const; bool search_in_global() const; + bool set_search_in_global(bool val); string const& shortcut() const; Results::Ptr const& results() const; Results::Ptr const& global_results() const; @@ -121,7 +123,7 @@ public: string private_connection_name_; - glib::DBusProxy proxy_; + glib::DBusProxy* proxy_; glib::Object<GCancellable> search_cancellable_; glib::Object<GCancellable> global_search_cancellable_; @@ -138,7 +140,8 @@ Lens::Impl::Impl(Lens* owner, string const& description, string const& search_hint, bool visible, - string const& shortcut) + string const& shortcut, + ModelType model_type) : owner_(owner) , id_(id) , dbus_name_(dbus_name) @@ -150,20 +153,46 @@ Lens::Impl::Impl(Lens* owner, , visible_(visible) , search_in_global_(false) , shortcut_(shortcut) - , results_(new Results()) - , global_results_(new Results()) - , categories_(new Categories()) - , filters_(new Filters()) + , results_(new Results(model_type)) + , global_results_(new Results(model_type)) + , categories_(new Categories(model_type)) + , filters_(new Filters(model_type)) , connected_(false) - , proxy_(dbus_name, dbus_path, "com.canonical.Unity.Lens") + , proxy_(NULL) , results_variant_(NULL) , global_results_variant_(NULL) { - proxy_.connected.connect(sigc::mem_fun(this, &Lens::Impl::OnProxyConnected)); - proxy_.disconnected.connect(sigc::mem_fun(this, &Lens::Impl::OnProxyDisconnected)); - proxy_.Connect("Changed", sigc::mem_fun(this, &Lens::Impl::OnChanged)); + if (model_type == ModelType::REMOTE) + { + proxy_ = new glib::DBusProxy(dbus_name, dbus_path, "com.canonical.Unity.Lens"); + proxy_->connected.connect(sigc::mem_fun(this, &Lens::Impl::OnProxyConnectionChanged)); + proxy_->disconnected.connect(sigc::mem_fun(this, &Lens::Impl::OnProxyDisconnected)); + proxy_->Connect("Changed", sigc::mem_fun(this, &Lens::Impl::OnChanged)); + } + + /* Technically these signals will only be fired by remote models, but we + * connect them no matter the ModelType. Dee may grow support in the future. + */ results_->end_transaction.connect(sigc::mem_fun(this, &Lens::Impl::ResultsModelUpdated)); global_results_->end_transaction.connect(sigc::mem_fun(this, &Lens::Impl::GlobalResultsModelUpdated)); + + owner_->id.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::id)); + owner_->dbus_name.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::dbus_name)); + owner_->dbus_path.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::dbus_path)); + owner_->name.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::name)); + owner_->icon_hint.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::icon_hint)); + owner_->description.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::description)); + owner_->search_hint.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::search_hint)); + owner_->visible.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::visible)); + owner_->search_in_global.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::search_in_global)); + owner_->search_in_global.SetSetterFunction(sigc::mem_fun(this, &Lens::Impl::set_search_in_global)); + owner_->shortcut.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::shortcut)); + owner_->results.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::results)); + owner_->global_results.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::global_results)); + owner_->categories.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::categories)); + owner_->filters.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::filters)); + owner_->connected.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::connected)); + owner_->view_type.changed.connect(sigc::mem_fun(this, &Lens::Impl::OnViewTypeChanged)); } Lens::Impl::~Impl() @@ -176,13 +205,19 @@ Lens::Impl::~Impl() { g_cancellable_cancel (global_search_cancellable_); } + + if (proxy_) + delete proxy_; } -void Lens::Impl::OnProxyConnected() +void Lens::Impl::OnProxyConnectionChanged() { - proxy_.Call("InfoRequest"); - ViewType current_view_type = owner_->view_type; - proxy_.Call("SetViewType", g_variant_new("(u)", current_view_type)); + if (proxy_->IsConnected()) + { + proxy_->Call("InfoRequest"); + ViewType current_view_type = owner_->view_type; + proxy_->Call("SetViewType", g_variant_new("(u)", current_view_type)); + } } void Lens::Impl::OnProxyDisconnected() @@ -387,12 +422,13 @@ void Lens::Impl::UpdateProperties(bool search_in_global, void Lens::Impl::OnViewTypeChanged(ViewType view_type) { - proxy_.Call("SetViewType", g_variant_new("(u)", view_type)); + if (proxy_ && proxy_->IsConnected()) + proxy_->Call("SetViewType", g_variant_new("(u)", view_type)); } void Lens::Impl::GlobalSearch(std::string const& search_string) { - LOG_DEBUG(logger) << "Global Searching " << id_ << " for " << search_string; + LOG_DEBUG(logger) << "Global Searching '" << id_ << "' for '" << search_string << "'"; GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}")); @@ -407,7 +443,7 @@ void Lens::Impl::GlobalSearch(std::string const& search_string) global_results_variant_ = NULL; } - proxy_.Call("GlobalSearch", + proxy_->Call("GlobalSearch", g_variant_new("(sa{sv})", search_string.c_str(), &b), @@ -418,7 +454,13 @@ void Lens::Impl::GlobalSearch(std::string const& search_string) void Lens::Impl::Search(std::string const& search_string) { - LOG_DEBUG(logger) << "Searching " << id_ << " for " << search_string; + LOG_DEBUG(logger) << "Searching '" << id_ << "' for '" << search_string << "'"; + + if (!proxy_->IsConnected()) + { + LOG_DEBUG(logger) << "Skipping search. Proxy not connected. ('" << id_ << "')"; + return; + } GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}")); @@ -432,23 +474,29 @@ void Lens::Impl::Search(std::string const& search_string) results_variant_ = NULL; } - proxy_.Call("Search", - g_variant_new("(sa{sv})", - search_string.c_str(), - &b), - sigc::mem_fun(this, &Lens::Impl::OnSearchFinished), - search_cancellable_); + proxy_->Call("Search", + g_variant_new("(sa{sv})", + search_string.c_str(), + &b), + sigc::mem_fun(this, &Lens::Impl::OnSearchFinished), + search_cancellable_); g_variant_builder_clear(&b); } void Lens::Impl::Activate(std::string const& uri) { - LOG_DEBUG(logger) << "Activating " << uri << " on " << id_; + LOG_DEBUG(logger) << "Activating '" << uri << "' on '" << id_ << "'"; + + if (!proxy_->IsConnected()) + { + LOG_DEBUG(logger) << "Skipping activation. Proxy not connected. ('" << id_ << "')"; + return; + } - proxy_.Call("Activate", - g_variant_new("(su)", uri.c_str(), 0), - sigc::mem_fun(this, &Lens::Impl::ActivationReply)); + proxy_->Call("Activate", + g_variant_new("(su)", uri.c_str(), 0), + sigc::mem_fun(this, &Lens::Impl::ActivationReply)); } void Lens::Impl::ActivationReply(GVariant* parameters) @@ -468,11 +516,17 @@ void Lens::Impl::ActivationReply(GVariant* parameters) void Lens::Impl::Preview(std::string const& uri) { - LOG_DEBUG(logger) << "Previewing " << uri << " on " << id_; + LOG_DEBUG(logger) << "Previewing '" << uri << "' on '" << id_ << "'"; + + if (!proxy_->IsConnected()) + { + LOG_DEBUG(logger) << "Skipping preview. Proxy not connected. ('" << id_ << "')"; + return; + } - proxy_.Call("Preview", - g_variant_new("(s)", uri.c_str()), - sigc::mem_fun(this, &Lens::Impl::PreviewReply)); + proxy_->Call("Preview", + g_variant_new("(s)", uri.c_str()), + sigc::mem_fun(this, &Lens::Impl::PreviewReply)); } void Lens::Impl::PreviewReply(GVariant* parameters) @@ -535,6 +589,17 @@ bool Lens::Impl::search_in_global() const return search_in_global_; } +bool Lens::Impl::set_search_in_global(bool val) +{ + if (search_in_global_ != val) + { + search_in_global_ = val; + owner_->search_in_global.EmitChanged(val); + } + + return search_in_global_; +} + string const& Lens::Impl::shortcut() const { return shortcut_; @@ -584,25 +649,33 @@ Lens::Lens(string const& id_, description_, search_hint_, visible_, - shortcut_)) -{ - id.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::id)); - dbus_name.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::dbus_name)); - dbus_path.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::dbus_path)); - name.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::name)); - icon_hint.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::icon_hint)); - description.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::description)); - search_hint.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::search_hint)); - visible.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::visible)); - search_in_global.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::search_in_global)); - shortcut.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::shortcut)); - results.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::results)); - global_results.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::global_results)); - categories.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::categories)); - filters.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::filters)); - connected.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::connected)); - view_type.changed.connect(sigc::mem_fun(pimpl, &Lens::Impl::OnViewTypeChanged)); -} + shortcut_, + ModelType::REMOTE)) +{} + +Lens::Lens(string const& id_, + string const& dbus_name_, + string const& dbus_path_, + string const& name_, + string const& icon_hint_, + string const& description_, + string const& search_hint_, + bool visible_, + string const& shortcut_, + ModelType model_type) + + : pimpl(new Impl(this, + id_, + dbus_name_, + dbus_path_, + name_, + icon_hint_, + description_, + search_hint_, + visible_, + shortcut_, + model_type)) +{} Lens::~Lens() { diff --git a/UnityCore/Lens.h b/UnityCore/Lens.h index cc145f7d9..886665211 100644 --- a/UnityCore/Lens.h +++ b/UnityCore/Lens.h @@ -67,12 +67,23 @@ public: bool visible = true, std::string const& shortcut = ""); - ~Lens(); - - void GlobalSearch(std::string const& search_string); - void Search(std::string const& search_string); - void Activate(std::string const& uri); - void Preview(std::string const& uri); + Lens(std::string const& id, + std::string const& dbus_name, + std::string const& dbus_path, + std::string const& name, + std::string const& icon, + std::string const& description, + std::string const& search_hint, + bool visible, + std::string const& shortcut, + ModelType model_type); + + virtual ~Lens(); + + virtual void GlobalSearch(std::string const& search_string); + virtual void Search(std::string const& search_string); + virtual void Activate(std::string const& uri); + virtual void Preview(std::string const& uri); nux::RWProperty<std::string> id; nux::RWProperty<std::string> dbus_name; diff --git a/UnityCore/Model-inl.h b/UnityCore/Model-inl.h index 7991c477d..ee5b93895 100644 --- a/UnityCore/Model-inl.h +++ b/UnityCore/Model-inl.h @@ -34,10 +34,28 @@ nux::logging::Logger _model_inl_logger("unity.dash.model"); template<class RowAdaptor> Model<RowAdaptor>::Model() + : model_type_(ModelType::REMOTE) +{ + Init(); +} + +template<class RowAdaptor> +Model<RowAdaptor>::Model (ModelType model_type) + : model_type_(model_type) +{ + Init(); + + if (model_type == ModelType::LOCAL) + swarm_name = ":local"; +} + +template<class RowAdaptor> +void Model<RowAdaptor>::Init () { swarm_name.changed.connect(sigc::mem_fun(this, &Model<RowAdaptor>::OnSwarmNameChanged)); count.SetGetterFunction(sigc::mem_fun(this, &Model<RowAdaptor>::get_count)); seqnum.SetGetterFunction(sigc::mem_fun(this, &Model<RowAdaptor>::get_seqnum)); + model.SetGetterFunction(sigc::mem_fun(this, &Model<RowAdaptor>::get_model)); } template<class RowAdaptor> @@ -52,7 +70,29 @@ void Model<RowAdaptor>::OnSwarmNameChanged(std::string const& swarm_name) if (model_) dee_model_clear(model_); - model_ = dee_shared_model_new(swarm_name.c_str()); + switch(model_type_) + { + case ModelType::LOCAL: + model_ = dee_sequence_model_new(); + break; + case ModelType::REMOTE: + model_ = dee_shared_model_new(swarm_name.c_str()); + sig_manager_.Add(new TransactionSignalType(model_, + "begin-transaction", + sigc::mem_fun(this, &Model<RowAdaptor>::OnTransactionBegin))); + + sig_manager_.Add(new TransactionSignalType(model_, + "end-transaction", + sigc::mem_fun(this, &Model<RowAdaptor>::OnTransactionEnd))); + break; + default: + LOG_ERROR(_model_inl_logger) << "Unexpected ModelType " << model_type_; + break; + } + + model.EmitChanged(model_); + + renderer_tag_ = dee_model_register_tag(model_, NULL); sig_manager_.Add(new RowSignalType(model_, @@ -66,14 +106,6 @@ void Model<RowAdaptor>::OnSwarmNameChanged(std::string const& swarm_name) sig_manager_.Add(new RowSignalType(model_, "row-removed", sigc::mem_fun(this, &Model<RowAdaptor>::OnRowRemoved))); - - sig_manager_.Add(new TransactionSignalType(model_, - "begin-transaction", - sigc::mem_fun(this, &Model<RowAdaptor>::OnTransactionBegin))); - - sig_manager_.Add(new TransactionSignalType(model_, - "end-transaction", - sigc::mem_fun(this, &Model<RowAdaptor>::OnTransactionEnd))); } template<class RowAdaptor> @@ -148,6 +180,12 @@ unsigned long long Model<RowAdaptor>::get_seqnum() return 0; } +template<class RowAdaptor> +glib::Object<DeeModel> Model<RowAdaptor>::get_model() +{ + return model_; +} + } } diff --git a/UnityCore/Model.h b/UnityCore/Model.h index f7f436094..8eb4d14b2 100644 --- a/UnityCore/Model.h +++ b/UnityCore/Model.h @@ -35,6 +35,12 @@ namespace unity namespace dash { +enum ModelType +{ + REMOTE, + LOCAL +}; + /* This template class encapsulates the basics of talking to a DeeSharedModel, * however it is a template as you can choose your own RowAdaptor (see * ResultsRowAdaptor.h for an example) which then presents the data in the rows @@ -47,6 +53,7 @@ public: typedef std::shared_ptr<Model> Ptr; Model(); + Model (ModelType model_type); virtual ~Model(); const RowAdaptor RowAtIndex(std::size_t index); @@ -54,6 +61,7 @@ public: nux::Property<std::string> swarm_name; nux::ROProperty<std::size_t> count; nux::ROProperty<unsigned long long> seqnum; + nux::ROProperty<glib::Object<DeeModel>> model; sigc::signal<void, RowAdaptor&> row_added; sigc::signal<void, RowAdaptor&> row_changed; @@ -63,6 +71,7 @@ public: sigc::signal<void, unsigned long long, unsigned long long> end_transaction; private: + void Init(); void OnRowAdded(DeeModel* model, DeeModelIter* iter); void OnRowChanged(DeeModel* model, DeeModelIter* iter); void OnRowRemoved(DeeModel* model, DeeModelIter* iter); @@ -71,11 +80,13 @@ private: void OnSwarmNameChanged(std::string const& swarm_name); std::size_t get_count(); unsigned long long get_seqnum(); + glib::Object<DeeModel> get_model(); private: glib::Object<DeeModel> model_; glib::SignalManager sig_manager_; DeeModelTag* renderer_tag_; + ModelType model_type_; }; } diff --git a/UnityCore/Results.cpp b/UnityCore/Results.cpp index d59302c4e..b38c8e77e 100644 --- a/UnityCore/Results.cpp +++ b/UnityCore/Results.cpp @@ -31,6 +31,14 @@ Results::Results() row_removed.connect(sigc::mem_fun(this, &Results::OnRowRemoved)); } +Results::Results(ModelType model_type) + : Model<Result>::Model(model_type) +{ + row_added.connect(sigc::mem_fun(this, &Results::OnRowAdded)); + row_changed.connect(sigc::mem_fun(this, &Results::OnRowChanged)); + row_removed.connect(sigc::mem_fun(this, &Results::OnRowRemoved)); +} + void Results::OnRowAdded(Result& result) { result_added.emit(result); diff --git a/UnityCore/Results.h b/UnityCore/Results.h index a6d48e4a8..ca98b9b95 100644 --- a/UnityCore/Results.h +++ b/UnityCore/Results.h @@ -36,6 +36,7 @@ public: typedef std::shared_ptr<Results> Ptr; Results(); + Results(ModelType model_type); sigc::signal<void, Result const&> result_added; sigc::signal<void, Result const&> result_changed; diff --git a/com.canonical.Unity.gschema.xml b/com.canonical.Unity.gschema.xml index 6aa7f3058..bb783b7d6 100644 --- a/com.canonical.Unity.gschema.xml +++ b/com.canonical.Unity.gschema.xml @@ -21,7 +21,7 @@ <description>Whether the home screen should be expanded.</description> </key> </schema> - <schema path="/desktop/unity/launcher/" id="com.canonical.Unity.Launcher" gettext-domain="unity"> + <schema path="/desktop/unity/launcher/" id="com.canonical.Unity.Launcher" gettext-domain="unity"> <key type="as" name="favorites"> <default>[ 'ubiquity-gtkui.desktop', 'nautilus-home.desktop', 'firefox.desktop', 'libreoffice-writer.desktop', 'libreoffice-calc.desktop', 'libreoffice-impress.desktop', 'ubuntu-software-center.desktop', 'ubuntuone-installer.desktop', 'gnome-control-center.desktop' ]</default> <summary>List of desktop file ids for favorites on the launcher.</summary> @@ -33,7 +33,7 @@ <description>This is a detection key for the favorite migration script to know whether the needed migration is done or not.</description> </key> </schema> - <schema path="/desktop/unity/panel/" id="com.canonical.Unity.Panel" gettext-domain="unity"> + <schema path="/desktop/unity/panel/" id="com.canonical.Unity.Panel" gettext-domain="unity"> <key type="as" name="systray-whitelist"> <default>[ 'JavaEmbeddedFrame', 'Wine', 'scp-dbus-service', 'Update-notifier' ]</default> <summary>List of client names, resource classes or wm classes to allow in the Panel's systray implementation.</summary> @@ -46,5 +46,12 @@ <summary>List of device uuid for favorites on the launcher.</summary> <description>These devices are shown in the Launcher by default.</description> </key> - </schema> + </schema> + <schema path="/desktop/unity/dash/" id="com.canonical.Unity.Dash" gettext-domain="unity"> + <key type="as" name="home-lens-ordering"> + <default>[ 'applications.lens', 'files.lens', 'music.lens' ]</default> + <summary>List of lens ids specifying how lenses should be ordered in the Dash home screen.</summary> + <description>The categories listed on the Dash home screen will be ordered according to this list. Lenses not appearing in this list will not have any particular ordering and will always sort after lenses specified in this list.</description> + </key> + </schema> </schemalist> diff --git a/manual-tests/AutoMaximize.txt b/manual-tests/AutoMaximize.txt new file mode 100644 index 000000000..dc375888c --- /dev/null +++ b/manual-tests/AutoMaximize.txt @@ -0,0 +1,19 @@ +auto-maximize window +======================= +Thit test make sure that the auto-maximize window feature is disabled for +resolution above the 1024x600. + +- Open the display panel +- Set a resolution greater then 1024x600 (e.g 1366x768) +- Open a window, that usually doesn't start maximized + +Outcome +The window should not be in the maximized state. + +- Open the display panel +- Set a resolution lower or equal to 1024x600. +- Open the ccsm and change the automaximize-threshold option to 20 (experimental-tab of unity-plugin). +- Open nautilus, that usually doesn't start maximized + +Outcome +The window should be in the maximized state. diff --git a/manual-tests/Dash.txt b/manual-tests/Dash.txt index 7869d8d3c..116defd76 100644 --- a/manual-tests/Dash.txt +++ b/manual-tests/Dash.txt @@ -36,3 +36,18 @@ Outcome The dropped icon should not have the keyboard focus and should not remain enlightened. +Dash SearchBar middle-click +--------------------------- +This test shows how the middle click over the dash search bar should work +(see lp:842462) + +#. Open a text editor, and write some dummy text +#. Select some text part of the written dummy text with mouse +#. Press Super or Alt+F2 to open the Dash +#. Move the mouse over the dash search bar +#. Press middle click to paste the content of your primary clipboard + +Outcome + The text previously selected is pasted on the search bar at mouse pointer + position, if the operation is repeated the text is inserted where + the mouse pointer is. diff --git a/plugins/unityshell/resources/dash-widgets.json b/plugins/unityshell/resources/dash-widgets.json index 86f60fdf0..11da04b10 100644 --- a/plugins/unityshell/resources/dash-widgets.json +++ b/plugins/unityshell/resources/dash-widgets.json @@ -40,14 +40,14 @@ "icon-gap" : 40}, "button-label": { - "border-opacity" : [ 1.0, 0.15, 0.15, 0.15, 0.15], + "border-opacity" : [ 0.8, 0.13, 0.13, 0.13, 0.13], "border-color" : ["#ffffff", "#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF"], "border-size" : [ 2.0, 1.0, 1.0, 0.5, 0.5], "text-size" : 1.0, "text-color" : ["#ffffff", "#ffffff", "#ffffff", "#ffffff", "#ffffff"], "text-opacity" : [ 1.0, 1.0, 1.0, 1.0, 1.0], "fill-color" : ["#FFFFFF", "#000000", "#000000", "#000000", "#000000"], - "fill-opacity" : [ 0.15, 0.0, 0.0, 0.0, 0.0], + "fill-opacity" : [ 0.13, 0.0, 0.0, 0.0, 0.0], "overlay-opacity": [ 0.1, 0.1, 0.1, 0.0, 0.0], "overlay-mode" : [ "normal", "normal", "normal", "normal", "normal"], "blur-size" : [ 1, 1, 1, 0, 0]}, diff --git a/plugins/unityshell/src/BamfLauncherIcon.cpp b/plugins/unityshell/src/BamfLauncherIcon.cpp index a136d2114..fa4f007fb 100644 --- a/plugins/unityshell/src/BamfLauncherIcon.cpp +++ b/plugins/unityshell/src/BamfLauncherIcon.cpp @@ -871,7 +871,7 @@ void BamfLauncherIcon::Quit() g_list_free(children); } -void BamfLauncherIcon::Stick() +void BamfLauncherIcon::Stick(bool save) { BamfView* view = BAMF_VIEW(m_App); @@ -881,7 +881,7 @@ void BamfLauncherIcon::Stick() const gchar* desktop_file = DesktopFile(); bamf_view_set_sticky(view, true); - if (desktop_file && strlen(desktop_file) > 0) + if (save && desktop_file && strlen(desktop_file) > 0) FavoriteStore::GetDefault().AddFavorite(desktop_file, -1); } diff --git a/plugins/unityshell/src/BamfLauncherIcon.h b/plugins/unityshell/src/BamfLauncherIcon.h index 4dc0cc0eb..24c1e053a 100644 --- a/plugins/unityshell/src/BamfLauncherIcon.h +++ b/plugins/unityshell/src/BamfLauncherIcon.h @@ -46,9 +46,9 @@ public: const char* DesktopFile(); bool IsSticky(); - void Quit(); - void Stick(); + void Stick(bool save = true); void UnStick(); + void Quit(); void ActivateLauncherIcon(ActionArg arg); diff --git a/plugins/unityshell/src/DashView.cpp b/plugins/unityshell/src/DashView.cpp index 36cf739d6..cc1b7807e 100644 --- a/plugins/unityshell/src/DashView.cpp +++ b/plugins/unityshell/src/DashView.cpp @@ -47,6 +47,7 @@ NUX_IMPLEMENT_OBJECT_TYPE(DashView); DashView::DashView() : nux::View(NUX_TRACKER_LOCATION) + , home_lens_(new HomeLens(_("Home"), _("Home screen"), _("Search"))) , active_lens_view_(0) , last_activated_uri_("") , searching_timeout_id_(0) @@ -67,6 +68,8 @@ DashView::DashView() mouse_down.connect(sigc::mem_fun(this, &DashView::OnMouseButtonDown)); Relayout(); + + home_lens_->AddLenses(lenses_); lens_bar_->Activate("home.lens"); } @@ -81,6 +84,23 @@ void DashView::AboutToShow() ubus_manager_.SendMessage(UBUS_BACKGROUND_REQUEST_COLOUR_EMIT); visible_ = true; search_bar_->text_entry()->SelectAll(); + + /* Give the lenses a chance to prep data before we map them */ + lens_bar_->Activate(active_lens_view_->lens()->id()); + if (active_lens_view_->lens()->id() == "home.lens") + { + for (auto lens : lenses_.GetLenses()) + { + lens->view_type = ViewType::HOME_VIEW; + LOG_DEBUG(logger) << "Setting ViewType " << ViewType::HOME_VIEW + << " on '" << lens->id() << "'"; + } + + home_lens_->view_type = ViewType::LENS_VIEW; + LOG_DEBUG(logger) << "Setting ViewType " << ViewType::LENS_VIEW + << " on '" << home_lens_->id() << "'"; + } + renderer_.AboutToShow(); } @@ -88,6 +108,17 @@ void DashView::AboutToHide() { visible_ = false; renderer_.AboutToHide(); + + for (auto lens : lenses_.GetLenses()) + { + lens->view_type = ViewType::HIDDEN; + LOG_DEBUG(logger) << "Setting ViewType " << ViewType::HIDDEN + << " on '" << lens->id() << "'"; + } + + home_lens_->view_type = ViewType::HIDDEN; + LOG_DEBUG(logger) << "Setting ViewType " << ViewType::HIDDEN + << " on '" << home_lens_->id() << "'"; } void DashView::SetupViews() @@ -111,9 +142,9 @@ void DashView::SetupViews() lenses_layout_ = new nux::VLayout(); content_layout_->AddView(lenses_layout_, 1, nux::MINOR_POSITION_LEFT); - home_view_ = new HomeView(); + home_view_ = new LensView(home_lens_); active_lens_view_ = home_view_; - lens_views_["home.lens"] = home_view_; + lens_views_[home_lens_->id] = home_view_; lenses_layout_->AddView(home_view_); lens_bar_ = new LensBar(); @@ -239,7 +270,6 @@ void DashView::OnActivateRequest(GVariant* args) std::string id = AnalyseLensURI(uri.Str()); - home_view_->search_string = ""; lens_bar_->Activate(id); if ((id == "home.lens" && handled_type != GOTO_DASH_URI ) || !visible_) @@ -336,7 +366,6 @@ void DashView::OnLensAdded(Lens::Ptr& lens) { std::string id = lens->id; lens_bar_->AddLens(lens); - home_view_->AddLens(lens); LensView* view = new LensView(lens); view->SetVisible(false); @@ -362,15 +391,17 @@ void DashView::OnLensBarActivated(std::string const& id) for (auto it: lens_views_) { bool id_matches = it.first == id; + ViewType view_type = id_matches ? LENS_VIEW : (view == home_view_ ? HOME_VIEW : HIDDEN); it.second->SetVisible(id_matches); - it.second->view_type = id_matches ? LENS_VIEW : (view == home_view_ ? HOME_VIEW : HIDDEN); + it.second->view_type = view_type; + + LOG_DEBUG(logger) << "Setting ViewType " << view_type + << " on '" << it.first << "'"; } search_bar_->search_string = view->search_string; - if (view != home_view_) - search_bar_->search_hint = view->lens()->search_hint; - else - search_bar_->search_hint = _("Search"); + search_bar_->search_hint = view->lens()->search_hint; + bool expanded = view->filters_expanded; search_bar_->showing_filters = expanded; @@ -553,6 +584,7 @@ bool DashView::InspectKeyEvent(unsigned int eventType, ubus_manager_.SendMessage(UBUS_PLACE_VIEW_CLOSE_REQUEST); else search_bar_->search_string = ""; + return true; } return false; diff --git a/plugins/unityshell/src/DashView.h b/plugins/unityshell/src/DashView.h index bc3d16277..f23ce2d0f 100644 --- a/plugins/unityshell/src/DashView.h +++ b/plugins/unityshell/src/DashView.h @@ -27,10 +27,10 @@ #include <Nux/View.h> #include <Nux/VLayout.h> #include <UnityCore/FilesystemLenses.h> +#include <UnityCore/HomeLens.h> #include "BackgroundEffectHelper.h" #include "SearchBar.h" -#include "HomeView.h" #include "Introspectable.h" #include "LensBar.h" #include "LensView.h" @@ -95,6 +95,7 @@ private: std::string AnalyseLensURI(std::string const& uri); void UpdateLensFilter(std::string lens, std::string filter, std::string value); void UpdateLensFilterValue(Filter::Ptr filter, std::string value); + void EnsureLensesInitialized(); bool AcceptKeyNavFocus(); bool InspectKeyEvent(unsigned int eventType, unsigned int key_sym, const char* character); @@ -108,6 +109,7 @@ private: private: UBusManager ubus_manager_; FilesystemLenses lenses_; + HomeLens::Ptr home_lens_; LensViews lens_views_; @@ -118,7 +120,7 @@ private: nux::VLayout* lenses_layout_; LensBar* lens_bar_; - HomeView* home_view_; + LensView* home_view_; LensView* active_lens_view_; // Drawing related diff --git a/plugins/unityshell/src/FavoriteStore.h b/plugins/unityshell/src/FavoriteStore.h index 237e13582..ec6aa605e 100644 --- a/plugins/unityshell/src/FavoriteStore.h +++ b/plugins/unityshell/src/FavoriteStore.h @@ -34,7 +34,7 @@ namespace unity // Use GetDefault () to get the correct store for the session typedef std::list<std::string> FavoriteList; -class FavoriteStore : boost::noncopyable +class FavoriteStore : public sigc::trackable, boost::noncopyable { public: virtual ~FavoriteStore(); @@ -54,12 +54,12 @@ public: virtual void SetFavorites(FavoriteList const& desktop_paths) = 0; // Signals - // Therse only emit if something has changed the GSettings object externally + // These only emit if something has changed the GSettings object externally - //desktop_path, position - sigc::signal<void, std::string, int> favorite_added; + //desktop_path, position, before/after + sigc::signal<void, std::string const&, std::string const&, bool> favorite_added; //desktop_path - sigc::signal<void, std::string> favorite_removed; + sigc::signal<void, std::string const&> favorite_removed; sigc::signal<void> reordered; }; diff --git a/plugins/unityshell/src/FavoriteStoreGSettings.cpp b/plugins/unityshell/src/FavoriteStoreGSettings.cpp index 8de351a39..239ea07a9 100644 --- a/plugins/unityshell/src/FavoriteStoreGSettings.cpp +++ b/plugins/unityshell/src/FavoriteStoreGSettings.cpp @@ -17,15 +17,14 @@ * Authored by: Neil Jagdish Patel <neil.patel@canonical.com> */ -#include "FavoriteStoreGSettings.h" - #include <algorithm> -#include <iostream> #include <gio/gdesktopappinfo.h> - #include <NuxCore/Logger.h> +#include "FavoriteStoreGSettings.h" +#include "FavoriteStorePrivate.h" + #include "config.h" /** @@ -103,7 +102,12 @@ void FavoriteStoreGSettings::Init() void FavoriteStoreGSettings::Refresh() { - favorites_.clear(); + FillList(favorites_); +} + +void FavoriteStoreGSettings::FillList(FavoriteList& list) +{ + list.clear(); gchar** favs = g_settings_get_strv(settings_, "favorites"); @@ -114,7 +118,7 @@ void FavoriteStoreGSettings::Refresh() { if (g_file_test(favs[i], G_FILE_TEST_EXISTS)) { - favorites_.push_back(favs[i]); + list.push_back(favs[i]); } else { @@ -131,12 +135,11 @@ void FavoriteStoreGSettings::Refresh() if (filename) { - favorites_.push_back(filename); + list.push_back(filename); } else { - LOG_WARNING(logger) << "Unable to load GDesktopAppInfo for '" - << favs[i] << "'"; + LOG_WARNING(logger) << "Unable to load GDesktopAppInfo for '" << favs[i] << "'"; } } } @@ -222,7 +225,7 @@ void FavoriteStoreGSettings::SetFavorites(FavoriteList const& favorites) Refresh(); } -void FavoriteStoreGSettings::SaveFavorites(FavoriteList const& favorites) +void FavoriteStoreGSettings::SaveFavorites(FavoriteList const& favorites, bool ignore) { const int size = favorites.size(); const char* favs[size + 1]; @@ -244,7 +247,7 @@ void FavoriteStoreGSettings::SaveFavorites(FavoriteList const& favorites) favs[index] = iter->c_str(); } - ignore_signals_ = true; + ignore_signals_ = ignore; if (!g_settings_set_strv(settings_, "favorites", favs)) { LOG_WARNING(logger) << "Saving favorites failed."; @@ -254,10 +257,34 @@ void FavoriteStoreGSettings::SaveFavorites(FavoriteList const& favorites) void FavoriteStoreGSettings::Changed(std::string const& key) { - if (ignore_signals_) + if (ignore_signals_ or key != "favorites") return; + + FavoriteList old(favorites_); + FillList(favorites_); + + auto newbies = impl::GetNewbies(old, favorites_); - LOG_DEBUG(logger) << "Changed: " << key; + for (auto it : favorites_) + { + if (std::find(newbies.begin(), newbies.end(), it) == newbies.end()) + continue; + + std::string pos; + bool before; + + impl::GetSignalAddedInfo(favorites_, newbies , it, pos, before); + favorite_added.emit(it, pos, before); + } + + for (auto it : impl::GetRemoved(old, favorites_)) + { + favorite_removed.emit(it); + } + + if (impl::NeedToBeReordered(old, favorites_)) + reordered.emit(); + } namespace diff --git a/plugins/unityshell/src/FavoriteStoreGSettings.h b/plugins/unityshell/src/FavoriteStoreGSettings.h index d4e573806..600b6162c 100644 --- a/plugins/unityshell/src/FavoriteStoreGSettings.h +++ b/plugins/unityshell/src/FavoriteStoreGSettings.h @@ -42,6 +42,7 @@ public: virtual void AddFavorite(std::string const& desktop_path, int position); virtual void RemoveFavorite(std::string const& desktop_path); virtual void MoveFavorite(std::string const& desktop_path, int position); + void SaveFavorites(FavoriteList const& favorites, bool ignore = true); virtual void SetFavorites(FavoriteList const& desktop_paths); //Methods @@ -50,7 +51,7 @@ public: private: void Init(); void Refresh(); - void SaveFavorites(FavoriteList const& favorites); + void FillList(FavoriteList& list); FavoriteList favorites_; glib::Object<GSettings> settings_; diff --git a/plugins/unityshell/src/FavoriteStorePrivate.cpp b/plugins/unityshell/src/FavoriteStorePrivate.cpp new file mode 100644 index 000000000..5478fa0d5 --- /dev/null +++ b/plugins/unityshell/src/FavoriteStorePrivate.cpp @@ -0,0 +1,130 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2011 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzaronea <azzaronea@gmail.com> +*/ + +#include <algorithm> +#include <boost/utility.hpp> + +#include "FavoriteStorePrivate.h" + +namespace unity +{ +namespace internal +{ +namespace impl +{ + +std::vector<std::string> GetNewbies(std::list<std::string> const& old, std::list<std::string> const& fresh) +{ + auto sorted_old(old); + auto sorted_fresh(fresh); + + sorted_old.sort(); + sorted_fresh.sort(); + + std::vector<std::string> result; + std::set_difference(sorted_fresh.begin(), sorted_fresh.end(), sorted_old.begin(), sorted_old.end(), + std::inserter(result, result.end())); + + return result; +} + +void GetSignalAddedInfo(std::list<std::string> const& favs, std::vector<std::string> const& newbies, + std::string const& path, std::string& position, bool& before) +{ + auto it = std::find(favs.begin(), favs.end(), path); + before = (it == favs.begin()); + position = ""; + + if (before and favs.size() > 1) + { + while (it != favs.end() && std::find(newbies.begin(), newbies.end(), *it) != newbies.end()) + ++it; + + if (it != favs.end()) + position = *it; + } + else if (!before) + { + position = *(boost::prior(it)); + } + +} + +std::vector<std::string> GetRemoved(std::list<std::string> const& old, std::list<std::string> const& fresh) +{ + auto sorted_old(old); + auto sorted_fresh(fresh); + + sorted_old.sort(); + sorted_fresh.sort(); + + std::vector<std::string> result; + std::set_difference(sorted_old.begin(), sorted_old.end(), sorted_fresh.begin(), sorted_fresh.end(), + std::inserter(result, result.end())); + + return result; +} + + +bool NeedToBeReordered(std::list<std::string> const& old, std::list<std::string> const& fresh) +{ + auto sorted_old(old); + auto sorted_fresh(fresh); + + sorted_old.sort(); + sorted_fresh.sort(); + + std::vector<std::string> ignore_old, ignore_fresh; + + std::set_difference(sorted_old.begin(), sorted_old.end(), sorted_fresh.begin(), sorted_fresh.end(), + std::inserter(ignore_old, ignore_old.end())); + std::set_difference(sorted_fresh.begin(), sorted_fresh.end(), sorted_old.begin(), sorted_old.end(), + std::inserter(ignore_fresh, ignore_fresh.end())); + + auto it_old = old.begin(); + auto it_fresh = fresh.begin(); + + while (it_old != old.end() && it_fresh != fresh.end()) + { + + while (it_old != old.end() && std::find(ignore_old.begin(), ignore_old.end(), *it_old) != ignore_old.end()) + ++it_old; + + while (it_fresh != fresh.end() && std::find(ignore_fresh.begin(), ignore_fresh.end(), *it_fresh) != ignore_fresh.end()) + ++it_fresh; + + if (it_old == old.end() || it_fresh == fresh.end()) + break; + + if (*it_old != *it_fresh) + { + return true; + } + + ++it_old; + ++it_fresh; + } + + return false; +} + + +} // namespace impl +} // namespace internal +} // namespace unity diff --git a/plugins/unityshell/src/FavoriteStorePrivate.h b/plugins/unityshell/src/FavoriteStorePrivate.h new file mode 100644 index 000000000..4b27e0c03 --- /dev/null +++ b/plugins/unityshell/src/FavoriteStorePrivate.h @@ -0,0 +1,47 @@ +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- +/* +* Copyright (C) 2011 Canonical Ltd +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 3 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authored by: Andrea Azzaronea <azzaronea@gmail.com> +*/ + +#ifndef UNITYSHELL_FAVORITESTOREPRIVATE_H +#define UNITYSHELL_FAVORITESTOREPRIVATE_H + +#include <list> +#include <string> + +namespace unity +{ +namespace internal +{ +namespace impl +{ + +std::vector<std::string> GetNewbies(std::list<std::string> const& old, std::list<std::string> const& fresh); + +void GetSignalAddedInfo(std::list<std::string> const& favs, std::vector<std::string> const& newbies, + std::string const& path, std::string& position, bool& before); + +std::vector<std::string> GetRemoved(std::list<std::string> const& old, std::list<std::string> const& fresh); + +bool NeedToBeReordered(std::list<std::string> const& old, std::list<std::string> const& fresh); + +} // namespace impl +} // namespace internal +} // namespace unity + +#endif + diff --git a/plugins/unityshell/src/FilterBar.cpp b/plugins/unityshell/src/FilterBar.cpp index c33316a26..4b80369ad 100644 --- a/plugins/unityshell/src/FilterBar.cpp +++ b/plugins/unityshell/src/FilterBar.cpp @@ -56,7 +56,7 @@ FilterBar::~FilterBar() void FilterBar::Init() { nux::LinearLayout* layout = new nux::VLayout(NUX_TRACKER_LOCATION); - layout->SetSpaceBetweenChildren(12); + layout->SetSpaceBetweenChildren(10); SetLayout(layout); } @@ -105,10 +105,38 @@ void FilterBar::DrawContent(nux::GraphicsEngine& GfxContext, bool force_draw) { GfxContext.PushClippingRectangle(GetGeometry()); GetLayout()->ProcessDraw(GfxContext, force_draw); + + nux::Color col(0.13f, 0.13f, 0.13f, 0.13f); + + std::list<Area *>& layout_list = GetLayout()->GetChildren(); + std::list<Area*>::iterator iter; + int i = 0; + int num_separators = layout_list.size() - 1; + + for (iter = layout_list.begin(); iter != layout_list.end(); iter++) + { + if (i != num_separators) + { + nux::Area* filter_view = (*iter); + nux::Geometry const& geom = filter_view->GetGeometry(); + + unsigned int alpha = 0, src = 0, dest = 0; + GfxContext.GetRenderStates().GetBlend(alpha, src, dest); + + GfxContext.GetRenderStates().SetBlend(true, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + GfxContext.GetRenderStates().SetColorMask(true, true, true, false); + nux::GetPainter().Draw2DLine(GfxContext, + geom.x , geom.y + geom.height - 1, + geom.x + geom.width, geom.y + geom.height - 1, + col); + GfxContext.GetRenderStates().SetBlend(alpha, src, dest); + } + i++; + } + GfxContext.PopClippingRectangle(); } - } // namespace dash } // namespace unity diff --git a/plugins/unityshell/src/FilterBasicButton.cpp b/plugins/unityshell/src/FilterBasicButton.cpp index e7699e7a7..a4257f212 100644 --- a/plugins/unityshell/src/FilterBasicButton.cpp +++ b/plugins/unityshell/src/FilterBasicButton.cpp @@ -22,6 +22,12 @@ #include "DashStyle.h" #include "FilterBasicButton.h" +namespace +{ +const int kMinButtonHeight = 30; +const int kMinButtonWidth = 48; +} + namespace unity { namespace dash @@ -68,7 +74,8 @@ void FilterBasicButton::InitTheme() normal_.reset(new nux::CairoWrapper(geo, sigc::bind(sigc::mem_fun(this, &FilterBasicButton::RedrawTheme), nux::ButtonVisualState::VISUAL_STATE_NORMAL))); } - // SetMinimumHeight(32); + SetMinimumHeight(kMinButtonHeight); + SetMinimumWidth(kMinButtonWidth); } void FilterBasicButton::RedrawTheme(nux::Geometry const& geom, cairo_t* cr, nux::ButtonVisualState faked_state) @@ -79,6 +86,7 @@ void FilterBasicButton::RedrawTheme(nux::Geometry const& geom, cairo_t* cr, nux: long FilterBasicButton::ComputeContentSize() { long ret = nux::Button::ComputeContentSize(); + nux::Geometry const& geo = GetGeometry(); if (cached_geometry_ != geo) @@ -136,5 +144,4 @@ void FilterBasicButton::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) } } // namespace dash -} // namespace unity - +} // namespace unity \ No newline at end of file diff --git a/plugins/unityshell/src/FilterExpanderLabel.cpp b/plugins/unityshell/src/FilterExpanderLabel.cpp index 302de673e..e5e4b3691 100644 --- a/plugins/unityshell/src/FilterExpanderLabel.cpp +++ b/plugins/unityshell/src/FilterExpanderLabel.cpp @@ -24,11 +24,18 @@ #include "FilterBasicButton.h" #include "FilterExpanderLabel.h" +namespace +{ +const float kExpandDefaultIconOpacity = 1.0f; +} + namespace unity { namespace dash { + + NUX_IMPLEMENT_OBJECT_TYPE(FilterExpanderLabel); FilterExpanderLabel::FilterExpanderLabel(std::string const& label, NUX_FILE_LINE_DECL) @@ -37,10 +44,9 @@ FilterExpanderLabel::FilterExpanderLabel(std::string const& label, NUX_FILE_LINE , layout_(nullptr) , top_bar_layout_(nullptr) , right_hand_contents_(nullptr) - , expander_graphic_(nullptr) , cairo_label_(nullptr) , raw_label_(label) - , label_("<span size='larger' weight='bold'>" + label + "</span>" + " â–¾") + , label_("<span size='larger' weight='bold'>" + label + "</span>") { expanded.changed.connect(sigc::mem_fun(this, &FilterExpanderLabel::DoExpandChange)); BuildLayout(); @@ -57,7 +63,6 @@ void FilterExpanderLabel::SetLabel(std::string const& label) label_ = "<span size='larger' weight='bold'>"; label_ += raw_label_; label_ += "</span>"; - label_ += expanded ? " â–¾" : " â–¸"; cairo_label_->SetText(label_.c_str()); } @@ -66,7 +71,7 @@ void FilterExpanderLabel::SetRightHandView(nux::View* view) view->SetMaximumHeight(30); right_hand_contents_ = view; - top_bar_layout_->AddView(right_hand_contents_, 0, nux::MINOR_POSITION_LEFT, nux::MINOR_SIZE_FULL); + top_bar_layout_->AddView(right_hand_contents_, 0, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); } void FilterExpanderLabel::SetContents(nux::Layout* contents) @@ -83,6 +88,7 @@ void FilterExpanderLabel::BuildLayout() { layout_ = new nux::VLayout(NUX_TRACKER_LOCATION); top_bar_layout_ = new nux::HLayout(NUX_TRACKER_LOCATION); + top_bar_layout_->SetHorizontalInternalMargin(8); cairo_label_ = new nux::StaticText(label_.c_str(), NUX_TRACKER_LOCATION); cairo_label_->SetFontName("Ubuntu 10"); @@ -93,10 +99,31 @@ void FilterExpanderLabel::BuildLayout() expanded = !expanded; }); - top_bar_layout_->AddView(cairo_label_, 1, nux::MINOR_POSITION_LEFT, nux::MINOR_SIZE_FULL); + nux::BaseTexture* arrow; + arrow = dash::Style::Instance().GetGroupUnexpandIcon(); + expand_icon_ = new IconTexture(arrow, + arrow->GetWidth(), + arrow->GetHeight()); + expand_icon_->SetOpacity(kExpandDefaultIconOpacity); + expand_icon_->SetMinimumSize(arrow->GetWidth(), arrow->GetHeight()); + expand_icon_->SetVisible(true); + expand_icon_->mouse_click.connect( + [&] (int x, int y, unsigned long b, unsigned long k) + { + expanded = !expanded; + }); + arrow_layout_ = new nux::VLayout(); + arrow_top_space_ = new nux::SpaceLayout(2, 2, 11, 11); + arrow_bottom_space_ = new nux::SpaceLayout(2, 2, 9, 9); + arrow_layout_->AddView(arrow_top_space_, 0, nux::MINOR_POSITION_CENTER); + arrow_layout_->AddView(expand_icon_, 0, nux::MINOR_POSITION_CENTER); + arrow_layout_->AddView(arrow_bottom_space_, 0, nux::MINOR_POSITION_CENTER); + + top_bar_layout_->AddView(cairo_label_, 1, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); + top_bar_layout_->AddView(arrow_layout_, 0, nux::MINOR_POSITION_CENTER); top_bar_layout_->AddSpace(1, 1); - top_bar_layout_->SetMaximumWidth((Style::Instance().GetTileWidth() - 12) * 2 + 10); + top_bar_layout_->SetMaximumWidth((Style::Instance().GetTileWidth() - 12) * 2 + 20); layout_->AddLayout(top_bar_layout_, 0, nux::MINOR_POSITION_LEFT); layout_->SetVerticalInternalMargin(0); @@ -109,13 +136,11 @@ void FilterExpanderLabel::BuildLayout() void FilterExpanderLabel::DoExpandChange(bool change) { - label_ = "<span size='larger' weight='bold'>"; - label_ += raw_label_; - label_ += "</span>"; - label_ += expanded ? " â–¾" : " â–¸"; - - if (cairo_label_) - cairo_label_->SetText(label_); + dash::Style& style = dash::Style::Instance(); + if (expanded) + expand_icon_->SetTexture(style.GetGroupUnexpandIcon()); + else + expand_icon_->SetTexture(style.GetGroupExpandIcon()); if (change and contents_ and !contents_->IsChildOf(layout_)) { @@ -149,4 +174,4 @@ void FilterExpanderLabel::DrawContent(nux::GraphicsEngine& GfxContext, bool forc } } // namespace dash -} // namespace unity +} // namespace unity \ No newline at end of file diff --git a/plugins/unityshell/src/FilterExpanderLabel.h b/plugins/unityshell/src/FilterExpanderLabel.h index ca1b96181..ed409241b 100644 --- a/plugins/unityshell/src/FilterExpanderLabel.h +++ b/plugins/unityshell/src/FilterExpanderLabel.h @@ -30,6 +30,7 @@ #include <Nux/StaticText.h> #include "FilterWidget.h" +#include "IconTexture.h" namespace unity { @@ -60,10 +61,13 @@ private: nux::LinearLayout* layout_; nux::LinearLayout* top_bar_layout_; nux::View* right_hand_contents_; - nux::View* expander_graphic_; nux::StaticText* cairo_label_; std::string raw_label_; std::string label_; + nux::VLayout* arrow_layout_; + nux::SpaceLayout* arrow_top_space_; + nux::SpaceLayout* arrow_bottom_space_; + IconTexture* expand_icon_; nux::ObjectPtr<nux::Layout> contents_; }; diff --git a/plugins/unityshell/src/FilterGenreWidget.cpp b/plugins/unityshell/src/FilterGenreWidget.cpp index a12558f41..9ab2277eb 100644 --- a/plugins/unityshell/src/FilterGenreWidget.cpp +++ b/plugins/unityshell/src/FilterGenreWidget.cpp @@ -47,7 +47,7 @@ FilterGenre::FilterGenre(int columns, NUX_FILE_LINE_DECL) genre_layout_->ForceChildrenSize(true); genre_layout_->MatchContentSize(true); genre_layout_->SetSpaceBetweenChildren (9, 9); - genre_layout_->SetTopAndBottomPadding(12); + genre_layout_->SetTopAndBottomPadding(9, 12); genre_layout_->EnablePartialVisibility(false); if (columns == 3) { @@ -55,7 +55,7 @@ FilterGenre::FilterGenre(int columns, NUX_FILE_LINE_DECL) } else { - genre_layout_->SetChildrenSize(Style::Instance().GetTileWidth() - 12, 33); + genre_layout_->SetChildrenSize(Style::Instance().GetTileWidth() - 7, 33); } SetRightHandView(all_button_); @@ -122,17 +122,9 @@ void FilterGenre::InitTheme() void FilterGenre::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) { nux::Geometry const& geo = GetGeometry(); - nux::Color col(0.2f, 0.2f, 0.2f, 0.2f); GfxContext.PushClippingRectangle(geo); nux::GetPainter().PaintBackground(GfxContext, geo); - - nux::GetPainter().Draw2DLine(GfxContext, - geo.x, geo.y + geo.height - 1, - geo.x + geo.width, geo.y + geo.height - 1, - col, - col); - GfxContext.PopClippingRectangle(); } diff --git a/plugins/unityshell/src/FilterMultiRangeWidget.cpp b/plugins/unityshell/src/FilterMultiRangeWidget.cpp index a09a3aad2..ef552d2ce 100644 --- a/plugins/unityshell/src/FilterMultiRangeWidget.cpp +++ b/plugins/unityshell/src/FilterMultiRangeWidget.cpp @@ -44,7 +44,7 @@ FilterMultiRange::FilterMultiRange(NUX_FILE_LINE_DECL) all_button_ = new FilterAllButton(NUX_TRACKER_LOCATION); layout_ = new nux::HLayout(NUX_TRACKER_LOCATION); - layout_->SetVerticalExternalMargin(12); + layout_->SetTopAndBottomPadding(9, 12); SetRightHandView(all_button_); SetContents(layout_); @@ -158,17 +158,9 @@ void FilterMultiRange::InitTheme() void FilterMultiRange::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) { nux::Geometry const& geo = GetGeometry(); - nux::Color col(0.2f, 0.2f, 0.2f, 0.2f); GfxContext.PushClippingRectangle(geo); nux::GetPainter().PaintBackground(GfxContext, geo); - - nux::GetPainter().Draw2DLine(GfxContext, - geo.x, geo.y + geo.height - 1, - geo.x + geo.width, geo.y + geo.height - 1, - col, - col); - GfxContext.PopClippingRectangle(); } diff --git a/plugins/unityshell/src/FilterRatingsWidget.cpp b/plugins/unityshell/src/FilterRatingsWidget.cpp index b650d1d7d..0b9129cc1 100644 --- a/plugins/unityshell/src/FilterRatingsWidget.cpp +++ b/plugins/unityshell/src/FilterRatingsWidget.cpp @@ -43,7 +43,7 @@ FilterRatingsWidget::FilterRatingsWidget(NUX_FILE_LINE_DECL) all_button_ = new FilterAllButton(NUX_TRACKER_LOCATION); nux::VLayout* layout = new nux::VLayout(NUX_TRACKER_LOCATION); - layout->SetTopAndBottomPadding(10, 0); + layout->SetTopAndBottomPadding(11, 12); ratings_ = new FilterRatingsButton(NUX_TRACKER_LOCATION); layout->AddView(ratings_); diff --git a/plugins/unityshell/src/HomeView.cpp b/plugins/unityshell/src/HomeView.cpp deleted file mode 100644 index a8c40d7ec..000000000 --- a/plugins/unityshell/src/HomeView.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2010 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * Authored by: Neil Jagdish Patel <neil.patel@canonical.com> - */ - -#include "HomeView.h" - -#include <boost/lexical_cast.hpp> - -#include <NuxCore/Logger.h> - -#include "DashStyle.h" -#include "ResultRendererTile.h" -#include "UBusMessages.h" - -namespace unity -{ -namespace dash -{ - -namespace -{ -nux::logging::Logger logger("unity.dash.homeview"); -} - -// This is so we can override the scroll bar for the view. -class HomeScrollView: public nux::ScrollView -{ -public: - HomeScrollView(nux::VScrollBar* scroll_bar, NUX_FILE_LINE_DECL) - : nux::ScrollView(NUX_FILE_LINE_PARAM) - { - SetVScrollBar(scroll_bar); - } -}; - - - -NUX_IMPLEMENT_OBJECT_TYPE(HomeView); - -HomeView::HomeView() - : fix_renderering_id_(0) -{ - SetupViews(); - - search_string.changed.connect([&](std::string const& search) - { - for (auto lens : lenses_) - lens->GlobalSearch(search); - - for (auto group: categories_) - { - group->SetVisible(search != "" && counts_[group]); - } - home_view_->SetVisible(search == ""); - scroll_view_->SetVisible(search != ""); - - QueueDraw(); - }); -} - -HomeView::~HomeView() -{ - if (fix_renderering_id_) - g_source_remove(fix_renderering_id_); -} - -void HomeView::SetupViews() -{ - layout_ = new nux::HLayout(NUX_TRACKER_LOCATION); - layout_->SetHorizontalExternalMargin(7); - - scroll_view_ = new HomeScrollView(new PlacesVScrollBar(NUX_TRACKER_LOCATION), - NUX_TRACKER_LOCATION); - scroll_view_->EnableVerticalScrollBar(true); - scroll_view_->EnableHorizontalScrollBar(false); - scroll_view_->SetVisible(false); - layout_->AddView(scroll_view_); - - scroll_layout_ = new nux::VLayout(); - scroll_view_->SetLayout(scroll_layout_); - - home_view_ = new PlacesHomeView(); - layout_->AddView(home_view_); - - SetLayout(layout_); -} - -void HomeView::AddLens(Lens::Ptr lens) -{ - lenses_.push_back(lens); - - std::string name = lens->name; - std::string icon_hint = lens->icon_hint; - - LOG_DEBUG(logger) << "Lens added " << name; - - PlacesGroup* group = new PlacesGroup(); - group->SetName(name.c_str()); - group->SetIcon(icon_hint.c_str()); - group->SetExpanded(false); - group->SetVisible(false); - group->expanded.connect(sigc::mem_fun(this, &HomeView::OnGroupExpanded)); - categories_.push_back(group); - counts_[group] = 0; - - ResultViewGrid* grid = new ResultViewGrid(NUX_TRACKER_LOCATION); - grid->expanded = false; - grid->SetModelRenderer(new ResultRendererTile(NUX_TRACKER_LOCATION)); - grid->UriActivated.connect([&, lens] (std::string const& uri) { uri_activated.emit(uri); lens->Activate(uri); }); - group->SetChildView(grid); - - Results::Ptr results = lens->global_results; - results->result_added.connect([&, group, grid] (Result const& result) - { - grid->AddResult(const_cast<Result&>(result)); - counts_[group]++; - UpdateCounts(group); - }); - - results->result_removed.connect([&, group, grid] (Result const& result) - { - grid->RemoveResult(const_cast<Result&>(result)); - counts_[group]--; - UpdateCounts(group); - }); - - - scroll_layout_->AddView(group, 0); -} - -void HomeView::UpdateCounts(PlacesGroup* group) -{ - group->SetCounts(dash::Style::Instance().GetDefaultNColumns(), counts_[group]); - group->SetVisible(counts_[group]); - - QueueFixRenderering(); -} - -void HomeView::OnGroupExpanded(PlacesGroup* group) -{ - ResultViewGrid* grid = static_cast<ResultViewGrid*>(group->GetChildView()); - grid->expanded = group->GetExpanded(); - ubus_manager_.SendMessage(UBUS_PLACE_VIEW_QUEUE_DRAW); -} - -void HomeView::OnColumnsChanged() -{ - unsigned int columns = dash::Style::Instance().GetDefaultNColumns(); - - for (auto group: categories_) - { - group->SetCounts(columns, counts_[group]); - } -} - -void HomeView::QueueFixRenderering() -{ - if (fix_renderering_id_) - return; - - fix_renderering_id_ = g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc)FixRenderering, this, NULL); -} - -gboolean HomeView::FixRenderering(HomeView* self) -{ - std::list<Area*> children = self->scroll_layout_->GetChildren(); - std::list<Area*>::reverse_iterator rit; - bool found_one = false; - - for (rit = children.rbegin(); rit != children.rend(); ++rit) - { - PlacesGroup* group = static_cast<PlacesGroup*>(*rit); - - if (group->IsVisible()) - group->SetDrawSeparator(found_one); - - found_one = group->IsVisible(); - } - - self->fix_renderering_id_ = 0; - return FALSE; -} - -void HomeView::Draw(nux::GraphicsEngine& gfx_context, bool force_draw) -{ - nux::Geometry geo = GetGeometry(); - - gfx_context.PushClippingRectangle(geo); - nux::GetPainter().PaintBackground(gfx_context, geo); - gfx_context.PopClippingRectangle(); -} - -void HomeView::DrawContent(nux::GraphicsEngine& gfx_context, bool force_draw) -{ - gfx_context.PushClippingRectangle(GetGeometry()); - - layout_->ProcessDraw(gfx_context, force_draw); - - gfx_context.PopClippingRectangle(); -} - -void HomeView::ActivateFirst() -{ - for (auto lens: lenses_) - { - Results::Ptr results = lens->global_results; - if (results->count()) - { - Result result = results->RowAtIndex(0); - if (result.uri != "") - { - uri_activated(result.uri); - lens->Activate(result.uri); - return; - } - } - } -} - - -// Keyboard navigation -bool HomeView::AcceptKeyNavFocus() -{ - return false; -} - -// Introspectable -std::string HomeView::GetName() const -{ - return "HomeView"; -} - -void HomeView::AddProperties(GVariantBuilder* builder) -{} - - -} -} diff --git a/plugins/unityshell/src/HomeView.h b/plugins/unityshell/src/HomeView.h deleted file mode 100644 index 700779c6f..000000000 --- a/plugins/unityshell/src/HomeView.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2010 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * Authored by: Neil Jagdish Patel <neil.patel@canonical.com> - */ - -#ifndef UNITY_HOME_VIEW_H_ -#define UNITY_HOME_VIEW_H_ - -#include <string> - -#include <NuxGraphics/GraphicsEngine.h> -#include <Nux/Nux.h> -#include <Nux/HLayout.h> -#include <Nux/View.h> -#include <Nux/VLayout.h> -#include <UnityCore/Lens.h> - -#include "LensView.h" -#include "PlacesGroup.h" -#include "PlacesHomeView.h" -#include "ResultViewGrid.h" -#include "UBusWrapper.h" - -namespace unity -{ -namespace dash -{ - -class HomeView : public LensView -{ - NUX_DECLARE_OBJECT_TYPE(HomeView, LensView); - typedef std::vector<PlacesGroup*> CategoryGroups; - typedef std::map<PlacesGroup*, unsigned int> ResultCounts; - typedef std::vector<Lens::Ptr> Lenses; - -public: - HomeView(); - ~HomeView(); - - void AddLens(Lens::Ptr lens); - void ActivateFirst(); - -private: - void SetupViews(); - - void OnResultAdded(Result const& result); - void OnResultRemoved(Result const& result); - void UpdateCounts(PlacesGroup* group); - void OnGroupExpanded(PlacesGroup* group); - void OnColumnsChanged(); - void QueueFixRenderering(); - - static gboolean FixRenderering(HomeView* self); - - void Draw(nux::GraphicsEngine& gfx_context, bool force_draw); - void DrawContent(nux::GraphicsEngine& gfx_context, bool force_draw); - - bool AcceptKeyNavFocus(); - std::string GetName() const; - void AddProperties(GVariantBuilder* builder); - -private: - UBusManager ubus_manager_; - CategoryGroups categories_; - ResultCounts counts_; - Lenses lenses_; - - nux::HLayout* layout_; - nux::ScrollView* scroll_view_; - nux::VLayout* scroll_layout_; - - PlacesHomeView* home_view_; - - guint fix_renderering_id_; -}; - - -} -} -#endif diff --git a/plugins/unityshell/src/IMTextEntry.cpp b/plugins/unityshell/src/IMTextEntry.cpp index 2558bdf20..bc1b217c6 100644 --- a/plugins/unityshell/src/IMTextEntry.cpp +++ b/plugins/unityshell/src/IMTextEntry.cpp @@ -40,8 +40,6 @@ IMTextEntry::IMTextEntry() , preedit_string("") , im_enabled(false) , im_active(false) - , im_context_(0) - , client_window_(0) , focused_(false) { g_setenv("IBUS_ENABLE_SYNC_MODE", "1", TRUE); @@ -51,14 +49,6 @@ IMTextEntry::IMTextEntry() mouse_up.connect(sigc::mem_fun(this, &IMTextEntry::OnMouseButtonUp)); } -IMTextEntry::~IMTextEntry() -{ - if (im_context_) - g_object_unref(im_context_); - if (client_window_) - g_object_unref(client_window_); -} - void IMTextEntry::CheckIMEnabled() { const char* module = g_getenv("GTK_IM_MODULE"); @@ -74,7 +64,7 @@ void IMTextEntry::CheckIMEnabled() void IMTextEntry::SetupSimpleIM() { im_context_ = gtk_im_context_simple_new(); - + sig_manager_.Add(new Signal<void, GtkIMContext*, char*>(im_context_, "commit", sigc::mem_fun(this, &IMTextEntry::OnCommit))); sig_manager_.Add(new Signal<void, GtkIMContext*>(im_context_, "preedit-changed", sigc::mem_fun(this, &IMTextEntry::OnPreeditChanged))); sig_manager_.Add(new Signal<void, GtkIMContext*>(im_context_, "preedit-start", sigc::mem_fun(this, &IMTextEntry::OnPreeditStart))); @@ -84,7 +74,7 @@ void IMTextEntry::SetupSimpleIM() void IMTextEntry::SetupMultiIM() { im_context_ = gtk_im_multicontext_new(); - + sig_manager_.Add(new Signal<void, GtkIMContext*, char*>(im_context_, "commit", sigc::mem_fun(this, &IMTextEntry::OnCommit))); sig_manager_.Add(new Signal<void, GtkIMContext*>(im_context_, "preedit-changed", sigc::mem_fun(this, &IMTextEntry::OnPreeditChanged))); sig_manager_.Add(new Signal<void, GtkIMContext*>(im_context_, "preedit-start", sigc::mem_fun(this, &IMTextEntry::OnPreeditStart))); @@ -98,7 +88,7 @@ bool IMTextEntry::InspectKeyEvent(unsigned int event_type, bool propagate_event = !(TryHandleEvent(event_type, keysym, character)); LOG_DEBUG(logger) << "Input method " - << (im_enabled ? gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(im_context_)) : "simple") + << (im_enabled ? gtk_im_multicontext_get_context_id(glib::object_cast<GtkIMMulticontext>(im_context_)) : "simple") << " " << (propagate_event ? "did not handle " : "handled ") << "event (" @@ -222,19 +212,37 @@ void IMTextEntry::Copy() } } -void IMTextEntry::Paste() +void IMTextEntry::Paste(bool primary) { - GtkClipboard* clip = gtk_clipboard_get_for_display(gdk_display_get_default(), - GDK_SELECTION_CLIPBOARD); + GdkAtom origin = primary ? GDK_SELECTION_PRIMARY : GDK_SELECTION_CLIPBOARD; + glib::Object<GtkClipboard> clip(gtk_clipboard_get_for_display(gdk_display_get_default(), + origin)); auto callback = [](GtkClipboard* clip, const char* text, gpointer user_data) { IMTextEntry* self = static_cast<IMTextEntry*>(user_data); - self->OnCommit (self->im_context_, const_cast<char*>(text)); + if (text) + self->InsertTextAt(self->cursor_, std::string(text)); }; gtk_clipboard_request_text(clip, callback, this); } +void IMTextEntry::InsertTextAt(unsigned int position, std::string const& text) +{ + DeleteSelection(); + + if (!text.empty()) + { + std::string new_text(GetText()); + new_text.insert(position, text); + + int cursor = position; + SetText(new_text.c_str()); + SetCursor(cursor + text.length()); + UpdateCursorLocation(); + } +} + void IMTextEntry::OnCommit(GtkIMContext* context, char* str) { LOG_DEBUG(logger) << "Commit: " << str; @@ -242,13 +250,7 @@ void IMTextEntry::OnCommit(GtkIMContext* context, char* str) if (str) { - std::string new_text = GetText(); - new_text.insert(cursor_, str); - - int cursor = cursor_; - SetText(new_text.c_str()); - SetCursor(cursor + strlen(str)); - UpdateCursorLocation(); + InsertTextAt(cursor_, std::string(str)); } } @@ -263,7 +265,7 @@ void IMTextEntry::OnPreeditChanged(GtkIMContext* context) preedit_ = preedit.Str(); - if (strlen(preedit.Str().c_str())) { + if (!preedit.Str().empty()) { preedit_cursor_ = preedit.Str().length(); QueueRefresh(true, true); sigTextChanged.emit(this); @@ -309,20 +311,28 @@ void IMTextEntry::UpdateCursorLocation() nux::Rect strong, weak; GetCursorRects(&strong, &weak); nux::Geometry geo = GetGeometry(); - + GdkRectangle area = { strong.x + geo.x, strong.y + geo.y, strong.width, strong.height }; gtk_im_context_set_cursor_location(im_context_, &area); } void IMTextEntry::OnMouseButtonUp(int x, int y, unsigned long bflags, unsigned long kflags) { - if (nux::GetEventButton(bflags) == 3 && im_enabled) + int button = nux::GetEventButton(bflags); + + if (im_enabled && button == 3) { GtkWidget* menu = gtk_menu_new(); - gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(im_context_), - GTK_MENU_SHELL(menu)); + gtk_im_multicontext_append_menuitems(glib::object_cast<GtkIMMulticontext>(im_context_), + GTK_MENU_SHELL(menu)); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, GDK_CURRENT_TIME); } + else if (button == 2) + { + SetCursor(XYToTextIndex(x, y)); + UpdateCursorLocation(); + Paste(true); + } } } diff --git a/plugins/unityshell/src/IMTextEntry.h b/plugins/unityshell/src/IMTextEntry.h index 388193473..ab847bae6 100644 --- a/plugins/unityshell/src/IMTextEntry.h +++ b/plugins/unityshell/src/IMTextEntry.h @@ -26,6 +26,7 @@ #include <Nux/Nux.h> #include <Nux/TextEntry.h> #include <UnityCore/GLibSignal.h> +#include <UnityCore/GLibWrapper.h> namespace unity { @@ -38,7 +39,6 @@ class IMTextEntry : public nux::TextEntry NUX_DECLARE_OBJECT_TYPE(IMTextEntry, nux::TextEntry); public: IMTextEntry(); - ~IMTextEntry(); nux::Property<std::string> preedit_string; nux::Property<bool> im_enabled; @@ -54,9 +54,10 @@ private: void KeyEventToGdkEventKey(Event& event, GdkEventKey& gdk_event); inline void CheckValidClientWindow(Window window); bool TryHandleSpecial(unsigned int eventType, unsigned int keysym, const char* character); + void InsertTextAt(unsigned int position, std::string const& text); void Cut(); void Copy(); - void Paste(); + void Paste(bool primary = false); void OnCommit(GtkIMContext* context, char* str); void OnPreeditChanged(GtkIMContext* context); @@ -71,8 +72,8 @@ private: private: glib::SignalManager sig_manager_; - GtkIMContext* im_context_; - GdkWindow* client_window_; + glib::Object<GtkIMContext> im_context_; + glib::Object<GdkWindow> client_window_; bool focused_; }; diff --git a/plugins/unityshell/src/LauncherController.cpp b/plugins/unityshell/src/LauncherController.cpp index f8063a02b..a06ab3042 100644 --- a/plugins/unityshell/src/LauncherController.cpp +++ b/plugins/unityshell/src/LauncherController.cpp @@ -73,6 +73,11 @@ public: void OnLauncherEntryRemoteAdded(LauncherEntryRemote* entry); void OnLauncherEntryRemoteRemoved(LauncherEntryRemote* entry); + + void OnFavoriteStoreFavoriteAdded(std::string const& entry, std::string const& pos, bool before); + void OnFavoriteStoreFavoriteRemoved(std::string const& entry); + void OnFavoriteStoreReordered(); + void InsertExpoAction(); void RemoveExpoAction(); @@ -179,6 +184,10 @@ Controller::Impl::Impl(Display* display) remote_model_.entry_added.connect(sigc::mem_fun(this, &Impl::OnLauncherEntryRemoteAdded)); remote_model_.entry_removed.connect(sigc::mem_fun(this, &Impl::OnLauncherEntryRemoteRemoved)); + + FavoriteStore::GetDefault().favorite_added.connect(sigc::mem_fun(this, &Impl::OnFavoriteStoreFavoriteAdded)); + FavoriteStore::GetDefault().favorite_removed.connect(sigc::mem_fun(this, &Impl::OnFavoriteStoreFavoriteRemoved)); + FavoriteStore::GetDefault().reordered.connect(sigc::mem_fun(this, &Impl::OnFavoriteStoreReordered)); RegisterIcon(new BFBLauncherIcon(raw_launcher)); desktop_icon_ = new DesktopLauncherIcon(raw_launcher); @@ -349,6 +358,82 @@ void Controller::Impl::OnLauncherEntryRemoteRemoved(LauncherEntryRemote* entry) } } +void Controller::Impl::OnFavoriteStoreFavoriteAdded(std::string const& entry, std::string const& pos, bool before) +{ + auto bamf_list = model_->GetSublist<BamfLauncherIcon>(); + LauncherIcon* other = (bamf_list.size() > 0) ? *(bamf_list.begin()) : nullptr; + + if (!pos.empty()) + { + for (auto it : bamf_list) + { + if (it->GetQuirk(LauncherIcon::QUIRK_VISIBLE) && pos == it->DesktopFile()) + other = it; + } + } + + for (auto it : bamf_list) + { + if (entry == it->DesktopFile()) + { + it->Stick(false); + if (!before) + model_->ReorderAfter(it, other); + else + model_->ReorderBefore(it, other, false); + return; + } + } + + LauncherIcon* result = CreateFavorite(entry.c_str()); + if (result) + { + RegisterIcon(result); + if (!before) + model_->ReorderAfter(result, other); + else + model_->ReorderBefore(result, other, false); + } +} + +void Controller::Impl::OnFavoriteStoreFavoriteRemoved(std::string const& entry) +{ + for (auto it : model_->GetSublist<BamfLauncherIcon> ()) + { + if (it->DesktopFile() == entry) + { + OnLauncherRemoveRequest(it); + break; + } + } +} + +void Controller::Impl::OnFavoriteStoreReordered() +{ + FavoriteList const& favs = FavoriteStore::GetDefault().GetFavorites(); + auto bamf_list = model_->GetSublist<BamfLauncherIcon>(); + + int i = 0; + for (auto it : favs) + { + auto icon = std::find_if(bamf_list.begin(), bamf_list.end(), + [&it](BamfLauncherIcon* x) { return (x->DesktopFile() == it); }); + + if (icon != bamf_list.end()) + { + (*icon)->SetSortPriority(i++); + } + } + + for (auto it : bamf_list) + { + if (!it->IsSticky()) + it->SetSortPriority(i++); + } + + model_->Sort(); +} + void Controller::Impl::OnExpoActivated() { WindowManager::Default()->InitiateExpo(); diff --git a/plugins/unityshell/src/LauncherModel.cpp b/plugins/unityshell/src/LauncherModel.cpp index 45cc4196a..ea9769bc6 100644 --- a/plugins/unityshell/src/LauncherModel.cpp +++ b/plugins/unityshell/src/LauncherModel.cpp @@ -188,6 +188,36 @@ LauncherModel::IconHasSister(LauncherIcon* icon) } void +LauncherModel::ReorderAfter(LauncherIcon* icon, LauncherIcon* other) +{ + if (icon == other) + return; + + int i = 0; + for (LauncherModel::iterator it = begin(); it != end(); ++it) + { + if ((*it) == icon) + continue; + + if ((*it) == other) + { + (*it)->SetSortPriority(i); + ++i; + + icon->SetSortPriority(i); + ++i; + } + else + { + (*it)->SetSortPriority(i); + ++i; + } + } + + Sort(); +} + +void LauncherModel::ReorderBefore(LauncherIcon* icon, LauncherIcon* other, bool save) { if (icon == other) diff --git a/plugins/unityshell/src/LauncherModel.h b/plugins/unityshell/src/LauncherModel.h index fa27fb011..e2113f3ea 100644 --- a/plugins/unityshell/src/LauncherModel.h +++ b/plugins/unityshell/src/LauncherModel.h @@ -51,6 +51,7 @@ public: bool IconHasSister(LauncherIcon* icon); + void ReorderAfter(LauncherIcon* icon, LauncherIcon* other); void ReorderBefore(LauncherIcon* icon, LauncherIcon* other, bool save); void ReorderSmart(LauncherIcon* icon, LauncherIcon* other, bool save); diff --git a/plugins/unityshell/src/LensBar.cpp b/plugins/unityshell/src/LensBar.cpp index 23bf429b9..c59fdb683 100644 --- a/plugins/unityshell/src/LensBar.cpp +++ b/plugins/unityshell/src/LensBar.cpp @@ -148,10 +148,20 @@ void LensBar::DrawContent(nux::GraphicsEngine& gfx_context, bool force_draw) void LensBar::SetActive(LensBarIcon* activated) { + bool state_changed = false; + for (auto icon: icons_) - icon->active = icon == activated; + { + bool state = icon == activated; + + if (icon->active != state) + state_changed = true; + + icon->active = state; + } - lens_activated.emit(activated->id); + if (state_changed) + lens_activated.emit(activated->id); } void LensBar::ActivateNext() diff --git a/plugins/unityshell/src/LensView.cpp b/plugins/unityshell/src/LensView.cpp index d7d35920c..b3ddda08d 100644 --- a/plugins/unityshell/src/LensView.cpp +++ b/plugins/unityshell/src/LensView.cpp @@ -219,7 +219,12 @@ void LensView::OnCategoryAdded(Category const& category) group->SetExpanded(false); group->SetVisible(false); group->expanded.connect(sigc::mem_fun(this, &LensView::OnGroupExpanded)); - categories_.push_back(group); + + + /* Add the group at the correct offset into the categories vector */ + categories_.insert(categories_.begin() + index, group); + + /* Reset result count */ counts_[group] = 0; ResultViewGrid* grid = new ResultViewGrid(NUX_TRACKER_LOCATION); @@ -232,7 +237,11 @@ void LensView::OnCategoryAdded(Category const& category) grid->UriActivated.connect([&] (std::string const& uri) { uri_activated.emit(uri); lens_->Activate(uri); }); group->SetChildView(grid); - scroll_layout_->AddView(group, 0); + /* We need the full range of method args so we can specify the offset + * of the group into the layout */ + scroll_layout_->AddView(group, 0, nux::MinorDimensionPosition::eAbove, + nux::MinorDimensionSize::eFull, 100.0f, + (nux::LayoutPosition)index); } void LensView::OnResultAdded(Result const& result) diff --git a/plugins/unityshell/src/PlacesGroup.cpp b/plugins/unityshell/src/PlacesGroup.cpp index 83f7d9b43..5da93b3ce 100644 --- a/plugins/unityshell/src/PlacesGroup.cpp +++ b/plugins/unityshell/src/PlacesGroup.cpp @@ -44,10 +44,13 @@ #include "DashStyle.h" -static const nux::Color kExpandDefaultTextColor(1.0f, 1.0f, 1.0f, 0.5f); -static const nux::Color kExpandHoverTextColor(1.0f, 1.0f, 1.0f, 1.0f); -static const float kExpandDefaultIconOpacity = 0.5f; -static const float kExpandHoverIconOpacity = 1.0f; +namespace +{ +const nux::Color kExpandDefaultTextColor(1.0f, 1.0f, 1.0f, 0.5f); +const nux::Color kExpandHoverTextColor(1.0f, 1.0f, 1.0f, 1.0f); +const float kExpandDefaultIconOpacity = 0.5f; +const float kExpandHoverIconOpacity = 1.0f; +} namespace unity { diff --git a/plugins/unityshell/src/PlacesHomeView.cpp b/plugins/unityshell/src/PlacesHomeView.cpp deleted file mode 100644 index 01c80573e..000000000 --- a/plugins/unityshell/src/PlacesHomeView.cpp +++ /dev/null @@ -1,378 +0,0 @@ -// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- -/* - * Copyright (C) 2010 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * Authored by: Neil Jagdish Patel <neil.patel@canonical.com> - */ - -#include "config.h" - -#include <Nux/Nux.h> -#include <Nux/BaseWindow.h> -#include <Nux/HLayout.h> -#include <Nux/Layout.h> -#include <Nux/WindowCompositor.h> - -#include <NuxImage/CairoGraphics.h> -#include <NuxImage/ImageSurface.h> - -#include <NuxGraphics/GLThread.h> -#include <NuxGraphics/RenderingPipe.h> - -#include <glib.h> -#include <glib/gi18n-lib.h> -#include <gio/gdesktopappinfo.h> -#include "ubus-server.h" -#include "UBusMessages.h" - -#include "PlacesHomeView.h" -#include "PlacesSimpleTile.h" - -#include "DashStyle.h" -#include <UnityCore/GLibWrapper.h> -#include <UnityCore/Variant.h> - -#include <string> -#include <vector> - -#define DELTA_DOUBLE_REQUEST 500000000 - -namespace unity -{ - -enum -{ - TYPE_PLACE = 0, - TYPE_EXEC -}; - -class Shortcut : public PlacesSimpleTile -{ -public: - Shortcut(const char* icon, const char* name, int size) - : PlacesSimpleTile(icon, name, size), - _id(0), - _place_id(NULL), - _place_section(0), - _exec(NULL) - { - SetDndEnabled(false, false); - } - - ~Shortcut() - { - g_free(_place_id); - g_free(_exec); - } - - int _id; - gchar* _place_id; - guint32 _place_section; - char* _exec; -}; - -PlacesHomeView::PlacesHomeView() - : _ubus_handle(0) -{ - dash::Style& style = dash::Style::Instance(); - - SetName(_("Shortcuts")); - SetIcon(PKGDATADIR"/shortcuts_group_icon.png"); - SetDrawSeparator(false); - - _layout = new nux::GridHLayout(NUX_TRACKER_LOCATION); - _layout->SetReconfigureParentLayoutOnGeometryChange(true); - SetChildLayout(_layout); - - _layout->ForceChildrenSize(true); - _layout->SetChildrenSize(style.GetHomeTileWidth(), style.GetHomeTileHeight()); - _layout->EnablePartialVisibility(false); - _layout->MatchContentSize(true); - _layout->SetLeftAndRightPadding(32); - _layout->SetSpaceBetweenChildren(32, 32); - _layout->SetMinMaxSize((style.GetHomeTileWidth() * 4) + (32 * 5), - (style.GetHomeTileHeight() * 2) + 32); - - _ubus_handle = ubus_server_register_interest(ubus_server_get_default(), - UBUS_OVERLAY_SHOWN, - (UBusCallback) &PlacesHomeView::DashVisible, - this); - - //In case the GConf key is invalid (e.g. when an app was uninstalled), we - //rely on a fallback "whitelist" mechanism instead of showing nothing at all - _browser_alternatives.push_back("firefox"); - _browser_alternatives.push_back("chromium-browser"); - _browser_alternatives.push_back("epiphany-browser"); - _browser_alternatives.push_back("midori"); - - _photo_alternatives.push_back("shotwell"); - _photo_alternatives.push_back("f-spot"); - _photo_alternatives.push_back("gthumb"); - _photo_alternatives.push_back("gwenview"); - _photo_alternatives.push_back("eog"); - - _email_alternatives.push_back("evolution"); - _email_alternatives.push_back("thunderbird"); - _email_alternatives.push_back("claws-mail"); - _email_alternatives.push_back("kmail"); - - _music_alternatives.push_back("banshee-1"); - _music_alternatives.push_back("rhythmbox"); - _music_alternatives.push_back("totem"); - _music_alternatives.push_back("vlc"); - - expanded.connect(sigc::mem_fun(this, &PlacesHomeView::Refresh)); - - Refresh(); -} - -PlacesHomeView::~PlacesHomeView() -{ - if (_ubus_handle != 0) - ubus_server_unregister_interest(ubus_server_get_default(), _ubus_handle); -} - -void -PlacesHomeView::DashVisible(GVariant* data, void* val) -{ - PlacesHomeView* self = (PlacesHomeView*)val; - self->Refresh(); -} - -void -PlacesHomeView::Refresh(PlacesGroup*foo) -{ - Shortcut* shortcut = NULL; - gchar* markup = NULL; - const char* temp = "<big>%s</big>"; - int icon_size = dash::Style::Instance().GetHomeTileIconSize(); - - _layout->Clear(); - - // Media Apps - markup = g_strdup_printf(temp, _("Media Apps")); - shortcut = new Shortcut(PKGDATADIR"/find_media_apps.png", - markup, - icon_size); - shortcut->_id = TYPE_PLACE; - shortcut->_place_id = g_strdup("applications.lens?filter_type=media"); - shortcut->_place_section = 9; - _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull); - shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked)); - g_free(markup); - - // Internet Apps - markup = g_strdup_printf(temp, _("Internet Apps")); - shortcut = new Shortcut(PKGDATADIR"/find_internet_apps.png", - markup, - icon_size); - shortcut->_id = TYPE_PLACE; - shortcut->_place_id = g_strdup("applications.lens?filter_type=internet"); - shortcut->_place_section = 8; - _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull); - shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked)); - g_free(markup); - - // More Apps - markup = g_strdup_printf(temp, _("More Apps")); - shortcut = new Shortcut(PKGDATADIR"/find_more_apps.png", - markup, - icon_size); - shortcut->_id = TYPE_PLACE; - shortcut->_place_id = g_strdup("applications.lens"); - shortcut->_place_section = 0; - _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull); - shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked)); - g_free(markup); - - // Find Files - markup = g_strdup_printf(temp, _("Find Files")); - shortcut = new Shortcut(PKGDATADIR"/find_files.png", - markup, - icon_size); - shortcut->_id = TYPE_PLACE; - shortcut->_place_id = g_strdup("files.lens"); - shortcut->_place_section = 0; - _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull); - shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked)); - g_free(markup); - - // Browser - CreateShortcutFromMime("x-scheme-handler/http", _("Browse the Web"), _browser_alternatives); - - // Photos - // FIXME: Need to figure out the default - CreateShortcutFromExec("shotwell", _("View Photos"), _photo_alternatives); - - CreateShortcutFromMime("x-scheme-handler/mailto", _("Check Email"), _email_alternatives); - - CreateShortcutFromMime("audio/x-vorbis+ogg", _("Listen to Music"), _music_alternatives); - - SetExpanded(true); - SetCounts(8, 8); - - QueueDraw(); - _layout->QueueDraw(); - QueueRelayout(); -} - -void -PlacesHomeView::CreateShortcutFromExec(const char* exec, - const char* name, - std::vector<std::string>& alternatives) -{ - dash::Style& style = dash::Style::Instance(); - Shortcut* shortcut = NULL; - gchar* id; - gchar* markup; - gchar* icon; - gchar* real_exec; - GDesktopAppInfo* info; - - markup = g_strdup_printf("<big>%s</big>", name); - - // We're going to try and create a desktop id from a exec string. Now, this is hairy at the - // best of times but the following is the closest best-guess without having to do D-Bus - // roundtrips to BAMF. - if (exec) - { - char* basename; - - if (exec[0] == '/') - basename = g_path_get_basename(exec); - else - basename = g_strdup(exec); - - id = g_strdup_printf("%s.desktop", basename); - - g_free(basename); - } - else - { - id = g_strdup_printf("%s.desktop", alternatives[0].c_str()); - } - - info = g_desktop_app_info_new(id); - std::vector<std::string>::iterator iter = alternatives.begin(); - while (iter != alternatives.end()) - { - if (!G_IS_DESKTOP_APP_INFO(info)) - { - id = g_strdup_printf("%s.desktop", (*iter).c_str()); - info = g_desktop_app_info_new(id); - iter++; - } - - if (G_IS_DESKTOP_APP_INFO(info)) - { - icon = g_icon_to_string(g_app_info_get_icon(G_APP_INFO(info))); - real_exec = g_strdup(g_app_info_get_executable(G_APP_INFO(info))); - - shortcut = new Shortcut(icon, markup, style.GetHomeTileIconSize()); - shortcut->_id = TYPE_EXEC; - shortcut->_exec = real_exec; - _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull); - shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked)); - - g_free(icon); - - break; - } - } - - g_free(id); - g_free(markup); -} - -void PlacesHomeView::CreateShortcutFromMime(const char* mime, - const char* name, - std::vector<std::string>& alternatives) -{ - dash::Style& style = dash::Style::Instance(); - GAppInfo* info = g_app_info_get_default_for_type(mime, FALSE); - - // If it was invalid check alternatives for backup - if (!G_IS_DESKTOP_APP_INFO(info)) - { - for (auto alt: alternatives) - { - std::string id = alt + ".desktop"; - info = G_APP_INFO(g_desktop_app_info_new(id.c_str())); - - if (G_IS_DESKTOP_APP_INFO(info)) - break; - } - } - - if (G_IS_DESKTOP_APP_INFO(info)) - { - glib::String icon(g_icon_to_string(g_app_info_get_icon(G_APP_INFO(info)))); - glib::String markup(g_strdup_printf("<big>%s</big>", name)); - - Shortcut* shortcut = new Shortcut(icon.Value(), markup.Value(), style.GetHomeTileIconSize()); - shortcut->_id = TYPE_EXEC; - shortcut->_exec = g_strdup (g_app_info_get_executable(G_APP_INFO(info)));; - shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked)); - _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull); - - g_object_unref(info); - } -} - -void -PlacesHomeView::OnShortcutClicked(PlacesTile* tile) -{ - Shortcut* shortcut = static_cast<Shortcut*>(tile); - int id = shortcut->_id; - - if (id == TYPE_PLACE) - { - ubus_server_send_message(ubus_server_get_default(), - UBUS_PLACE_ENTRY_ACTIVATE_REQUEST, - g_variant_new("(sus)", - shortcut->_place_id, - shortcut->_place_section, - "")); - } - else if (id == TYPE_EXEC) - { - GError* error = NULL; - - if (!g_spawn_command_line_async(shortcut->_exec, &error)) - { - g_warning("%s: Unable to launch %s: %s", - G_STRFUNC, - shortcut->_exec, - error->message); - g_error_free(error); - } - - ubus_server_send_message(ubus_server_get_default(), - UBUS_PLACE_VIEW_CLOSE_REQUEST, - NULL); - } -} - -std::string PlacesHomeView::GetName() const -{ - return "PlacesHomeView"; -} - -void PlacesHomeView::AddProperties(GVariantBuilder* builder) -{ - unity::variant::BuilderWrapper(builder).add(GetGeometry()); -} - -} // namespace unity diff --git a/plugins/unityshell/src/PlacesHomeView.h b/plugins/unityshell/src/PlacesHomeView.h deleted file mode 100644 index ead5aedc1..000000000 --- a/plugins/unityshell/src/PlacesHomeView.h +++ /dev/null @@ -1,72 +0,0 @@ -// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- -/* - * Copyright (C) 2010 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * Authored by: Neil Jagdish Patel <neil.patel@canonical.com> - */ - -#ifndef PLACES_HOME_VIEW_H -#define PLACES_HOME_VIEW_H - -#include <Nux/View.h> -#include <Nux/Layout.h> -#include <Nux/TextureArea.h> -#include <NuxGraphics/GraphicsEngine.h> - -#include "Introspectable.h" - -#include <Nux/GridHLayout.h> - -#include "PlacesTile.h" -#include "PlacesGroup.h" - -namespace unity -{ - -class PlacesHomeView : public unity::debug::Introspectable, public PlacesGroup -{ -public: - PlacesHomeView(); - ~PlacesHomeView(); - - void Refresh(PlacesGroup* foo =NULL); - -protected: - // Introspectable methods - std::string GetName() const; - void AddProperties(GVariantBuilder* builder); - -private: - static void DashVisible(GVariant* data, void* val); - void OnShortcutClicked(PlacesTile* _tile); - void CreateShortcutFromExec(const char* exec, - const char* name, - std::vector<std::string>& alternatives); - void CreateShortcutFromMime(const char* mime, - const char* name, - std::vector<std::string>& alternatives); - -private: - nux::GridHLayout* _layout; - std::vector<std::string> _browser_alternatives; - std::vector<std::string> _photo_alternatives; - std::vector<std::string> _email_alternatives; - std::vector<std::string> _music_alternatives; - - guint _ubus_handle; -}; - -} -#endif diff --git a/plugins/unityshell/src/PluginAdapter.cpp b/plugins/unityshell/src/PluginAdapter.cpp index 1bc3e0dd2..dbc300053 100644 --- a/plugins/unityshell/src/PluginAdapter.cpp +++ b/plugins/unityshell/src/PluginAdapter.cpp @@ -28,6 +28,9 @@ namespace nux::logging::Logger logger("unity.plugin"); +const int THRESHOLD_HEIGHT = 600; +const int THRESHOLD_WIDTH = 1024; + } PluginAdapter* PluginAdapter::_default = 0; @@ -923,6 +926,10 @@ bool PluginAdapter::MaximizeIfBigEnough(CompWindow* window) screen_height = o.workArea().height(); screen_width = o.workArea().width(); + + // See bug https://bugs.launchpad.net/unity/+bug/797808 + if (screen_height * screen_width > THRESHOLD_HEIGHT * THRESHOLD_WIDTH) + return false; // use server<parameter> because the window won't show the real parameter as // not mapped yet diff --git a/plugins/unityshell/src/ResultViewGrid.cpp b/plugins/unityshell/src/ResultViewGrid.cpp index ef2773420..0511b8f53 100644 --- a/plugins/unityshell/src/ResultViewGrid.cpp +++ b/plugins/unityshell/src/ResultViewGrid.cpp @@ -626,8 +626,19 @@ void ResultViewGrid::Draw(nux::GraphicsEngine& GfxContext, bool force_draw) int half_width = recorded_dash_width_ / 2; int half_height = recorded_dash_height_; - int offset_x = MAX(MIN((x_position - half_width) / (half_width / 10), 5), -5); - int offset_y = MAX(MIN(((y_position + absolute_y) - half_height) / (half_height / 10), 5), -5); + int offset_x, offset_y; + + /* Guard against divide-by-zero. SIGFPEs are not mythological + * contrary to popular belief */ + if (half_width >= 10) + offset_x = MAX(MIN((x_position - half_width) / (half_width / 10), 5), -5); + else + offset_x = 0; + + if (half_height >= 10) + offset_y = MAX(MIN(((y_position + absolute_y) - half_height) / (half_height / 10), 5), -5); + else + offset_y = 0; if (recorded_dash_width_ < 1 || recorded_dash_height_ < 1) { diff --git a/plugins/unityshell/src/SearchBar.cpp b/plugins/unityshell/src/SearchBar.cpp index 50a49fb94..cc1f9fc01 100644 --- a/plugins/unityshell/src/SearchBar.cpp +++ b/plugins/unityshell/src/SearchBar.cpp @@ -45,7 +45,10 @@ #define LIVE_SEARCH_TIMEOUT 40 #define SPINNER_TIMEOUT 100 -static const float kExpandDefaultIconOpacity = 1.0f; +namespace +{ +const float kExpandDefaultIconOpacity = 1.0f; +} namespace unity { @@ -131,9 +134,10 @@ void SearchBar::Init() if (show_filter_hint_) { - std::string filter_str = _("<b>Filter results</b>"); + std::string filter_str = _("<small><b>Filter results</b></small>"); show_filters_ = new nux::StaticCairoText(filter_str.c_str()); show_filters_->SetVisible(false); + show_filters_->SetFont("Ubuntu 10"); show_filters_->SetTextColor(nux::Color(1.0f, 1.0f, 1.0f, 1.0f)); show_filters_->SetTextAlignment(nux::StaticCairoText::NUX_ALIGN_LEFT); show_filters_->mouse_click.connect([&] (int x, int y, unsigned long b, unsigned long k) { showing_filters = !showing_filters; }); @@ -166,7 +170,6 @@ void SearchBar::Init() layout_->AddView(filter_layout_, 1, nux::MINOR_POSITION_RIGHT, nux::MINOR_SIZE_FULL); } - sig_manager_.Add(new Signal<void, GtkSettings*, GParamSpec*> (gtk_settings_get_default(), "notify::gtk-font-name", @@ -508,4 +511,4 @@ void SearchBar::AddProperties(GVariantBuilder* builder) g_variant_builder_add (builder, "{sv}", "search_string", g_variant_new_string (pango_entry_->GetText().c_str()) ); } -} +} // namespace unity diff --git a/po/POTFILES.in b/po/POTFILES.in index c3b59400e..9154d35f1 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -3,7 +3,6 @@ plugins/unityshell/src/DeviceLauncherIcon.cpp plugins/unityshell/src/LauncherController.cpp plugins/unityshell/src/PanelMenuView.cpp plugins/unityshell/src/PlacesGroup.cpp -plugins/unityshell/src/PlacesHomeView.cpp plugins/unityshell/src/SpacerLauncherIcon.cpp plugins/unityshell/src/TrashLauncherIcon.cpp plugins/unityshell/src/BFBLauncherIcon.cpp diff --git a/standalone-clients/CMakeLists.txt b/standalone-clients/CMakeLists.txt index 7deefde2c..c015f91d6 100644 --- a/standalone-clients/CMakeLists.txt +++ b/standalone-clients/CMakeLists.txt @@ -76,8 +76,6 @@ add_executable (dash ${UNITY_SRC}/FontSettings.h ${UNITY_SRC}/IMTextEntry.cpp ${UNITY_SRC}/IMTextEntry.h - ${UNITY_SRC}/PlacesHomeView.cpp - ${UNITY_SRC}/PlacesHomeView.h ${UNITY_SRC}/PlacesGroup.cpp ${UNITY_SRC}/PlacesGroup.h ${UNITY_SRC}/PlacesTile.cpp @@ -90,8 +88,6 @@ add_executable (dash ${UNITY_SRC}/DashView.h ${UNITY_SRC}/DashViewPrivate.cpp ${UNITY_SRC}/DashViewPrivate.h - ${UNITY_SRC}/HomeView.cpp - ${UNITY_SRC}/HomeView.h ${UNITY_SRC}/DashStyle.cpp ${UNITY_SRC}/IconLoader.cpp ${UNITY_SRC}/IconLoader.h diff --git a/standalone-clients/standalone_dash.cpp b/standalone-clients/standalone_dash.cpp index 937214ceb..6f9363303 100644 --- a/standalone-clients/standalone_dash.cpp +++ b/standalone-clients/standalone_dash.cpp @@ -77,18 +77,8 @@ void TestRunner::InitWindowThread(nux::NThread* thread, void* InitData) self->Init (); } -void -ControlThread (nux::NThread* thread, - void* data) -{ - // sleep for 3 seconds - nux::SleepForMilliseconds (3000); - printf ("ControlThread successfully started\n"); -} - int main(int argc, char **argv) { - nux::SystemThread* st = NULL; nux::WindowThread* wt = NULL; gtk_init (&argc, &argv); @@ -109,13 +99,7 @@ int main(int argc, char **argv) &TestRunner::InitWindowThread, test_runner); - st = nux::CreateSystemThread (NULL, ControlThread, wt); - - if (st) - st->Start (NULL); - wt->Run (NULL); - delete st; delete wt; return 0; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8db214881..1941e3c78 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -121,11 +121,13 @@ if (GTEST_FOUND AND test_glib_variant.cpp ${CMAKE_CURRENT_BINARY_DIR}/test_glib_signals_utils_marshal.cpp test_favorite_store_gsettings.cpp + test_favorite_store_private.cpp + test_home_lens.cpp test_shortcut_model.cpp test_shortcut_private.cpp test_introspection.cpp test_main_xless.cpp - test_grabhandle.cpp + test_grabhandle.cpp ${UNITY_SRC}/AbstractLauncherIcon.h ${UNITY_SRC}/AbstractShortcutHint.h ${UNITY_SRC}/Animator.cpp @@ -138,6 +140,8 @@ if (GTEST_FOUND AND ${UNITY_SRC}/FavoriteStore.h ${UNITY_SRC}/FavoriteStoreGSettings.cpp ${UNITY_SRC}/FavoriteStoreGSettings.h + ${UNITY_SRC}/FavoriteStorePrivate.cpp + ${UNITY_SRC}/FavoriteStorePrivate.h ${UNITY_SRC}/MockLauncherIcon.h ${UNITY_SRC}/MockShortcutHint.h ${UNITY_SRC}/ShortcutModel.cpp @@ -154,11 +158,11 @@ if (GTEST_FOUND AND ${UNITY_SRC}/Timer.h ${UNITY_SRC}/WindowManager.cpp ${UNITY_SRC}/WindowManager.h - ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle.cpp - ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-group.cpp - ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-impl-factory.cpp - ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-layout.cpp - ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-texture.cpp + ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle.cpp + ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-group.cpp + ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-impl-factory.cpp + ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-layout.cpp + ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-texture.cpp ) target_link_libraries(test-gtest-xless ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIB} ${GMOCK_MAIN_LIB}) add_test(UnityGTestXless test-gtest-xless) diff --git a/tests/test_favorite_store_gsettings.cpp b/tests/test_favorite_store_gsettings.cpp index 33413bd09..deed131fd 100644 --- a/tests/test_favorite_store_gsettings.cpp +++ b/tests/test_favorite_store_gsettings.cpp @@ -42,8 +42,8 @@ namespace { const gchar* SCHEMA_DIRECTORY = BUILDDIR"/settings"; const gchar* BASE_STORE_FILE = BUILDDIR"/settings/test-favorite-store-gsettings.store"; const gchar* BASE_STORE_CONTENTS = "[desktop/unity/launcher]\n" \ - "favorites=['%s', '%s', '%s']"; - + "favorites=['%s', '%s', '%s']"; + const char* base_store_favs[] = { BUILDDIR"/tests/data/ubuntuone-installer.desktop", BUILDDIR"/tests/data/ubuntu-software-center.desktop", BUILDDIR"/tests/data/update-manager.desktop", @@ -222,5 +222,271 @@ TEST_F(TestFavoriteStoreGSettings, TestMoveFavoriteBad) EXPECT_EQ(at(favs, 2), base_store_favs[2]); } +TEST_F(TestFavoriteStoreGSettings, TestFavoriteAddedSignalFirst) +{ + internal::FavoriteStoreGSettings settings(backend.RawPtr()); + bool signal_received = false; + std::string position; + bool before = false; + + settings.favorite_added.connect([&](std::string const& path, std::string const& pos, bool bef) + { + signal_received = true; + position = pos; + before = bef; + }); + + FavoriteList favs; + favs.push_back(other_desktop); + favs.push_back(base_store_favs[0]); + favs.push_back(base_store_favs[1]); + favs.push_back(base_store_favs[2]); + settings.SaveFavorites(favs, false); + + sleep(1); + + ASSERT_TRUE(signal_received); + EXPECT_EQ(position, base_store_favs[0]); + EXPECT_TRUE(before); +} + +TEST_F(TestFavoriteStoreGSettings, TestFavoriteAddedSignalMiddle) +{ + internal::FavoriteStoreGSettings settings(backend.RawPtr()); + bool signal_received = false; + std::string position; + bool before = true; + + settings.favorite_added.connect([&](std::string const& path, std::string const& pos, bool bef) + { + signal_received = true; + position = pos; + before = bef; + }); + + FavoriteList favs; + favs.push_back(base_store_favs[0]); + favs.push_back(base_store_favs[1]); + favs.push_back(other_desktop); + favs.push_back(base_store_favs[2]); + settings.SaveFavorites(favs, false); + + sleep(1); + + ASSERT_TRUE(signal_received); + EXPECT_EQ(position, base_store_favs[1]); + EXPECT_FALSE(before); +} + +TEST_F(TestFavoriteStoreGSettings, TestFavoriteAddedSignalEnd) +{ + internal::FavoriteStoreGSettings settings(backend.RawPtr()); + bool signal_received = false; + std::string position; + bool before = true; + + settings.favorite_added.connect([&](std::string const& path, std::string const& pos, bool bef) + { + signal_received = true; + position = pos; + before = bef; + }); + + FavoriteList favs; + favs.push_back(base_store_favs[0]); + favs.push_back(base_store_favs[1]); + favs.push_back(base_store_favs[2]); + favs.push_back(other_desktop); + settings.SaveFavorites(favs, false); + + sleep(1); + + ASSERT_TRUE(signal_received); + EXPECT_EQ(position, base_store_favs[2]); + EXPECT_FALSE(before); +} + +TEST_F(TestFavoriteStoreGSettings, TestFavoriteAddedSignalEmpty) +{ + internal::FavoriteStoreGSettings settings(backend.RawPtr()); + bool signal_received = false; + std::string position; + bool before = false; + + settings.favorite_added.connect([&](std::string const& path, std::string const& pos, bool bef) + { + signal_received = true; + position = pos; + before = bef; + }); + + FavoriteList favs; + favs.push_back(other_desktop); + settings.SaveFavorites(favs, false); + + sleep(1); + + ASSERT_TRUE(signal_received); + EXPECT_EQ(position, ""); + EXPECT_TRUE(before); +} + +TEST_F(TestFavoriteStoreGSettings, TestFavoriteRemoved) +{ + internal::FavoriteStoreGSettings settings(backend.RawPtr()); + bool signal_received = false; + std::string path_removed; + + settings.favorite_removed.connect([&](std::string const& path) + { + signal_received = true; + path_removed = path; + }); + + FavoriteList favs; + favs.push_back(base_store_favs[0]); + favs.push_back(base_store_favs[2]); + settings.SaveFavorites(favs, false); + + sleep(1); + + ASSERT_TRUE(signal_received); + EXPECT_EQ(path_removed, base_store_favs[1]); +} + +TEST_F(TestFavoriteStoreGSettings, TestFavoriteReordered) +{ + internal::FavoriteStoreGSettings settings(backend.RawPtr()); + bool signal_received = false; + + settings.reordered.connect([&]() + { + signal_received = true; + }); + + FavoriteList favs; + favs.push_back(base_store_favs[0]); + favs.push_back(base_store_favs[2]); + favs.push_back(base_store_favs[1]); + settings.SaveFavorites(favs, false); + + sleep(1); + + ASSERT_TRUE(signal_received); + + signal_received = false; + favs.push_back(base_store_favs[0]); + favs.push_back(base_store_favs[2]); + favs.push_back(base_store_favs[1]); + settings.SaveFavorites(favs, false); + + sleep(1); + + ASSERT_FALSE(signal_received); +} + +TEST_F(TestFavoriteStoreGSettings, TestFavoriteSignalsMixed1) +{ + internal::FavoriteStoreGSettings settings(backend.RawPtr()); + bool added_received = false; + bool removed_received = false; + bool reordered_received = false; + + settings.favorite_added.connect([&](std::string const& path, std::string const& pos, bool bef) + { + added_received = true; + }); + + settings.favorite_removed.connect([&](std::string const& path) + { + removed_received = true; + }); + + settings.reordered.connect([&]() + { + reordered_received = true; + }); + + FavoriteList favs; + favs.push_back(base_store_favs[0]); + favs.push_back(base_store_favs[1]); + favs.push_back(other_desktop); + settings.SaveFavorites(favs, false); + + sleep(1); + + EXPECT_TRUE(added_received); + EXPECT_TRUE(removed_received); + EXPECT_FALSE(reordered_received); +} + +TEST_F(TestFavoriteStoreGSettings, TestFavoriteSignalsMixed2) +{ + internal::FavoriteStoreGSettings settings(backend.RawPtr()); + bool added_received = false; + bool removed_received = false; + bool reordered_received = false; + + settings.favorite_added.connect([&](std::string const& path, std::string const& pos, bool bef) + { + added_received = true; + }); + + settings.favorite_removed.connect([&](std::string const& path) + { + removed_received = true; + }); + + settings.reordered.connect([&]() + { + reordered_received = true; + }); + + FavoriteList favs; + favs.push_back(base_store_favs[1]); + favs.push_back(other_desktop); + favs.push_back(base_store_favs[0]); + settings.SaveFavorites(favs, false); + + sleep(1); + + EXPECT_TRUE(added_received); + EXPECT_TRUE(removed_received); + EXPECT_TRUE(reordered_received); +} + +TEST_F(TestFavoriteStoreGSettings, TestFavoriteSignalsMixed3) +{ + internal::FavoriteStoreGSettings settings(backend.RawPtr()); + bool added_received = false; + bool removed_received = false; + bool reordered_received = false; + + settings.favorite_added.connect([&](std::string const& path, std::string const& pos, bool bef) + { + added_received = true; + }); + + settings.favorite_removed.connect([&](std::string const& path) + { + removed_received = true; + }); + + settings.reordered.connect([&]() + { + reordered_received = true; + }); + + FavoriteList favs; + favs.push_back(base_store_favs[1]); + favs.push_back(base_store_favs[0]); + settings.SaveFavorites(favs, false); + + sleep(1); + + EXPECT_FALSE(added_received); + EXPECT_TRUE(removed_received); + EXPECT_TRUE(reordered_received); +} } // anonymous namespace diff --git a/tests/test_favorite_store_private.cpp b/tests/test_favorite_store_private.cpp new file mode 100644 index 000000000..093888150 --- /dev/null +++ b/tests/test_favorite_store_private.cpp @@ -0,0 +1,417 @@ +/* + * Copyright 2011 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 3 along with this program. If not, see + * <http://www.gnu.org/licenses/> + * + * Authored by: Andrea Azzarone <azzaronea@gmail.com> + * + */ + +#include <gtest/gtest.h> + +#include "FavoriteStorePrivate.h" + +using namespace unity; + +TEST(TestFavoriteStorePrivate, TestGetNewbies) +{ + std::list<std::string> old; + std::list<std::string> fresh; + std::vector<std::string> result; + + old.push_back("a"); + old.push_back("b"); + old.push_back("c"); + old.push_back("d"); + + // No change. + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("c"); + fresh.push_back("d"); + + result = internal::impl::GetNewbies(old, fresh); + + EXPECT_TRUE(result.empty()); + + // Permutation. + fresh.clear(); + result.clear(); + fresh.push_back("a"); + fresh.push_back("c"); + fresh.push_back("b"); + fresh.push_back("d"); + + result = internal::impl::GetNewbies(old, fresh); + + EXPECT_TRUE(result.empty()); + + // a b c d -> a c b + fresh.clear(); + result.clear(); + fresh.push_back("a"); + fresh.push_back("c"); + fresh.push_back("b"); + + result = internal::impl::GetNewbies(old, fresh); + + EXPECT_TRUE(result.empty()); + + // a b c d -> a b c d e f + fresh.clear(); + result.clear(); + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("c"); + fresh.push_back("d"); + fresh.push_back("e"); + fresh.push_back("f"); + + result = internal::impl::GetNewbies(old, fresh); + + EXPECT_EQ(result.size(), 2); + EXPECT_EQ(result[0], "e"); + EXPECT_EQ(result[1], "f"); + + // a b c d -> a b c e f + fresh.clear(); + result.clear(); + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("c"); + fresh.push_back("e"); + fresh.push_back("f"); + + result = internal::impl::GetNewbies(old, fresh); + + EXPECT_EQ(result.size(), 2); + EXPECT_EQ(result[0], "e"); + EXPECT_EQ(result[1], "f"); +} + +TEST(TestFavoriteStorePrivate, TestGetSignalAddedInfo) +{ + std::list<std::string> favs; + std::vector<std::string> newbies; + std::string position; + bool before; + + favs.push_back("a"); + favs.push_back("b"); + favs.push_back("c"); + favs.push_back("d"); + favs.push_back("e"); + + // b c d e -> a b c d e + newbies.push_back("a"); + internal::impl::GetSignalAddedInfo(favs, newbies, "a", position, before); + EXPECT_TRUE(before); + EXPECT_EQ(position, "b"); + + // a c d e -> a b c d e + newbies.clear(); + newbies.push_back("b"); + internal::impl::GetSignalAddedInfo(favs, newbies, "b", position, before); + EXPECT_FALSE(before); + EXPECT_EQ(position, "a"); + + // a b d e -> a b c d e + newbies.clear(); + newbies.push_back("c"); + internal::impl::GetSignalAddedInfo(favs, newbies, "c", position, before); + EXPECT_FALSE(before); + EXPECT_EQ(position, "b"); + + // a b c e -> a b c d e + newbies.clear(); + newbies.push_back("d"); + internal::impl::GetSignalAddedInfo(favs, newbies, "d", position, before); + EXPECT_FALSE(before); + EXPECT_EQ(position, "c"); + + // a b c d -> a b c d e + newbies.clear(); + newbies.push_back("e"); + internal::impl::GetSignalAddedInfo(favs, newbies, "e", position, before); + EXPECT_FALSE(before); + EXPECT_EQ(position, "d"); + + // -> b a c + favs.clear(); + favs.push_back("b"); + favs.push_back("a"); + favs.push_back("c"); + newbies.clear(); + newbies.push_back("a"); + newbies.push_back("b"); + newbies.push_back("c"); + + internal::impl::GetSignalAddedInfo(favs, newbies, "b", position, before); + EXPECT_TRUE(before); + EXPECT_EQ(position, ""); + + internal::impl::GetSignalAddedInfo(favs, newbies, "a", position, before); + EXPECT_FALSE(before); + EXPECT_EQ(position, "b"); + + internal::impl::GetSignalAddedInfo(favs, newbies, "c", position, before); + EXPECT_FALSE(before); + EXPECT_EQ(position, "a"); +} + + +TEST(TestFavoriteStorePrivate, TestGetRemoved) +{ + std::list<std::string> old; + std::list<std::string> fresh; + std::vector<std::string> result; + + old.push_back("a"); + old.push_back("b"); + old.push_back("c"); + old.push_back("d"); + + // No change. + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("c"); + fresh.push_back("d"); + + result = internal::impl::GetRemoved(old, fresh); + + EXPECT_TRUE(result.empty()); + + // Permutation. + fresh.clear(); + result.clear(); + fresh.push_back("a"); + fresh.push_back("c"); + fresh.push_back("b"); + fresh.push_back("d"); + + result = internal::impl::GetRemoved(old, fresh); + + EXPECT_TRUE(result.empty()); + + // a b c d -> b c + fresh.clear(); + result.clear(); + fresh.push_back("b"); + fresh.push_back("c"); + + result = internal::impl::GetRemoved(old, fresh); + + EXPECT_EQ(result.size(), 2); + EXPECT_EQ(result[0], "a"); + EXPECT_EQ(result[1], "d"); + + // a b c d -> a e f d + fresh.clear(); + result.clear(); + fresh.push_back("a"); + fresh.push_back("e"); + fresh.push_back("f"); + fresh.push_back("d"); + + + result = internal::impl::GetRemoved(old, fresh); + + EXPECT_EQ(result.size(), 2); + EXPECT_EQ(result[0], "b"); + EXPECT_EQ(result[1], "c"); +} + + +TEST(TestFavoriteStorePrivate, TestNeedToBeReorderedBasic) +{ + std::list<std::string> old; + std::list<std::string> fresh; + + old.push_back("a"); + old.push_back("b"); + old.push_back("c"); + old.push_back("d"); + + // No change. + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("c"); + fresh.push_back("d"); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); + + // Permutation. + fresh.clear(); + fresh.push_back("a"); + fresh.push_back("c"); + fresh.push_back("b"); + fresh.push_back("d"); + + EXPECT_TRUE(internal::impl::NeedToBeReordered(old, fresh)); + + // Empty. + fresh.clear(); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); +} + +TEST(TestFavoriteStorePrivate, TestNeedToBeReorderedLess) +{ + std::list<std::string> old; + std::list<std::string> fresh; + + old.push_back("a"); + old.push_back("b"); + old.push_back("c"); + old.push_back("d"); + + // a b c d -> a b c + fresh.clear(); + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("c"); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); + + // a b c d -> b c d + fresh.clear(); + fresh.push_back("b"); + fresh.push_back("c"); + fresh.push_back("d"); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); + + // a b c d -> a b d + fresh.clear(); + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("d"); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); + + // a b c d -> a + fresh.clear(); + fresh.push_back("a"); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); + + // a b c d -> a d b + fresh.clear(); + fresh.push_back("a"); + fresh.push_back("d"); + fresh.push_back("b"); + + EXPECT_TRUE(internal::impl::NeedToBeReordered(old, fresh)); + + // a b c d -> b a c + fresh.clear(); + fresh.push_back("b"); + fresh.push_back("a"); + fresh.push_back("c"); + + EXPECT_TRUE(internal::impl::NeedToBeReordered(old, fresh)); +} + +TEST(TestFavoriteStorePrivate, TestNeedToBeReorderedPlus) +{ + std::list<std::string> old; + std::list<std::string> fresh; + + old.push_back("a"); + old.push_back("b"); + old.push_back("c"); + old.push_back("d"); + + // All new elements. + fresh.clear(); + fresh.push_back("e"); + fresh.push_back("f"); + fresh.push_back("g"); + fresh.push_back("h"); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); + + // a b c d -> a b c d e + fresh.clear(); + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("c"); + fresh.push_back("d"); + fresh.push_back("e"); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); + + // a b c d -> a b e c d + fresh.clear(); + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("e"); + fresh.push_back("c"); + fresh.push_back("d"); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); + + // a b c d -> a b e d c + fresh.clear(); + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("e"); + fresh.push_back("d"); + fresh.push_back("c"); + + EXPECT_TRUE(internal::impl::NeedToBeReordered(old, fresh)); + + // a b c d -> f a b c d + fresh.clear(); + fresh.push_back("f"); + fresh.push_back("a"); + fresh.push_back("b"); + fresh.push_back("c"); + fresh.push_back("d"); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); +} + +TEST(TestFavoriteStorePrivate, TestNeedToBeReorderedMixed) +{ + std::list<std::string> old; + std::list<std::string> fresh; + + old.push_back("a"); + old.push_back("b"); + old.push_back("c"); + old.push_back("d"); + + // a b c d -> b f c g h + fresh.clear(); + fresh.push_back("b"); + fresh.push_back("f"); + fresh.push_back("c"); + fresh.push_back("g"); + fresh.push_back("h"); + + EXPECT_FALSE(internal::impl::NeedToBeReordered(old, fresh)); + + + // a b c d -> c f b g h + fresh.clear(); + fresh.push_back("c"); + fresh.push_back("f"); + fresh.push_back("b"); + fresh.push_back("g"); + fresh.push_back("h"); + + EXPECT_TRUE(internal::impl::NeedToBeReordered(old, fresh)); +} + diff --git a/tests/test_home_lens.cpp b/tests/test_home_lens.cpp new file mode 100644 index 000000000..069175ab5 --- /dev/null +++ b/tests/test_home_lens.cpp @@ -0,0 +1,362 @@ +#include <gtest/gtest.h> +#include <glib-object.h> +#include <dee.h> +#include <string> +#include <iostream> +#include <stdexcept> +#include <map> +#include <memory> +#include <sigc++/signal.h> +#include <sigc++/trackable.h> + +#include <UnityCore/GLibWrapper.h> +#include <UnityCore/Variant.h> +#include <UnityCore/HomeLens.h> +#include <UnityCore/Lens.h> +#include <UnityCore/Lenses.h> + +#include "test_utils.h" + +using namespace std; +using namespace unity::dash; + +namespace +{ + +/* + * FORWARDS + */ + +class StaticTestLens; + +typedef struct { + StaticTestLens* lens; + gchar* search_string; +} LensSearchClosure; + +static gboolean dispatch_global_search(gpointer userdata); + + +/* + * Mock Lens instance that does not use DBus. The default search does like this: + * For input "bar" output: + * + * i = 0 + * for letter in "bar": + * put result row [ "uri+$letter+$lens_id", "icon+$letter+$lens_id", i % 3, "mime+$letter+$lens_id", ...] + * i++ + * + * The mock lens has 3 categories: + * + * 0) "cat0+$lens_id" + * 1) "cat1+$lens_id" + * 2) "Shared cat" + */ +class StaticTestLens : public Lens +{ +public: + typedef std::shared_ptr<StaticTestLens> Ptr; + + StaticTestLens(string const& id, string const& name, string const& description, string const& search_hint) + : Lens(id, "", "", name, "lens-icon.png", + description, search_hint, true, "", + ModelType::LOCAL) + { + search_in_global(true); + + DeeModel* cats = categories()->model(); + DeeModel* results = global_results()->model(); + DeeModel* flters = filters()->model(); + + // Set model schemas + dee_model_set_schema(cats, "s", "s", "s", "a{sv}", NULL); + dee_model_set_schema(results, "s", "s", "u", "s", "s", "s", "s", NULL); + dee_model_set_schema(flters, "s", "s", "s", "s", "a{sv}", "b", "b", "b", NULL); + + // Populate categories model + ostringstream cat0, cat1; + cat0 << "cat0+" << id; + cat1 << "cat1+" << id; + GVariantBuilder b; + g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT); + GVariant *asv = g_variant_builder_end(&b); + + dee_model_append(cats, cat0.str().c_str(), "icon.png", "tile-vertical", asv); + dee_model_append(cats, cat1.str().c_str(), "icon.png", "tile-vertical", asv); + dee_model_append(cats, "Shared cat", "icon.png", "tile-vertical", asv); + } + + virtual ~StaticTestLens() {} + + virtual void DoGlobalSearch(string const& search_string) + { + DeeModel* model = global_results()->model(); + GVariant** row_buf = g_new(GVariant*, 8); + + row_buf[1] = g_variant_new_string(""); + row_buf[3] = g_variant_new_string(""); + row_buf[4] = g_variant_new_string(""); + row_buf[5] = g_variant_new_string(""); + row_buf[6] = g_variant_new_string(""); + row_buf[7] = NULL; + + unsigned int i; + for (i = 0; i < search_string.size(); i++) + { + ostringstream uri; + uri << "uri+" << search_string.at(i) << "+" << id(); + row_buf[0] = g_variant_new_string(uri.str().c_str()); + row_buf[2] = g_variant_new_uint32(i % 3); + + dee_model_append_row(model, row_buf); + } + + g_free(row_buf); + } + + void GlobalSearch(string const& search_string) + { + /* Dispatch search async, because that's */ + LensSearchClosure* closure = g_new0(LensSearchClosure, 1); + closure->lens = this; + closure->search_string = g_strdup(search_string.c_str()); + g_idle_add(dispatch_global_search, closure); + } + + void Search(string const& search_string) + { + + } + + void Activate(string const& uri) + { + + } + + void Preview(string const& uri) + { + + } + +}; + +static gboolean dispatch_global_search(gpointer userdata) +{ + LensSearchClosure* closure = (LensSearchClosure*) userdata; + + closure->lens->DoGlobalSearch(closure->search_string); + + g_free(closure->search_string); + g_free(closure); + + return FALSE; +} + +/* + * Mock Lenses class + */ +class StaticTestLenses : public Lenses +{ +public: + typedef std::shared_ptr<StaticTestLenses> Ptr; + + StaticTestLenses() + { + count.SetGetterFunction(sigc::mem_fun(&list_, &Lenses::LensList::size)); + } + + virtual ~StaticTestLenses() {} + + Lenses::LensList GetLenses() const + { + return list_; + } + + Lens::Ptr GetLens(std::string const& lens_id) const + { + for (auto lens : list_) + { + if (lens->id() == lens_id) + return lens; + } + return Lens::Ptr(); + } + + Lens::Ptr GetLensAtIndex(std::size_t index) const + { + return list_.at(index); + } + +protected: + Lenses::LensList list_; +}; + +class TwoStaticTestLenses : public StaticTestLenses +{ +public: + TwoStaticTestLenses() + : lens_1_(new StaticTestLens("first.lens", "First Lens", "The very first lens", "First search hint")) + , lens_2_(new StaticTestLens("second.lens", "Second Lens", "The second lens", "Second search hint")) + { + list_.push_back(lens_1_); + list_.push_back(lens_2_); + } + +private: + Lens::Ptr lens_1_; + Lens::Ptr lens_2_; +}; + +TEST(TestHomeLens, TestConstruction) +{ + HomeLens home_lens_("name", "description", "searchhint"); + + EXPECT_EQ(home_lens_.id(), "home.lens"); + EXPECT_EQ(home_lens_.connected, false); + EXPECT_EQ(home_lens_.search_in_global, false); + EXPECT_EQ(home_lens_.name, "name"); + EXPECT_EQ(home_lens_.description, "description"); + EXPECT_EQ(home_lens_.search_hint, "searchhint"); +} + +TEST(TestHomeLens, TestInitiallyEmpty) +{ + HomeLens home_lens_("name", "description", "searchhint"); + DeeModel* results = home_lens_.results()->model(); + DeeModel* categories = home_lens_.categories()->model();; + DeeModel* filters = home_lens_.filters()->model();; + + EXPECT_EQ(dee_model_get_n_rows(results), 0); + EXPECT_EQ(dee_model_get_n_rows(categories), 0); + EXPECT_EQ(dee_model_get_n_rows(filters), 0); + + EXPECT_EQ(home_lens_.count(), 0); +} + +TEST(TestHomeLens, TestTwoStaticLenses) +{ + HomeLens home_lens_("name", "description", "searchhint"); + TwoStaticTestLenses lenses_; + + home_lens_.AddLenses(lenses_); + + EXPECT_EQ(home_lens_.count, (size_t) 2); + + /* Test iteration of registered lensess */ + map<string,string> remaining; + remaining["first.lens"] = ""; + remaining["second.lens"] = ""; + for (auto lens : home_lens_.GetLenses()) + { + remaining.erase(lens->id()); + } + + EXPECT_EQ(remaining.size(), 0); + + /* Test sorting and GetAtIndex */ + EXPECT_EQ(home_lens_.GetLensAtIndex(0)->id(), "first.lens"); + EXPECT_EQ(home_lens_.GetLensAtIndex(1)->id(), "second.lens"); +} + +TEST(TestHomeLens, TestCategoryMerging) +{ + HomeLens home_lens_("name", "description", "searchhint"); + TwoStaticTestLenses lenses_; + DeeModel* cats = home_lens_.categories()->model(); + DeeModelIter* iter; + unsigned int cat0_first = 0, + cat1_first = 1, + cat_shared = 2, + cat0_second = 3, + cat1_second = 4; + const unsigned int NAME_COLUMN = 0; + + home_lens_.AddLenses(lenses_); + + EXPECT_EQ(dee_model_get_n_rows(cats), 5); // 5 because each lens has 3 cats, but 1 is shared between them + + /* Validate the merged categories */ + iter = dee_model_get_iter_at_row(cats, cat0_first); + EXPECT_EQ("cat0+first.lens", string(dee_model_get_string(cats, iter, NAME_COLUMN))); + + iter = dee_model_get_iter_at_row(cats, cat1_first); + EXPECT_EQ("cat1+first.lens", string(dee_model_get_string(cats, iter, NAME_COLUMN))); + + iter = dee_model_get_iter_at_row(cats, cat_shared); + EXPECT_EQ("Shared cat", string(dee_model_get_string(cats, iter, NAME_COLUMN))); + + iter = dee_model_get_iter_at_row(cats, cat0_second); + EXPECT_EQ("cat0+second.lens", string(dee_model_get_string(cats, iter, NAME_COLUMN))); + + iter = dee_model_get_iter_at_row(cats, cat1_second); + EXPECT_EQ("cat1+second.lens", string(dee_model_get_string(cats, iter, NAME_COLUMN))); +} + +// It's not that we must not support filters. It is just not implemented yet. +// But we actively test against it to make sure we don't end up with broken +// filters in the UI. When/if we land support for filters on the home screen +// this test should obviously be removed +TEST(TestHomeLens, TestIgnoreFilters) +{ + HomeLens home_lens_("name", "description", "searchhint"); + TwoStaticTestLenses lenses_; + DeeModel* filters = home_lens_.filters()->model(); + + EXPECT_EQ(dee_model_get_n_rows(filters), 0); +} + +TEST(TestHomeLens, TestOneSearch) +{ + HomeLens home_lens_("name", "description", "searchhint"); + TwoStaticTestLenses lenses_; + DeeModel* results = home_lens_.results()->model(); + DeeModel* cats = home_lens_.categories()->model(); + DeeModel* filters = home_lens_.filters()->model(); + DeeModelIter* iter; + unsigned int cat0_first = 0, + cat1_first = 1, + cat_shared = 2, + cat0_second = 3, + cat1_second = 4; + const unsigned int URI_COLUMN = 0; + const unsigned int CAT_COLUMN = 2; + + home_lens_.AddLenses(lenses_); + + home_lens_.Search("ape"); + + Utils::WaitForTimeoutMSec(); + + /* Validate counts */ + EXPECT_EQ(dee_model_get_n_rows(results), 6); // 3 hits from each lens + EXPECT_EQ(dee_model_get_n_rows(cats), 5); // 5 because each lens has 3 cats, but 1 is shared between them + EXPECT_EQ(dee_model_get_n_rows(filters), 0); // We ignore filters deliberately currently + + /* Validate results. In particular that we get the correct merged + * category offsets assigned */ + iter = dee_model_get_iter_at_row(results, 0); + EXPECT_EQ(string("uri+a+first.lens"), string(dee_model_get_string(results, iter, URI_COLUMN))); + EXPECT_EQ(cat0_first, dee_model_get_uint32(results, iter, CAT_COLUMN)); + + iter = dee_model_get_iter_at_row(results, 1); + EXPECT_EQ(string("uri+p+first.lens"), string(dee_model_get_string(results, iter, URI_COLUMN))); + EXPECT_EQ(cat1_first, dee_model_get_uint32(results, iter, CAT_COLUMN)); + + iter = dee_model_get_iter_at_row(results, 2); + EXPECT_EQ(string("uri+e+first.lens"), string(dee_model_get_string(results, iter, URI_COLUMN))); + EXPECT_EQ(cat_shared, dee_model_get_uint32(results, iter, CAT_COLUMN)); + + iter = dee_model_get_iter_at_row(results, 3); + EXPECT_EQ(string("uri+a+second.lens"), string(dee_model_get_string(results, iter, URI_COLUMN))); + EXPECT_EQ(cat0_second, dee_model_get_uint32(results, iter, CAT_COLUMN)); + + iter = dee_model_get_iter_at_row(results, 4); + EXPECT_EQ(string("uri+p+second.lens"), string(dee_model_get_string(results, iter, URI_COLUMN))); + EXPECT_EQ(cat1_second, dee_model_get_uint32(results, iter, CAT_COLUMN)); + + iter = dee_model_get_iter_at_row(results, 5); + EXPECT_EQ(string("uri+e+second.lens"), string(dee_model_get_string(results, iter, URI_COLUMN))); + EXPECT_EQ(cat_shared, dee_model_get_uint32(results, iter, CAT_COLUMN)); +} + +} diff --git a/tests/test_utils.h b/tests/test_utils.h index fa0c47414..a378487fe 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -51,6 +51,33 @@ public: return g_timeout_add_seconds(timeout_duration, TimeoutCallback, timeout_reached); } + static guint32 ScheduleTimeoutMSec(bool* timeout_reached, unsigned int timeout_duration = 10) + { + return g_timeout_add(timeout_duration, TimeoutCallback, timeout_reached); + } + + static void WaitForTimeout(unsigned int timeout_duration = 10) + { + bool timeout_reached = false; + guint32 timeout_id = ScheduleTimeout(&timeout_reached, timeout_duration); + + while (!timeout_reached) + g_main_context_iteration(g_main_context_get_thread_default(), TRUE); + + g_source_remove(timeout_id); + } + + static void WaitForTimeoutMSec(unsigned int timeout_duration = 10) + { + bool timeout_reached = false; + guint32 timeout_id = ScheduleTimeoutMSec(&timeout_reached, timeout_duration); + + while (!timeout_reached) + g_main_context_iteration(g_main_context_get_thread_default(), TRUE); + + g_source_remove(timeout_id); + } + private: static gboolean TimeoutCallback(gpointer data) { |
