/*
* 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