/* * Copyright (C) 2011-2013 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 */ #include #include #include #include "unity-shared/AnimationUtils.h" #include "unity-shared/UBusMessages.h" #include "unity-shared/WindowManager.h" #include "unity-shared/IconRenderer.h" #include "unity-shared/UnitySettings.h" #include "unity-shared/UScreen.h" #include "SwitcherController.h" #include "SwitcherControllerImpl.h" namespace unity { using launcher::AbstractLauncherIcon; using launcher::ActionArg; using ui::LayoutWindow; namespace { const std::string LAZY_TIMEOUT = "lazy-timeout"; const std::string SHOW_TIMEOUT = "show-timeout"; const std::string DETAIL_TIMEOUT = "detail-timeout"; const std::string VIEW_CONSTRUCT_IDLE = "view-construct-idle"; const unsigned FADE_DURATION = 80; const RawPixel XY_OFFSET = 100_em; } namespace switcher { Controller::Controller(WindowCreator const& create_window) : detail([this] { return impl_->model_ && impl_->model_->detail_selection(); }, [this] (bool d) { if (impl_->model_) { impl_->model_->detail_selection = d; } return false; }) , detail_mode([this] { return detail_mode_; }) , first_selection_mode(FirstSelectionMode::LAST_ACTIVE_VIEW) , show_desktop_disabled(false) , mouse_disabled(false) , timeout_length(0) , detail_on_timeout(true) , detail_timeout_length(500) , initial_detail_timeout_length(1500) , visible_(false) , monitor_(0) , detail_mode_(DetailMode::TAB_NEXT_WINDOW) , impl_(new Controller::Impl(this, 20, create_window)) {} Controller::~Controller() {} bool Controller::CanShowSwitcher(const std::vector& results) const { bool empty = (show_desktop_disabled() ? results.empty() : results.size() == 1); return (!empty && !WindowManager::Default().IsWallActive()); } void Controller::Show(ShowMode show, SortMode sort, std::vector const& results) { auto uscreen = UScreen::GetDefault(); monitor_ = uscreen->GetMonitorWithMouse(); impl_->Show(show, sort, results); } void Controller::AddIcon(AbstractLauncherIcon::Ptr const& icon) { impl_->AddIcon(icon); } void Controller::RemoveIcon(AbstractLauncherIcon::Ptr const& icon) { impl_->RemoveIcon(icon); } void Controller::Select(int index) { if (Visible()) impl_->model_->Select(index); } void Controller::Hide(bool accept_state) { if (Visible()) { impl_->Hide(accept_state); } } bool Controller::Visible() { return visible_; } nux::Geometry Controller::GetInputWindowGeometry() const { if (impl_->view_window_) return impl_->view_window_->GetGeometry(); return {0, 0, 0, 0}; } void Controller::Impl::StartDetailMode() { if (obj_->visible_) { if (obj_->detail() && HasNextDetailRow()) { NextDetailRow(); } else { SetDetail(true); } } } void Controller::Impl::StopDetailMode() { if (obj_->visible_) { if (obj_->detail() && HasPrevDetailRow()) { PrevDetailRow(); } else { SetDetail(false); } } } void Controller::Impl::CloseSelection() { if (obj_->detail()) { if (model_->detail_selection) { WindowManager::Default().Close(model_->DetailSelectionWindow()); } } else { // Using model_->Selection()->Close() would be nicer, but it wouldn't take // in consideration the workspace related settings for (auto window : model_->SelectionWindows()) WindowManager::Default().Close(window); } } void Controller::Next() { impl_->Next(); } void Controller::Prev() { impl_->Prev(); } SwitcherView::Ptr Controller::GetView() const { return impl_->GetView(); } void Controller::SetDetail(bool value, unsigned int min_windows) { impl_->SetDetail(value, min_windows); } void Controller::InitiateDetail() { impl_->InitiateDetail(); } void Controller::NextDetail() { impl_->NextDetail(); } void Controller::PrevDetail() { impl_->PrevDetail(); } LayoutWindow::Vector const& Controller::ExternalRenderTargets() const { return impl_->ExternalRenderTargets(); } int Controller::StartIndex() const { return (show_desktop_disabled() ? 0 : 1); } Selection Controller::GetCurrentSelection() const { return impl_->GetCurrentSelection(); } void Controller::SelectFirstItem() { impl_->SelectFirstItem(); } sigc::connection Controller::ConnectToViewBuilt(const sigc::slot &f) { return impl_->view_built.connect(f); } double Controller::Opacity() const { if (!impl_->view_window_) return 0.0f; return impl_->view_window_->GetOpacity(); } std::string Controller::GetName() const { return "SwitcherController"; } void Controller::AddProperties(debug::IntrospectionData& introspection) { introspection .add("detail_on_timeout", detail_on_timeout()) .add("initial_detail_timeout_length", initial_detail_timeout_length()) .add("detail_timeout_length", detail_timeout_length()) .add("visible", visible_) .add("monitor", monitor_) .add("show_desktop_disabled", show_desktop_disabled()) .add("mouse_disabled", mouse_disabled()) .add("detail_mode", static_cast(detail_mode_)) .add("first_selection_mode", static_cast(first_selection_mode())); } Controller::Impl::Impl(Controller* obj, unsigned int load_timeout, Controller::WindowCreator const& create_window) : construct_timeout_(load_timeout) , obj_(obj) , create_window_(create_window) , icon_renderer_(std::make_shared()) , main_layout_(nullptr) , fade_animator_(Settings::Instance().low_gfx() ? 0 : FADE_DURATION) { WindowManager::Default().average_color.changed.connect(sigc::mem_fun(this, &Impl::OnBackgroundUpdate)); if (create_window_ == nullptr) create_window_ = []() { return nux::ObjectPtr(new MockableBaseWindow("Switcher")); }; // TODO We need to get actual timing data to suggest this is necessary. //sources_.AddTimeoutSeconds(construct_timeout_, [this] { ConstructWindow(); return false; }, LAZY_TIMEOUT); fade_animator_.updated.connect([this] (double opacity) { if (view_window_) { view_window_->SetOpacity(opacity); if (!obj_->visible_ && opacity == 0.0f) HideWindow(); } }); Settings::Instance().low_gfx.changed.connect(sigc::track_obj([this] (bool low_gfx) { fade_animator_.SetDuration(low_gfx ? 0 : FADE_DURATION); }, *this)); } void Controller::Impl::OnBackgroundUpdate(nux::Color const& new_color) { if (view_) view_->background_color = new_color; } void Controller::Impl::AddIcon(AbstractLauncherIcon::Ptr const& icon) { if (!obj_->visible_ || !model_) return; model_->AddIcon(icon); } void Controller::Impl::RemoveIcon(AbstractLauncherIcon::Ptr const& icon) { if (!obj_->visible_ || !model_) return; model_->RemoveIcon(icon); } void Controller::Impl::Show(ShowMode show_mode, SortMode sort_mode, std::vector const& results) { if (results.empty() || obj_->visible_) return; model_ = std::make_shared(results, (sort_mode == SortMode::FOCUS_ORDER)); model_->only_apps_on_viewport = (show_mode == ShowMode::CURRENT_VIEWPORT); model_->selection_changed.connect(sigc::mem_fun(this, &Controller::Impl::OnModelSelectionChanged)); model_->detail_selection.changed.connect([this] (bool) { sources_.Remove(DETAIL_TIMEOUT); }); model_->updated.connect([this] { if (!model_->Size()) Hide(false); }); if (!model_->Size()) { model_.reset(); return; } SelectFirstItem(); obj_->AddChild(model_.get()); obj_->visible_ = true; int real_wait = obj_->timeout_length() - fade_animator_.Duration(); if (real_wait > 0) { sources_.AddIdle([this] { ConstructView(); return false; }, VIEW_CONSTRUCT_IDLE); sources_.AddTimeout(real_wait, [this] { ShowView(); return false; }, SHOW_TIMEOUT); } else { ShowView(); } nux::GetWindowCompositor().SetKeyFocusArea(view_.GetPointer()); ResetDetailTimer(obj_->initial_detail_timeout_length); ubus_manager_.SendMessage(UBUS_OVERLAY_CLOSE_REQUEST); ubus_manager_.SendMessage(UBUS_SWITCHER_SHOWN, g_variant_new("(bi)", true, obj_->monitor_)); } void Controller::Impl::ResetDetailTimer(int timeout_length) { if (obj_->detail_on_timeout) { auto cb_func = sigc::mem_fun(this, &Controller::Impl::OnDetailTimer); sources_.AddTimeout(timeout_length, cb_func, DETAIL_TIMEOUT); } } bool Controller::Impl::OnDetailTimer() { if (obj_->Visible() && !model_->detail_selection) { SetDetail(true, 2); obj_->detail_mode_ = DetailMode::TAB_NEXT_WINDOW; } return false; } void Controller::Impl::OnModelSelectionChanged(AbstractLauncherIcon::Ptr const& icon) { ResetDetailTimer(obj_->detail_timeout_length); if (icon) { if (!obj_->Visible()) { ubus_manager_.SendMessage(UBUS_SWITCHER_SHOWN, g_variant_new("(bi)", true, obj_->monitor_)); } ubus_manager_.SendMessage(UBUS_SWITCHER_SELECTION_CHANGED, glib::Variant(icon->tooltip_text())); } } void Controller::Impl::ShowView() { if (!obj_->Visible()) return; ConstructView(); ubus_manager_.SendMessage(UBUS_SWITCHER_START, NULL); if (view_window_) { view_->live_background = true; view_window_->ShowWindow(true); view_window_->PushToFront(); animation::StartOrReverse(fade_animator_, animation::Direction::FORWARD); } } void Controller::Impl::ConstructWindow() { // sources_.Remove(LAZY_TIMEOUT); if (!view_window_) { main_layout_ = new nux::HLayout(NUX_TRACKER_LOCATION); main_layout_->SetVerticalExternalMargin(0); main_layout_->SetHorizontalExternalMargin(0); view_window_ = create_window_(); view_window_->SetOpacity(0.0f); view_window_->SetLayout(main_layout_); view_window_->SetBackgroundColor(nux::color::Transparent); } } nux::Geometry GetSwitcherViewsGeometry() { auto uscreen = UScreen::GetDefault(); int monitor = uscreen->GetMonitorWithMouse(); auto monitor_geo = uscreen->GetMonitorGeometry(monitor); auto em = Settings::Instance().em(monitor); monitor_geo.Expand(-XY_OFFSET.CP(em), -XY_OFFSET.CP(em)); return monitor_geo; } void Controller::Impl::ConstructView() { if (view_ || !model_) return; sources_.Remove(VIEW_CONSTRUCT_IDLE); view_ = SwitcherView::Ptr(new SwitcherView(icon_renderer_)); view_->SetModel(model_); view_->background_color = WindowManager::Default().average_color(); view_->monitor = obj_->monitor_; view_->hide_request.connect(sigc::mem_fun(this, &Controller::Impl::Hide)); view_->switcher_mouse_move.connect([this] (int icon_index) { if (icon_index >= 0) ResetDetailTimer(obj_->detail_timeout_length); }); view_->switcher_next.connect(sigc::mem_fun(this, &Impl::Next)); view_->switcher_prev.connect(sigc::mem_fun(this, &Impl::Prev)); view_->switcher_start_detail.connect(sigc::mem_fun(this, &Impl::StartDetailMode)); view_->switcher_stop_detail.connect(sigc::mem_fun(this, &Impl::StopDetailMode)); view_->switcher_close_current.connect(sigc::mem_fun(this, &Impl::CloseSelection)); obj_->AddChild(view_.GetPointer()); ConstructWindow(); main_layout_->AddView(view_.GetPointer(), 1); view_window_->SetEnterFocusInputArea(view_.GetPointer()); view_window_->SetGeometry(GetSwitcherViewsGeometry()); view_built.emit(); } void Controller::Impl::Hide(bool accept_state) { if (accept_state) { Selection selection = GetCurrentSelection(); if (selection.application_) { Time timestamp = 0; selection.application_->Activate(ActionArg(ActionArg::Source::SWITCHER, 0, timestamp, selection.window_)); } } ubus_manager_.SendMessage(UBUS_SWITCHER_END, glib::Variant(!accept_state)); ubus_manager_.SendMessage(UBUS_SWITCHER_SHOWN, g_variant_new("(bi)", false, obj_->monitor_)); sources_.Remove(VIEW_CONSTRUCT_IDLE); sources_.Remove(SHOW_TIMEOUT); sources_.Remove(DETAIL_TIMEOUT); obj_->visible_ = false; animation::StartOrReverse(fade_animator_, animation::Direction::BACKWARD); } void Controller::Impl::HideWindow() { if (model_->detail_selection()) obj_->detail.changed.emit(false); main_layout_->RemoveChildObject(view_.GetPointer()); view_window_->SetOpacity(0.0f); view_window_->ShowWindow(false); view_window_->PushToBack(); obj_->RemoveChild(model_.get()); obj_->RemoveChild(view_.GetPointer()); model_.reset(); view_.Release(); } void Controller::Impl::Next() { if (!model_) return; if (model_->detail_selection) { switch (obj_->detail_mode_) { case DetailMode::TAB_NEXT_WINDOW: if (model_->detail_selection_index < model_->DetailXids().size() - 1) model_->NextDetail(); else model_->Next(); break; case DetailMode::TAB_NEXT_TILE: model_->Next(); break; case DetailMode::TAB_NEXT_WINDOW_LOOP: model_->NextDetail(); //looping automatic break; } } else { model_->Next(); } } void Controller::Impl::Prev() { if (!model_) return; if (model_->detail_selection) { switch (obj_->detail_mode_) { case DetailMode::TAB_NEXT_WINDOW: if (model_->detail_selection_index > (unsigned int) 0) model_->PrevDetail(); else model_->Prev(); break; case DetailMode::TAB_NEXT_TILE: model_->Prev(); break; case DetailMode::TAB_NEXT_WINDOW_LOOP: model_->PrevDetail(); //looping automatic break; } } else { model_->Prev(); } } SwitcherView::Ptr Controller::Impl::GetView() const { return view_; } void Controller::Impl::SetDetail(bool value, unsigned int min_windows) { if (value && model_->Selection()->AllowDetailViewInSwitcher() && model_->SelectionWindows().size() >= min_windows) { model_->detail_selection = true; obj_->detail_mode_ = DetailMode::TAB_NEXT_WINDOW; obj_->detail.changed.emit(true); } else { obj_->detail.changed.emit(false); model_->detail_selection = false; } } void Controller::Impl::InitiateDetail(bool animate) { if (!model_) return; if (!model_->detail_selection) { SetDetail(true); if (!animate) view_->SkipAnimation(); } } void Controller::Impl::NextDetail() { if (!model_) return; InitiateDetail(true); model_->NextDetail(); } void Controller::Impl::PrevDetail() { if (!model_) return; InitiateDetail(true); model_->PrevDetail(); } void Controller::Impl::NextDetailRow() { if (!model_) return; model_->NextDetailRow(); } void Controller::Impl::PrevDetailRow() { if (!model_) return; model_->PrevDetailRow(); } bool Controller::Impl::HasNextDetailRow() const { if (!model_) return false; return model_->HasNextDetailRow(); } bool Controller::Impl::HasPrevDetailRow() const { if (!model_) return false; return model_->HasPrevDetailRow(); } LayoutWindow::Vector const& Controller::Impl::ExternalRenderTargets() const { if (!view_) { static LayoutWindow::Vector empty_list; return empty_list; } return view_->ExternalTargets(); } Selection Controller::Impl::GetCurrentSelection() const { AbstractLauncherIcon::Ptr application; Window window = 0; if (model_) { application = model_->Selection(); if (application) { if (model_->detail_selection) { window = model_->DetailSelectionWindow(); } else if (model_->SelectionIsActive()) { auto const& selection_windows = model_->SelectionWindows(); if (!selection_windows.empty()) window = selection_windows.front(); } } } return {application, window}; } void Controller::Impl::SelectFirstItem() { if (!model_) return; int first_icon_index = obj_->StartIndex(); int second_icon_index = first_icon_index + 1; AbstractLauncherIcon::Ptr const& first = model_->at(first_icon_index); AbstractLauncherIcon::Ptr const& second = model_->at(second_icon_index); if (!first) { model_->Select(0); return; } else if (!second) { model_->Select(1); return; } if (obj_->first_selection_mode == FirstSelectionMode::LAST_ACTIVE_APP) { model_->Select(second); return; } uint64_t first_highest = 0; uint64_t first_second = 0; // first icons second highest active uint64_t second_first = 0; // second icons first highest active WindowManager& wm = WindowManager::Default(); auto const& windows = (model_->only_apps_on_viewport) ? first->WindowsOnViewport() : first->Windows(); for (auto& window : windows) { uint64_t num = wm.GetWindowActiveNumber(window->window_id()); if (num > first_highest) { first_second = first_highest; first_highest = num; } else if (num > first_second) { first_second = num; } } second_first = second->SwitcherPriority(); if (first_second > second_first) model_->Select(first); else model_->Select(second); } } // switcher namespace } // unity namespace