Back to index

unity  6.0.0
LensView.cpp
Go to the documentation of this file.
00001 // -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
00002 /*
00003  * Copyright (C) 2010, 2011 Canonical Ltd
00004  *
00005  * This program is free software: you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License version 3 as
00007  * published by the Free Software Foundation.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016  *
00017  * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
00018  */
00019 
00020 #include "LensView.h"
00021 #include "LensViewPrivate.h"
00022 
00023 #include <boost/lexical_cast.hpp>
00024 
00025 #include <NuxCore/Logger.h>
00026 
00027 #include "unity-shared/DashStyle.h"
00028 #include "ResultRendererTile.h"
00029 #include "ResultRendererHorizontalTile.h"
00030 #include "unity-shared/UBusMessages.h"
00031 #include "unity-shared/UBusWrapper.h"
00032 #include "PlacesVScrollBar.h"
00033 
00034 #include <glib/gi18n-lib.h>
00035 
00036 namespace unity
00037 {
00038 namespace dash
00039 {
00040 namespace
00041 {
00042 nux::logging::Logger logger("unity.dash.lensview");
00043 
00044 const int CARD_VIEW_GAP_VERT  = 24; // pixels
00045 const int CARD_VIEW_GAP_HORIZ = 25; // pixels
00046 }
00047 
00048 // This is so we can access some protected members in scrollview.
00049 class LensScrollView: public nux::ScrollView
00050 {
00051 public:
00052   LensScrollView(nux::VScrollBar* scroll_bar, NUX_FILE_LINE_DECL)
00053     : nux::ScrollView(NUX_FILE_LINE_PARAM)
00054     , right_area_(nullptr)
00055     , up_area_(nullptr)
00056   {
00057     SetVScrollBar(scroll_bar);
00058   }
00059 
00060   void ScrollToPosition(nux::Geometry const& position)
00061   {
00062     // much of this code is copied from Nux/ScrollView.cpp
00063     nux::Geometry const& geo = GetGeometry();
00064 
00065     int child_y = position.y - geo.y;
00066     int child_y_diff = child_y - abs (_delta_y);
00067 
00068     if (child_y_diff + position.height < geo.height && child_y_diff >= 0)
00069     {
00070       return;
00071     }
00072 
00073     if (child_y_diff < 0)
00074     {
00075       ScrollUp (1, abs (child_y_diff));
00076     }
00077     else
00078     {
00079       int size = child_y_diff - geo.height;
00080 
00081       // always keeps the top of a view on the screen
00082       size += position.height;
00083 
00084       ScrollDown (1, size);
00085     }
00086   }
00087 
00088   void SetRightArea(nux::Area* area)
00089   {
00090     right_area_ = area;
00091   }
00092 
00093   void SetUpArea(nux::Area* area)
00094   {
00095     up_area_ = area;
00096   }
00097 
00098 protected:
00099 
00100   // This is so we can break the natural key navigation path.
00101   nux::Area* KeyNavIteration(nux::KeyNavDirection direction)
00102   {
00103     nux::Area* focus_area = nux::GetWindowCompositor().GetKeyFocusArea();
00104 
00105     if (direction == nux::KEY_NAV_RIGHT && focus_area && focus_area->IsChildOf(this))
00106       return right_area_;
00107     else if (direction == nux::KEY_NAV_UP && focus_area && focus_area->IsChildOf(this))
00108       return up_area_;
00109     else
00110       return nux::ScrollView::KeyNavIteration(direction);
00111   }
00112 
00113 private:
00114   nux::Area* right_area_;
00115   nux::Area* up_area_;
00116 };
00117 
00118 
00119 NUX_IMPLEMENT_OBJECT_TYPE(LensView);
00120 
00121 LensView::LensView()
00122   : nux::View(NUX_TRACKER_LOCATION)
00123   , filters_expanded(false)
00124   , can_refine_search(false)
00125   , no_results_active_(false)
00126 {}
00127 
00128 LensView::LensView(Lens::Ptr lens, nux::Area* show_filters)
00129   : nux::View(NUX_TRACKER_LOCATION)
00130   , filters_expanded(false)
00131   , can_refine_search(false)
00132   , lens_(lens)
00133   , initial_activation_(true)
00134   , no_results_active_(false)
00135 {
00136   SetupViews(show_filters);
00137   SetupCategories();
00138   SetupResults();
00139   SetupFilters();
00140 
00141   dash::Style::Instance().columns_changed.connect(sigc::mem_fun(this, &LensView::OnColumnsChanged));
00142 
00143   lens_->connected.changed.connect([&](bool is_connected) { if (is_connected) initial_activation_ = true; });
00144   search_string.SetGetterFunction(sigc::mem_fun(this, &LensView::get_search_string));
00145   filters_expanded.changed.connect([&](bool expanded) { fscroll_view_->SetVisible(expanded); QueueRelayout(); OnColumnsChanged(); });
00146   view_type.changed.connect(sigc::mem_fun(this, &LensView::OnViewTypeChanged));
00147 
00148   ubus_manager_.RegisterInterest(UBUS_RESULT_VIEW_KEYNAV_CHANGED, [&] (GVariant* data) {
00149     // we get this signal when a result view keynav changes,
00150     // its a bad way of doing this but nux ABI needs to be broken
00151     // to do it properly
00152     nux::Geometry focused_pos;
00153     g_variant_get (data, "(iiii)", &focused_pos.x, &focused_pos.y, &focused_pos.width, &focused_pos.height);
00154 
00155     for (auto category : categories_)
00156     {
00157       if (category->GetLayout() != nullptr)
00158       {
00159         auto expand_label = category->GetHeaderFocusableView();
00160         auto child = category->GetChildView();
00161 
00162         if ((child && child->HasKeyFocus()) ||
00163             (expand_label && expand_label->HasKeyFocus()))
00164         {
00165 
00166           focused_pos.x += child->GetGeometry().x;
00167           focused_pos.y += child->GetGeometry().y - 30;
00168           focused_pos.height += 30;
00169           scroll_view_->ScrollToPosition(focused_pos);
00170           break;
00171         }
00172       }
00173     }
00174   });
00175 
00176 }
00177 
00178 void LensView::SetupViews(nux::Area* show_filters)
00179 {
00180   dash::Style& style = dash::Style::Instance();
00181 
00182   layout_ = new nux::HLayout(NUX_TRACKER_LOCATION);
00183   layout_->SetSpaceBetweenChildren(style.GetSpaceBetweenLensAndFilters());
00184 
00185   scroll_view_ = new LensScrollView(new PlacesVScrollBar(NUX_TRACKER_LOCATION),
00186                                     NUX_TRACKER_LOCATION);
00187   scroll_view_->EnableVerticalScrollBar(true);
00188   scroll_view_->EnableHorizontalScrollBar(false);
00189   layout_->AddView(scroll_view_);
00190 
00191   scroll_layout_ = new nux::VLayout(NUX_TRACKER_LOCATION);
00192   scroll_view_->SetLayout(scroll_layout_);
00193   scroll_view_->SetRightArea(show_filters);
00194 
00195   no_results_ = new nux::StaticCairoText("", NUX_TRACKER_LOCATION);
00196   no_results_->SetTextColor(nux::color::White);
00197   no_results_->SetVisible(false);
00198   scroll_layout_->AddView(no_results_, 1, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_MATCHCONTENT);
00199 
00200   fscroll_view_ = new LensScrollView(new PlacesVScrollBar(NUX_TRACKER_LOCATION), NUX_TRACKER_LOCATION);
00201   fscroll_view_->EnableVerticalScrollBar(true);
00202   fscroll_view_->EnableHorizontalScrollBar(false);
00203   fscroll_view_->SetVisible(false);
00204   fscroll_view_->SetUpArea(show_filters);
00205   layout_->AddView(fscroll_view_, 1);
00206 
00207   fscroll_layout_ = new nux::VLayout();
00208   fscroll_view_->SetLayout(fscroll_layout_);
00209 
00210   filter_bar_ = new FilterBar();
00211   int width = style.GetFilterBarWidth() +
00212               style.GetFilterBarLeftPadding() +
00213               style.GetFilterBarRightPadding();
00214 
00215   fscroll_view_->SetMinimumWidth(width + style.GetFilterViewRightPadding());
00216   fscroll_view_->SetMaximumWidth(width + style.GetFilterViewRightPadding());
00217   filter_bar_->SetMinimumWidth(width);
00218   filter_bar_->SetMaximumWidth(width);
00219   AddChild(filter_bar_);
00220   fscroll_layout_->AddView(filter_bar_, 0);
00221 
00222   SetLayout(layout_);
00223 }
00224 
00225 void LensView::SetupCategories()
00226 {
00227   Categories::Ptr categories = lens_->categories;
00228   categories->category_added.connect(sigc::mem_fun(this, &LensView::OnCategoryAdded));
00229 
00230   for (unsigned int i = 0; i < categories->count(); ++i)
00231     OnCategoryAdded(categories->RowAtIndex(i));
00232 }
00233 
00234 void LensView::SetupResults()
00235 {
00236   Results::Ptr results = lens_->results;
00237   results->result_added.connect(sigc::mem_fun(this, &LensView::OnResultAdded));
00238   results->result_removed.connect(sigc::mem_fun(this, &LensView::OnResultRemoved));
00239 
00240   for (unsigned int i = 0; i < results->count(); ++i)
00241     OnResultAdded(results->RowAtIndex(i));
00242 }
00243 
00244 void LensView::SetupFilters()
00245 {
00246   Filters::Ptr filters = lens_->filters;
00247   filters->filter_added.connect(sigc::mem_fun(this, &LensView::OnFilterAdded));
00248   filters->filter_removed.connect(sigc::mem_fun(this, &LensView::OnFilterRemoved));
00249 
00250   for (unsigned int i = 0; i < filters->count(); ++i)
00251     OnFilterAdded(filters->FilterAtIndex(i));
00252 }
00253 
00254 void LensView::OnCategoryAdded(Category const& category)
00255 {
00256   std::string name = category.name;
00257   std::string icon_hint = category.icon_hint;
00258   std::string renderer_name = category.renderer_name;
00259   int index = category.index;
00260 
00261   LOG_DEBUG(logger) << "Category added: " << name
00262                     << "(" << icon_hint
00263                     << ", " << renderer_name
00264                     << ", " << boost::lexical_cast<int>(index) << ")";
00265 
00266   PlacesGroup* group = new PlacesGroup();
00267   AddChild(group);
00268   group->SetName(name);
00269   group->SetIcon(icon_hint);
00270   group->SetExpanded(false);
00271   group->SetVisible(false);
00272   group->expanded.connect(sigc::mem_fun(this, &LensView::OnGroupExpanded));
00273 
00274 
00275   /* Add the group at the correct offset into the categories vector */
00276   categories_.insert(categories_.begin() + index, group);
00277 
00278   /* Reset result count */
00279   counts_[group] = 0;
00280 
00281   ResultViewGrid* grid = new ResultViewGrid(NUX_TRACKER_LOCATION);
00282   grid->expanded = false;
00283   if (renderer_name == "tile-horizontal")
00284   {
00285     grid->SetModelRenderer(new ResultRendererHorizontalTile(NUX_TRACKER_LOCATION));
00286     grid->horizontal_spacing = CARD_VIEW_GAP_HORIZ;
00287     grid->vertical_spacing = CARD_VIEW_GAP_VERT;
00288   }    
00289   else
00290     grid->SetModelRenderer(new ResultRendererTile(NUX_TRACKER_LOCATION));
00291 
00292   grid->UriActivated.connect([&] (std::string const& uri) { uri_activated.emit(uri); lens_->Activate(uri); });
00293   group->SetChildView(grid);
00294 
00295   /* We need the full range of method args so we can specify the offset
00296    * of the group into the layout */
00297   scroll_layout_->AddView(group, 0, nux::MinorDimensionPosition::eAbove,
00298                           nux::MinorDimensionSize::eFull, 100.0f,
00299                           (nux::LayoutPosition)index);
00300 }
00301 
00302 void LensView::OnResultAdded(Result const& result)
00303 {
00304   try {
00305     PlacesGroup* group = categories_.at(result.category_index);
00306     ResultViewGrid* grid = static_cast<ResultViewGrid*>(group->GetChildView());
00307 
00308     std::string uri = result.uri;
00309     LOG_TRACE(logger) << "Result added: " << uri;
00310 
00311     grid->AddResult(const_cast<Result&>(result));
00312     counts_[group]++;
00313     UpdateCounts(group);
00314     // make sure we don't display the no-results-hint if we do have results
00315     if (G_UNLIKELY (no_results_active_))
00316     {
00317       CheckNoResults(Lens::Hints());
00318     }
00319   } catch (std::out_of_range& oor) {
00320     LOG_WARN(logger) << "Result does not have a valid category index: "
00321                      << boost::lexical_cast<unsigned int>(result.category_index)
00322                      << ". Is out of range.";
00323   }
00324 }
00325 
00326 void LensView::OnResultRemoved(Result const& result)
00327 {
00328   try {
00329     PlacesGroup* group = categories_.at(result.category_index);
00330     ResultViewGrid* grid = static_cast<ResultViewGrid*>(group->GetChildView());
00331 
00332     std::string uri = result.uri;
00333     LOG_TRACE(logger) << "Result removed: " << uri;
00334 
00335     grid->RemoveResult(const_cast<Result&>(result));
00336     counts_[group]--;
00337     UpdateCounts(group);
00338   } catch (std::out_of_range& oor) {
00339     LOG_WARN(logger) << "Result does not have a valid category index: "
00340                      << boost::lexical_cast<unsigned int>(result.category_index)
00341                      << ". Is out of range.";
00342   }
00343 }
00344 
00345 void LensView::UpdateCounts(PlacesGroup* group)
00346 {
00347   unsigned int columns = dash::Style::Instance().GetDefaultNColumns();
00348   columns -= filters_expanded ? 2 : 0;
00349 
00350   group->SetCounts(columns, counts_[group]);
00351   group->SetVisible(counts_[group]);
00352 
00353   QueueFixRenderering();
00354 }
00355 
00356 void LensView::QueueFixRenderering()
00357 {
00358   if (fix_rendering_idle_)
00359     return;
00360 
00361   fix_rendering_idle_.reset(new glib::Idle(sigc::mem_fun(this, &LensView::FixRenderering),
00362                                            glib::Source::Priority::DEFAULT));
00363 }
00364 
00365 bool LensView::FixRenderering()
00366 {
00367   std::list<AbstractPlacesGroup*> groups;
00368 
00369   for (auto child : scroll_layout_->GetChildren())
00370   {
00371     if (child == no_results_)
00372       continue;
00373 
00374     groups.push_back(static_cast<AbstractPlacesGroup*>(child));
00375   }
00376 
00377   dash::impl::UpdateDrawSeparators(groups);
00378 
00379   fix_rendering_idle_.reset();
00380   return false;
00381 }
00382 
00383 void LensView::CheckNoResults(Lens::Hints const& hints)
00384 {
00385   gint count = lens_->results()->count();
00386 
00387   if (count == 0 && !no_results_active_ && !search_string_.empty())
00388   {
00389     std::stringstream markup;
00390     Lens::Hints::const_iterator it;
00391 
00392     it = hints.find("no-results-hint");
00393     markup << "<span size='larger' weight='bold'>";
00394 
00395     if (it != hints.end())
00396     {
00397       markup << it->second.GetString();
00398     }
00399     else
00400     {
00401       markup << _("Sorry, there is nothing that matches your search.");
00402     }
00403     markup << "</span>";
00404 
00405     LOG_DEBUG(logger) << "The no-result-hint is: " << markup.str();
00406 
00407     scroll_layout_->SetContentDistribution(nux::MAJOR_POSITION_CENTER);
00408 
00409     no_results_active_ = true;
00410     no_results_->SetText(markup.str());
00411     no_results_->SetVisible(true);
00412   }
00413   else if (count && no_results_active_)
00414   {
00415     scroll_layout_->SetContentDistribution(nux::MAJOR_POSITION_START);
00416 
00417     no_results_active_ = false;
00418     no_results_->SetText("");
00419     no_results_->SetVisible(false);
00420   }
00421 }
00422 
00423 void LensView::HideResultsMessage()
00424 {
00425   if (no_results_active_)
00426   {
00427     scroll_layout_->SetContentDistribution(nux::MAJOR_POSITION_START);
00428     no_results_active_ = false;
00429     no_results_->SetText("");
00430     no_results_->SetVisible(false);
00431   }
00432 }
00433 
00434 void LensView::PerformSearch(std::string const& search_query)
00435 {
00436   search_string_ = search_query;
00437   lens_->Search(search_query);
00438 }
00439 
00440 std::string LensView::get_search_string() const
00441 {
00442   return search_string_;
00443 }
00444 
00445 void LensView::OnGroupExpanded(PlacesGroup* group)
00446 {
00447   ResultViewGrid* grid = static_cast<ResultViewGrid*>(group->GetChildView());
00448   grid->expanded = group->GetExpanded();
00449   ubus_manager_.SendMessage(UBUS_PLACE_VIEW_QUEUE_DRAW);
00450 }
00451 
00452 void LensView::OnColumnsChanged()
00453 {
00454   unsigned int columns = dash::Style::Instance().GetDefaultNColumns();
00455   columns -= filters_expanded ? 2 : 0;
00456 
00457   for (auto group: categories_)
00458   {
00459     group->SetCounts(columns, counts_[group]);
00460   }
00461 }
00462 
00463 void LensView::OnFilterAdded(Filter::Ptr filter)
00464 {
00465   filter_bar_->AddFilter(filter);
00466   can_refine_search = true;
00467 }
00468 
00469 void LensView::OnFilterRemoved(Filter::Ptr filter)
00470 {
00471   filter_bar_->RemoveFilter(filter);
00472 }
00473 
00474 void LensView::OnViewTypeChanged(ViewType view_type)
00475 {
00476   if (view_type != HIDDEN && initial_activation_)
00477   {
00478     /* We reset the lens for ourselves, in case this is a restart or something */
00479     lens_->Search(search_string_);
00480     initial_activation_ = false;
00481   }
00482 
00483   lens_->view_type = view_type;
00484 }
00485 
00486 void LensView::Draw(nux::GraphicsEngine& gfx_context, bool force_draw)
00487 {
00488   nux::Geometry const& geo = GetGeometry();
00489 
00490   gfx_context.PushClippingRectangle(geo);
00491   nux::GetPainter().PaintBackground(gfx_context, geo);
00492   gfx_context.PopClippingRectangle();
00493 }
00494 
00495 void LensView::DrawContent(nux::GraphicsEngine& gfx_context, bool force_draw)
00496 {
00497   gfx_context.PushClippingRectangle(GetGeometry());
00498   layout_->ProcessDraw(gfx_context, force_draw);
00499   gfx_context.PopClippingRectangle();
00500 }
00501 
00502 Lens::Ptr LensView::lens() const
00503 {
00504   return lens_;
00505 }
00506 
00507 nux::Area* LensView::fscroll_view() const
00508 {
00509   return fscroll_view_;
00510 }
00511 
00512 int LensView::GetNumRows()
00513 {
00514   unsigned int columns = dash::Style::Instance().GetDefaultNColumns();
00515   columns -= filters_expanded ? 2 : 0;
00516 
00517   int num_rows = 0;
00518   for (auto group: categories_)
00519   {
00520     if (group->IsVisible())
00521     {
00522       num_rows += 1; // The category header
00523 
00524       if (group->GetExpanded() && columns)
00525         num_rows += ceil(counts_[group] / static_cast<double>(columns));
00526       else
00527         num_rows += 1;
00528     }
00529   }
00530 
00531   return num_rows;
00532 }
00533 
00534 void LensView::JumpToTop()
00535 {
00536   scroll_view_->ScrollToPosition(nux::Geometry(0, 0, 0, 0));
00537 }
00538 
00539 void LensView::ActivateFirst()
00540 {
00541   Results::Ptr results = lens_->results;
00542   if (results->count())
00543   {
00544     for (unsigned int c = 0; c < scroll_layout_->GetChildren().size(); ++c)
00545     {
00546       for (unsigned int i = 0; i < results->count(); ++i)
00547       {
00548         Result result = results->RowAtIndex(i);
00549         if (result.category_index == c && result.uri != "")
00550         {
00551           uri_activated(result.uri);
00552           lens_->Activate(result.uri);
00553           return;
00554         }
00555       }
00556     }
00557     // Fallback
00558     Result result = results->RowAtIndex(0);
00559     if (result.uri != "")
00560     {
00561       uri_activated(result.uri);
00562       lens_->Activate(result.uri);
00563     }
00564   }
00565 }
00566 
00567 // Keyboard navigation
00568 bool LensView::AcceptKeyNavFocus()
00569 {
00570   return false;
00571 }
00572 
00573 // Introspectable
00574 std::string LensView::GetName() const
00575 {
00576   return "LensView";
00577 }
00578 
00579 void LensView::AddProperties(GVariantBuilder* builder)
00580 {
00581   unity::variant::BuilderWrapper(builder)
00582     .add("name", lens_->id)
00583     .add("lens-name", lens_->name)
00584     .add("no-results-active", no_results_active_);
00585 }
00586 
00587 
00588 }
00589 }