/* * Copyright (C) 2011-2015 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 . * * Authored by: Jason Smith * Marco Trevisan */ #include #include "SwitcherModel.h" #include "unity-shared/WindowManager.h" #include namespace unity { using launcher::AbstractLauncherIcon; namespace switcher { namespace { /** * Helper comparison functor for sorting application icons. */ bool CompareSwitcherItemsPriority(AbstractLauncherIcon::Ptr const& first, AbstractLauncherIcon::Ptr const& second) { return first->SwitcherPriority() > second->SwitcherPriority(); } } SwitcherModel::SwitcherModel(Applications const& icons, bool sort_by_priority) : detail_selection(false) , detail_selection_index(0) , only_apps_on_viewport(true) , applications_(icons) , sort_by_priority_(sort_by_priority) , index_(0) , last_index_(0) , row_index_(0) { for (auto it = applications_.begin(); it != applications_.end();) { ConnectToIconSignals(*it); if (!(*it)->ShowInSwitcher(only_apps_on_viewport)) { hidden_applications_.push_back(*it); it = applications_.erase(it); continue; } ++it; } if (sort_by_priority_) std::sort(std::begin(applications_), std::end(applications_), CompareSwitcherItemsPriority); UpdateLastActiveApplication(); only_apps_on_viewport.changed.connect([this] (bool) { VerifyApplications(); }); detail_selection.changed.connect([this] (bool) { UpdateDetailXids(); }); } void SwitcherModel::UpdateLastActiveApplication() { for (auto const& application : applications_) { if (application->GetQuirk(AbstractLauncherIcon::Quirk::ACTIVE)) { last_active_application_ = application; break; } } } void SwitcherModel::VerifyApplications() { bool anything_changed = false; for (auto it = applications_.begin(); it != applications_.end();) { if (!(*it)->ShowInSwitcher(only_apps_on_viewport)) { unsigned icon_index = it - applications_.begin(); hidden_applications_.push_back(*it); it = applications_.erase(it); anything_changed = true; bool was_in_detail = (detail_selection && icon_index == index_); if (icon_index < index_ || index_ == applications_.size()) PrevIndex(); if (was_in_detail) UnsetDetailSelection(); continue; } ++it; } for (auto it = hidden_applications_.begin(); it != hidden_applications_.end();) { if ((*it)->ShowInSwitcher(only_apps_on_viewport)) { InsertIcon(*it); it = hidden_applications_.erase(it); anything_changed = true; continue; } ++it; } if (anything_changed) { if (!last_active_application_ || !last_active_application_->ShowInSwitcher(only_apps_on_viewport)) UpdateLastActiveApplication(); updated.emit(); } } void SwitcherModel::InsertIcon(AbstractLauncherIcon::Ptr const& application) { if (sort_by_priority_) { auto pos = std::upper_bound(applications_.begin(), applications_.end(), application, CompareSwitcherItemsPriority); unsigned icon_index = pos - applications_.begin(); applications_.insert(pos, application); if (icon_index <= index_) NextIndex(); } else { applications_.push_back(application); } } void SwitcherModel::ConnectToIconSignals(launcher::AbstractLauncherIcon::Ptr const& icon) { icon->quirks_changed.connect(sigc::hide(sigc::hide(sigc::mem_fun(this, &SwitcherModel::OnIconQuirksChanged)))); icon->windows_changed.connect(sigc::hide(sigc::bind(sigc::mem_fun(this, &SwitcherModel::OnIconWindowsUpdated), icon.GetPointer()))); } void SwitcherModel::AddIcon(AbstractLauncherIcon::Ptr const& icon) { if (!icon) return; if (icon->ShowInSwitcher(only_apps_on_viewport)) { if (icon->GetQuirk(AbstractLauncherIcon::Quirk::ACTIVE)) last_active_application_ = icon; if (std::find(applications_.begin(), applications_.end(), icon) == applications_.end()) { InsertIcon(icon); ConnectToIconSignals(icon); updated.emit(); } } else if (std::find(hidden_applications_.begin(), hidden_applications_.end(), icon) == hidden_applications_.end()) { hidden_applications_.push_back(icon); ConnectToIconSignals(icon); } } void SwitcherModel::RemoveIcon(launcher::AbstractLauncherIcon::Ptr const& icon) { auto icon_it = std::find(applications_.begin(), applications_.end(), icon); if (icon_it != applications_.end()) { unsigned icon_index = icon_it - applications_.begin(); bool was_in_detail = (detail_selection && icon_index == index_); applications_.erase(icon_it); if (last_active_application_ == icon) UpdateLastActiveApplication(); if (icon_index < index_ || index_ == applications_.size()) PrevIndex(); if (was_in_detail) UnsetDetailSelection(); updated.emit(); } else { hidden_applications_.erase(std::remove(hidden_applications_.begin(), hidden_applications_.end(), icon), hidden_applications_.end()); } } void SwitcherModel::OnIconQuirksChanged() { auto old_selection = Selection(); VerifyApplications(); if (old_selection == last_active_application_) UpdateLastActiveApplication(); auto const& new_selection = Selection(); if (old_selection != new_selection) selection_changed.emit(new_selection); } void SwitcherModel::OnIconWindowsUpdated(AbstractLauncherIcon* icon) { if (detail_selection() && icon == Selection().GetPointer()) { UpdateDetailXids(); if (detail_selection_index() >= detail_xids_.size()) detail_selection_index = detail_xids_.empty() ? 0 : detail_xids_.size() - 1; } updated.emit(); } std::string SwitcherModel::GetName() const { return "SwitcherModel"; } void SwitcherModel::AddProperties(debug::IntrospectionData& introspection) { introspection .add("detail-selection", detail_selection) .add("detail-selection-index", detail_selection_index()) .add("detail-current-count", SelectionWindows().size()) .add("detail-windows", glib::Variant::FromVector(SelectionWindows())) .add("only-apps-on-viewport", only_apps_on_viewport()) .add("selection-index", SelectionIndex()) .add("last-selection-index", LastSelectionIndex()); } debug::Introspectable::IntrospectableList SwitcherModel::GetIntrospectableChildren() { debug::Introspectable::IntrospectableList children; unsigned order = 0; for (auto const& icon : applications_) { if (!icon->ShowInSwitcher(only_apps_on_viewport)) { icon->SetOrder(++order); children.push_back(icon.GetPointer()); } } return children; } SwitcherModel::iterator SwitcherModel::begin() { return applications_.begin(); } SwitcherModel::iterator SwitcherModel::end() { return applications_.end(); } SwitcherModel::reverse_iterator SwitcherModel::rbegin() { return applications_.rbegin(); } SwitcherModel::reverse_iterator SwitcherModel::rend() { return applications_.rend(); } AbstractLauncherIcon::Ptr SwitcherModel::at(unsigned int index) const { if (index >= applications_.size()) return AbstractLauncherIcon::Ptr(); return applications_[index]; } size_t SwitcherModel::Size() const { return applications_.size(); } AbstractLauncherIcon::Ptr SwitcherModel::Selection() const { return index_ < applications_.size() ? applications_.at(index_) : AbstractLauncherIcon::Ptr(); } int SwitcherModel::SelectionIndex() const { return index_; } bool SwitcherModel::SelectionIsActive() const { auto const& selection = Selection(); return selection ? selection->GetQuirk(AbstractLauncherIcon::Quirk::ACTIVE) : false; } AbstractLauncherIcon::Ptr SwitcherModel::LastSelection() const { return applications_.at(last_index_); } int SwitcherModel::LastSelectionIndex() const { return last_index_; } std::vector const& SwitcherModel::DetailXids() const { return detail_xids_; } std::vector SwitcherModel::SelectionWindows() const { if (!detail_xids_.empty()) return detail_xids_; WindowManager& wm = WindowManager::Default(); std::vector results; auto const& selection = Selection(); if (!selection) return results; for (auto& window : selection->Windows()) { Window xid = window->window_id(); if (!only_apps_on_viewport || wm.IsWindowOnCurrentDesktop(xid)) results.push_back(xid); } if (results.empty()) return results; std::sort(results.begin(), results.end(), [&wm](Window first, Window second) { return wm.GetWindowActiveNumber(first) > wm.GetWindowActiveNumber(second); }); if (selection == last_active_application_) { results.push_back(results.front()); results.erase(results.begin()); } return results; } void SwitcherModel::UpdateDetailXids() { detail_xids_.clear(); if (detail_selection) detail_xids_ = SelectionWindows(); } Window SwitcherModel::DetailSelectionWindow() const { if (!detail_selection || detail_xids_.empty()) return 0; if (detail_selection_index > detail_xids_.size() - 1) return 0; return detail_xids_[detail_selection_index]; } void SwitcherModel::UnsetDetailSelection() { detail_selection = false; detail_selection_index = 0; row_index_ = 0; } void SwitcherModel::NextIndex() { if (applications_.empty()) return; last_index_ = index_; ++index_ %= applications_.size(); } void SwitcherModel::Next() { NextIndex(); UnsetDetailSelection(); selection_changed.emit(Selection()); } void SwitcherModel::PrevIndex() { if (applications_.empty()) return; last_index_ = index_; index_ = ((index_ > 0 && index_ < applications_.size()) ? index_ : applications_.size()) - 1; } void SwitcherModel::Prev() { PrevIndex(); UnsetDetailSelection(); selection_changed.emit(Selection()); } void SwitcherModel::NextDetail() { if (!detail_selection() || detail_xids_.empty()) return; detail_selection_index = (detail_selection_index + 1) % detail_xids_.size(); UpdateRowIndex(); } void SwitcherModel::PrevDetail() { if (!detail_selection() || detail_xids_.empty()) return; detail_selection_index = ((detail_selection_index() > 0) ? detail_selection_index : detail_xids_.size()) - 1; UpdateRowIndex(); } void SwitcherModel::UpdateRowIndex() { int current_index = detail_selection_index; unsigned int current_row = 0; for (auto r : row_sizes_) { current_index -= r; if (current_index < 0) { row_index_ = current_row; return; } ++current_row; } } unsigned int SwitcherModel::SumNRows(unsigned int n) const { unsigned int total = 0; if (n < row_sizes_.size()) for (unsigned int i = 0; i <= n; ++i) total += row_sizes_[i]; return total; } bool SwitcherModel::DetailIndexInLeftHalfOfRow() const { unsigned int half = row_sizes_[row_index_]/2; unsigned int total_above = (row_index_ > 0 ? SumNRows(row_index_ - 1) : 0); unsigned int diff = detail_selection_index - total_above; return (diff < half); } void SwitcherModel::NextDetailRow() { if (row_sizes_.size() && row_index_ < row_sizes_.size() - 1) { unsigned int current_row = row_sizes_[row_index_]; unsigned int next_row = row_sizes_[row_index_ + 1]; unsigned int increment = current_row; if (!DetailIndexInLeftHalfOfRow()) increment = next_row; detail_selection_index = detail_selection_index + increment; ++row_index_; } else { detail_selection_index = (detail_selection_index + 1) % detail_xids_.size(); } } void SwitcherModel::PrevDetailRow() { if (row_index_ > 0) { unsigned int current_row = row_sizes_[row_index_]; unsigned int prev_row = row_sizes_[row_index_ - 1]; unsigned int decrement = current_row; if (DetailIndexInLeftHalfOfRow()) decrement = prev_row; detail_selection_index = detail_selection_index - decrement; row_index_--; } else { detail_selection_index = ((detail_selection_index() > 0) ? detail_selection_index : detail_xids_.size()) - 1; } } bool SwitcherModel::HasNextDetailRow() const { return (detail_selection_index() < detail_xids_.size() - 1); } bool SwitcherModel::HasPrevDetailRow() const { return (detail_selection_index() > 0); } void SwitcherModel::SetRowSizes(std::vector const& row_sizes) { row_sizes_ = row_sizes; } void SwitcherModel::Select(AbstractLauncherIcon::Ptr const& selection) { unsigned i = 0; for (iterator it = begin(), e = end(); it != e; ++it) { if (*it == selection) { if (index_ != i) { last_index_ = index_; index_ = i; UnsetDetailSelection(); selection_changed.emit(Selection()); } break; } ++i; } } void SwitcherModel::Select(unsigned int index) { unsigned int target = CLAMP(index, 0, applications_.size() - 1); if (target != index_) { last_index_ = index_; index_ = target; UnsetDetailSelection(); selection_changed.emit(Selection()); } } } }