// -*- 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 . * * Authored by: Jason Smith */ #include #include #include #include #include "LauncherIcon.h" #include "unity-shared/AnimationUtils.h" #include "unity-shared/CairoTexture.h" #include "unity-shared/ThemeSettings.h" #include "unity-shared/UnitySettings.h" #include "unity-shared/UScreen.h" #include "QuicklistManager.h" #include "QuicklistMenuItem.h" #include "QuicklistMenuItemLabel.h" #include "QuicklistMenuItemSeparator.h" #include "QuicklistMenuItemCheckmark.h" #include "QuicklistMenuItemRadio.h" #include "MultiMonitor.h" #include #include #include namespace unity { namespace launcher { DECLARE_LOGGER(logger, "unity.launcher.icon"); namespace { const int IGNORE_REPEAT_SHORTCUT_DURATION = 250; const std::string DEFAULT_ICON = "application-default-icon"; const std::string CENTER_STABILIZE_TIMEOUT = "center-stabilize-timeout"; const std::string PRESENT_TIMEOUT = "present-timeout"; const std::string QUIRK_DELAY_TIMEOUT = "quirk-delay-timeout"; const int COUNT_FONT_SIZE = 11; const int COUNT_PADDING = 2; } NUX_IMPLEMENT_OBJECT_TYPE(LauncherIcon); LauncherIcon::LauncherIcon(IconType type) : _icon_type(type) , _sticky(false) , _present_urgency(0) , _progress(0.0f) , _sort_priority(DefaultPriority(type)) , _order(0) , _last_monitor(0) , _background_color(nux::color::White) , _glow_color(nux::color::White) , _shortcut(0) , _allow_quicklist_to_show(true) , _center(monitors::MAX) , _number_of_visible_windows(monitors::MAX) , _quirks(monitors::MAX) , _quirk_animations(monitors::MAX, decltype(_quirk_animations)::value_type(unsigned(Quirk::LAST))) , _last_stable(monitors::MAX) , _saved_center(monitors::MAX) { tooltip_enabled = true; tooltip_enabled.changed.connect(sigc::mem_fun(this, &LauncherIcon::OnTooltipEnabledChanged)); position = Position::FLOATING; removed = false; // FIXME: the abstraction is already broken, should be fixed for O // right now, hooking the dynamic quicklist the less ugly possible way mouse_enter.connect(sigc::mem_fun(this, &LauncherIcon::RecvMouseEnter)); mouse_leave.connect(sigc::mem_fun(this, &LauncherIcon::RecvMouseLeave)); mouse_down.connect(sigc::mem_fun(this, &LauncherIcon::RecvMouseDown)); mouse_up.connect(sigc::mem_fun(this, &LauncherIcon::RecvMouseUp)); mouse_click.connect(sigc::mem_fun(this, &LauncherIcon::RecvMouseClick)); auto const& count_rebuild_cb = sigc::mem_fun(this, &LauncherIcon::CleanCountTextures); Settings::Instance().dpi_changed.connect(count_rebuild_cb); Settings::Instance().font_scaling.changed.connect(sigc::hide(count_rebuild_cb)); icon_size.changed.connect(sigc::hide(count_rebuild_cb)); for (unsigned i = 0; i < monitors::MAX; ++i) { for (unsigned j = 0; j < static_cast(Quirk::LAST); ++j) { _quirk_animations[i][j] = std::make_shared(); _quirk_animations[i][j]->updated.connect([this, i, j] (float value) { EmitNeedsRedraw(i); }); } // Center must have set a default value animation::SetValue(GetQuirkAnimation(Quirk::CENTER_SAVED, i), animation::Direction::FORWARD); } } void LauncherIcon::LoadTooltip() { int monitor = _last_monitor; if (monitor < 0) monitor = 0; _tooltip = new Tooltip(monitor); _tooltip->SetOpacity(0.0f); _tooltip->text = tooltip_text(); _tooltip->hidden.connect([this] { _tooltip.Release(); }); debug::Introspectable::AddChild(_tooltip.GetPointer()); } void LauncherIcon::LoadQuicklist() { int monitor = _last_monitor; if (monitor < 0) monitor = 0; _quicklist = new QuicklistView(monitor); _quicklist->hidden.connect([this] { _quicklist.Release(); }); debug::Introspectable::AddChild(_quicklist.GetPointer()); _quicklist->mouse_down_outside_pointer_grab_area.connect([this] (int x, int y, unsigned long button_flags, unsigned long key_flags) { _allow_quicklist_to_show = false; }); QuicklistManager::Default()->RegisterQuicklist(_quicklist); } bool LauncherIcon::WindowVisibleOnMonitor(int monitor) const { return _has_visible_window[monitor]; } bool LauncherIcon::WindowVisibleOnViewport() const { return _has_visible_window.any(); } size_t LauncherIcon::WindowsVisibleOnMonitor(int monitor) const { return _number_of_visible_windows[monitor]; } size_t LauncherIcon::WindowsVisibleOnViewport() const { return std::accumulate(begin(_number_of_visible_windows), end(_number_of_visible_windows), 0); } std::string LauncherIcon::GetName() const { return "LauncherIcon"; } void LauncherIcon::AddProperties(debug::IntrospectionData& introspection) { std::vector monitors_active, monitors_visible, monitors_urgent, monitors_running, monitors_starting, monitors_desaturated, monitors_presented; for (unsigned i = 0; i < monitors::MAX; ++i) { monitors_active.push_back(GetQuirk(Quirk::ACTIVE, i)); monitors_visible.push_back(IsVisibleOnMonitor(i)); monitors_urgent.push_back(GetQuirk(Quirk::URGENT, i)); monitors_running.push_back(GetQuirk(Quirk::RUNNING, i)); monitors_starting.push_back(GetQuirk(Quirk::STARTING, i)); monitors_desaturated.push_back(GetQuirk(Quirk::DESAT, i)); monitors_presented.push_back(GetQuirk(Quirk::PRESENTED, i)); } introspection .add("center", _center[unity::UScreen::GetDefault()->GetMonitorWithMouse()]) .add("related_windows", Windows().size()) .add("icon_type", unsigned(_icon_type)) .add("tooltip_text", tooltip_text()) .add("sort_priority", _sort_priority) .add("shortcut", _shortcut) .add("order", _order) .add("monitors_active", glib::Variant::FromVector(monitors_active)) .add("monitors_visibility", glib::Variant::FromVector(monitors_visible)) .add("monitors_urgent", glib::Variant::FromVector(monitors_urgent)) .add("monitors_running", glib::Variant::FromVector(monitors_running)) .add("monitors_starting", glib::Variant::FromVector(monitors_starting)) .add("monitors_desaturated", glib::Variant::FromVector(monitors_desaturated)) .add("monitors_presented", glib::Variant::FromVector(monitors_presented)) .add("active", GetQuirk(Quirk::ACTIVE)) .add("visible", GetQuirk(Quirk::VISIBLE)) .add("urgent", GetQuirk(Quirk::URGENT)) .add("running", GetQuirk(Quirk::RUNNING)) .add("starting", GetQuirk(Quirk::STARTING)) .add("desaturated", GetQuirk(Quirk::DESAT)) .add("presented", GetQuirk(Quirk::PRESENTED)); } bool LauncherIcon::IsActionArgValid(ActionArg const& arg) { if (arg.source != ActionArg::Source::LAUNCHER_KEYBINDING) return true; time::Spec now; now.SetToNow(); return (now.TimeDelta(_last_action) > IGNORE_REPEAT_SHORTCUT_DURATION); } void LauncherIcon::Activate(ActionArg arg) { if (!IsActionArgValid(arg)) return; /* Launcher Icons that handle spread will adjust the spread state * accordingly, for all other icons we should terminate spread */ WindowManager& wm = WindowManager::Default(); if (wm.IsScaleActive() && !HandlesSpread()) wm.TerminateScale(); ActivateLauncherIcon(arg); _last_action.SetToNow(); } void LauncherIcon::OpenInstance(ActionArg arg) { if (!IsActionArgValid(arg)) return; WindowManager& wm = WindowManager::Default(); if (wm.IsScaleActive()) wm.TerminateScale(); OpenInstanceLauncherIcon(arg.timestamp); _last_action.SetToNow(); } nux::Color LauncherIcon::BackgroundColor() const { return _background_color; } nux::Color LauncherIcon::GlowColor() { return _glow_color; } nux::BaseTexture* LauncherIcon::TextureForSize(int size) { nux::BaseTexture* result = GetTextureForSize(size); return result; } void LauncherIcon::ColorForIcon(GdkPixbuf* pixbuf, nux::Color& background, nux::Color& glow) { unsigned int width = gdk_pixbuf_get_width(pixbuf); unsigned int height = gdk_pixbuf_get_height(pixbuf); unsigned int row_bytes = gdk_pixbuf_get_rowstride(pixbuf); long int rtotal = 0, gtotal = 0, btotal = 0; float total = 0.0f; guchar* img = gdk_pixbuf_get_pixels(pixbuf); for (unsigned int i = 0; i < width; i++) { for (unsigned int j = 0; j < height; j++) { guchar* pixels = img + (j * row_bytes + i * 4); guchar r = *(pixels + 0); guchar g = *(pixels + 1); guchar b = *(pixels + 2); guchar a = *(pixels + 3); float saturation = (MAX(r, MAX(g, b)) - MIN(r, MIN(g, b))) / 255.0f; float relevance = .1 + .9 * (a / 255.0f) * saturation; rtotal += (guchar)(r * relevance); gtotal += (guchar)(g * relevance); btotal += (guchar)(b * relevance); total += relevance * 255; } } nux::color::RedGreenBlue rgb(rtotal / total, gtotal / total, btotal / total); nux::color::HueSaturationValue hsv(rgb); if (hsv.saturation > 0.15f) hsv.saturation = 0.65f; hsv.value = 0.90f; background = nux::Color(nux::color::RedGreenBlue(hsv)); hsv.value = 1.0f; glow = nux::Color(nux::color::RedGreenBlue(hsv)); } BaseTexturePtr LauncherIcon::TextureFromPixbuf(GdkPixbuf* pixbuf, int size, bool update_glow_colors) { g_return_val_if_fail(GDK_IS_PIXBUF(pixbuf), BaseTexturePtr()); glib::Object scaled_pixbuf(gdk_pixbuf_scale_simple(pixbuf, size, size, GDK_INTERP_BILINEAR)); if (update_glow_colors) ColorForIcon(scaled_pixbuf, _background_color, _glow_color); BaseTexturePtr result; result.Adopt(nux::CreateTexture2DFromPixbuf(scaled_pixbuf, true)); return result; } BaseTexturePtr LauncherIcon::TextureFromGtkTheme(std::string icon_name, int size, bool update_glow_colors) { GtkIconTheme* default_theme; BaseTexturePtr result; if (icon_name.empty()) icon_name = DEFAULT_ICON; default_theme = gtk_icon_theme_get_default(); result = TextureFromSpecificGtkTheme(default_theme, icon_name, size, update_glow_colors); if (!result) result = TextureFromSpecificGtkTheme(theme::Settings::Get()->UnityIconTheme(), icon_name, size, update_glow_colors); if (!result) result = TextureFromSpecificGtkTheme(default_theme, icon_name, size, update_glow_colors, true); if (!result) { if (icon_name != "folder") result = TextureFromSpecificGtkTheme(default_theme, "folder", size, update_glow_colors); } return result; } BaseTexturePtr LauncherIcon::TextureFromSpecificGtkTheme(GtkIconTheme* theme, std::string const& icon_name, int size, bool update_glow_colors, bool is_default_theme) { glib::Object icon(g_icon_new_for_string(icon_name.c_str(), nullptr)); glib::Object info; auto flags = GTK_ICON_LOOKUP_FORCE_SIZE; if (icon.IsType(G_TYPE_ICON)) { info = gtk_icon_theme_lookup_by_gicon(theme, icon, size, flags); } else { info = gtk_icon_theme_lookup_icon(theme, icon_name.c_str(), size, flags); } if (!info && !is_default_theme) return BaseTexturePtr(); if (!info) { info = gtk_icon_theme_lookup_icon(theme, DEFAULT_ICON.c_str(), size, flags); } if (!gtk_icon_info_get_filename(info)) { info = gtk_icon_theme_lookup_icon(theme, DEFAULT_ICON.c_str(), size, flags); } glib::Error error; glib::Object pbuf(gtk_icon_info_load_icon(info, &error)); if (pbuf.IsType(GDK_TYPE_PIXBUF)) { if (update_glow_colors) ColorForIcon(pbuf, _background_color, _glow_color); BaseTexturePtr result; result.Adopt(nux::CreateTexture2DFromPixbuf(pbuf, true)); return result; } else { LOG_WARN(logger) << "Unable to load '" << icon_name << "' from icon theme: " << error; } return BaseTexturePtr(); } BaseTexturePtr LauncherIcon::TextureFromPath(std::string const& icon_name, int size, bool update_glow_colors) { if (icon_name.empty()) return TextureFromGtkTheme(DEFAULT_ICON, size, update_glow_colors); glib::Error error; glib::Object pbuf(gdk_pixbuf_new_from_file_at_size(icon_name.c_str(), size, size, &error)); if (GDK_IS_PIXBUF(pbuf.RawPtr())) { if (update_glow_colors) ColorForIcon(pbuf, _background_color, _glow_color); BaseTexturePtr result; result.Adopt(nux::CreateTexture2DFromPixbuf(pbuf, true)); return result; } else { LOG_WARN(logger) << "Unable to load '" << icon_name << "' icon: " << error; return TextureFromGtkTheme(DEFAULT_ICON, size, update_glow_colors); } return BaseTexturePtr(); } void LauncherIcon::OnTooltipEnabledChanged(bool value) { if (!value) HideTooltip(); } void LauncherIcon::SetShortcut(guint64 shortcut) { // only relocate a digit with a digit (don't overwrite other shortcuts) if ((!_shortcut || (g_ascii_isdigit((gchar)_shortcut))) || !(g_ascii_isdigit((gchar) shortcut))) _shortcut = shortcut; } guint64 LauncherIcon::GetShortcut() { return _shortcut; } nux::Point LauncherIcon::GetTipPosition(int monitor) const { auto const& converter = Settings::Instance().em(monitor); if (Settings::Instance().launcher_position() == LauncherPosition::LEFT) { return nux::Point(_center[monitor].x + converter->CP(icon_size()) / 2 + 1, _center[monitor].y); } else { return nux::Point(_center[monitor].x, _center[monitor].y - converter->CP(icon_size()) / 2 - 1); } } void LauncherIcon::ShowTooltip() { if (!tooltip_enabled || tooltip_text().empty() || (_quicklist && _quicklist->IsVisible())) return; if (!_tooltip) LoadTooltip(); auto const& pos = GetTipPosition(_last_monitor); _tooltip->text = tooltip_text(); _tooltip->ShowTooltipWithTipAt(pos.x, pos.y); tooltip_visible.emit(_tooltip); } void LauncherIcon::RecvMouseEnter(int monitor) { _last_monitor = monitor; // FIXME We need to look at why we need to set the last_monitor to -1 when it leaves. // As it would be nice to not have to re-create the tooltip everytime now :( LoadTooltip(); } void LauncherIcon::RecvMouseLeave(int monitor) { _last_monitor = -1; _allow_quicklist_to_show = true; } bool LauncherIcon::OpenQuicklist(bool select_first_item, int monitor, bool restore_input_focus) { MenuItemsVector const& menus = Menus(); if (menus.empty()) return false; LoadQuicklist(); if (_tooltip) { // Hide the tooltip without fade animation _tooltip->ShowWindow(false); } for (auto const& menu_item : menus) { QuicklistMenuItem* ql_item = nullptr; const gchar* type = dbusmenu_menuitem_property_get(menu_item, DBUSMENU_MENUITEM_PROP_TYPE); const gchar* toggle_type = dbusmenu_menuitem_property_get(menu_item, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE); gboolean prop_visible = dbusmenu_menuitem_property_get_bool(menu_item, DBUSMENU_MENUITEM_PROP_VISIBLE); // Skip this item, it is invisible right now. if (!prop_visible) continue; if (g_strcmp0(type, DBUSMENU_CLIENT_TYPES_SEPARATOR) == 0) { ql_item = new QuicklistMenuItemSeparator(menu_item, NUX_TRACKER_LOCATION); } else if (g_strcmp0(toggle_type, DBUSMENU_MENUITEM_TOGGLE_CHECK) == 0) { ql_item = new QuicklistMenuItemCheckmark(menu_item, NUX_TRACKER_LOCATION); } else if (g_strcmp0(toggle_type, DBUSMENU_MENUITEM_TOGGLE_RADIO) == 0) { ql_item = new QuicklistMenuItemRadio(menu_item, NUX_TRACKER_LOCATION); } else //(g_strcmp0 (type, DBUSMENU_MENUITEM_PROP_LABEL) == 0) { ql_item = new QuicklistMenuItemLabel(menu_item, NUX_TRACKER_LOCATION); } _quicklist->AddMenuItem(ql_item); } if (select_first_item) _quicklist->SelectFirstItem(); if (monitor < 0) { if (_last_monitor >= 0) monitor = _last_monitor; else monitor = 0; } WindowManager& win_manager = WindowManager::Default(); auto const& pos = GetTipPosition(monitor); /* If the expo plugin is active, we need to wait it to be terminated, before * showing the icon quicklist. */ if (win_manager.IsExpoActive()) { auto conn = std::make_shared(); *conn = win_manager.terminate_expo.connect([this, conn, pos, restore_input_focus] { QuicklistManager::Default()->ShowQuicklist(_quicklist, pos.x, pos.y, restore_input_focus); conn->disconnect(); }); } else if (win_manager.IsScaleActive()) { auto conn = std::make_shared(); *conn = win_manager.terminate_spread.connect([this, conn, pos, restore_input_focus] { QuicklistManager::Default()->ShowQuicklist(_quicklist, pos.x, pos.y, restore_input_focus); conn->disconnect(); }); win_manager.TerminateScale(); } else { QuicklistManager::Default()->ShowQuicklist(_quicklist, pos.x, pos.y, restore_input_focus); } return true; } void LauncherIcon::CloseQuicklist() { _quicklist->HideAndEndQuicklistNav(); } void LauncherIcon::RecvMouseDown(int button, int monitor, unsigned long key_flags) { if (button == 3) OpenQuicklist(false, monitor); } void LauncherIcon::RecvMouseUp(int button, int monitor, unsigned long key_flags) { if (button == 3) { if (_allow_quicklist_to_show) { OpenQuicklist(false, monitor); } if (_quicklist && _quicklist->IsVisible()) { _quicklist->CaptureMouseDownAnyWhereElse(true); } } _allow_quicklist_to_show = true; } void LauncherIcon::RecvMouseClick(int button, int monitor, unsigned long key_flags) { auto timestamp = nux::GetGraphicsDisplay()->GetCurrentEvent().x11_timestamp; ActionArg arg(ActionArg::Source::LAUNCHER, button, timestamp); arg.monitor = monitor; bool shift_pressed = nux::GetKeyModifierState(key_flags, nux::NUX_STATE_SHIFT); // Click without shift if (button == 1 && !shift_pressed) Activate(arg); // Middle click or click with shift else if ((button == 2) || (button == 1 && shift_pressed)) OpenInstance(arg); } void LauncherIcon::HideTooltip() { if (_tooltip) _tooltip->Hide(); tooltip_visible.emit(nux::ObjectPtr()); } void LauncherIcon::PromptHideTooltip() { if (_tooltip) _tooltip->PromptHide(); tooltip_visible.emit(nux::ObjectPtr()); } void LauncherIcon::SetCenter(nux::Point3 const& new_center, int monitor) { nux::Point3& center = _center[monitor]; if (center == new_center) return; center = new_center; if (monitor == _last_monitor) { if (_quicklist && _quicklist->IsVisible()) { auto const& pos = GetTipPosition(monitor); QuicklistManager::Default()->MoveQuicklist(_quicklist, pos.x, pos.y); } else if (_tooltip && _tooltip->IsVisible()) { auto const& pos = GetTipPosition(monitor); _tooltip->SetTooltipPosition(pos.x, pos.y); } } _source_manager.AddTimeout(500, [this] { if (!std::equal(_center.begin(), _center.end(), _last_stable.begin())) { if (!removed()) OnCenterStabilized(_center); _last_stable = _center; } return false; }, CENTER_STABILIZE_TIMEOUT + std::to_string(monitor)); } void LauncherIcon::ResetCenters(int monitor) { if (monitor < 0) { for (unsigned i = 0; i < monitors::MAX; ++i) _center[i].Set(0, 0, 0); } else { _center[monitor].Set(0, 0, 0); } } nux::Point3 LauncherIcon::GetCenter(int monitor) { return _center[monitor]; } nux::Point3 LauncherIcon::GetSavedCenter(int monitor) { return _saved_center[monitor]; } std::vector LauncherIcon::GetCenters() { return _center; } void LauncherIcon::SaveCenter() { _saved_center = _center; FullyAnimateQuirk(Quirk::CENTER_SAVED, 0); } std::pair LauncherIcon::GetCenterForMonitor(int monitor) const { monitor = CLAMP(monitor, 0, static_cast(_center.size() - 1)); if (_center[monitor].x && _center[monitor].y) return {monitor, _center[monitor]}; for (unsigned i = 0; i < _center.size(); ++i) { if (_center[i].x && _center[i].y) return {i, _center[i]}; } return {-1, nux::Point3()}; } void LauncherIcon::SetNumberOfWindowsVisibleOnMonitor(int number_of_windows, int monitor) { if (_number_of_visible_windows[monitor] == number_of_windows) return; _has_visible_window[monitor] = (number_of_windows > 0); _number_of_visible_windows[monitor] = number_of_windows; windows_changed.emit(monitor); EmitNeedsRedraw(monitor); } void LauncherIcon::SetVisibleOnMonitor(int monitor, bool visible) { SetQuirk(Quirk::VISIBLE, visible, monitor); } bool LauncherIcon::IsVisibleOnMonitor(int monitor) const { return GetQuirk(Quirk::VISIBLE, monitor); } float LauncherIcon::PresentUrgency() { return _present_urgency; } void LauncherIcon::Present(float present_urgency, int length, int monitor) { if (GetQuirk(Quirk::PRESENTED, monitor)) return; if (length >= 0) { _source_manager.AddTimeout(length, [this, monitor] { if (!GetQuirk(Quirk::PRESENTED, monitor)) return false; Unpresent(monitor); return false; }, PRESENT_TIMEOUT + std::to_string(monitor)); } _present_urgency = CLAMP(present_urgency, 0.0f, 1.0f); SetQuirk(Quirk::PRESENTED, true, monitor); SetQuirk(Quirk::UNFOLDED, true, monitor); } void LauncherIcon::Unpresent(int monitor) { if (!GetQuirk(Quirk::PRESENTED, monitor)) return; _source_manager.Remove(PRESENT_TIMEOUT + std::to_string(monitor)); SetQuirk(Quirk::PRESENTED, false, monitor); SetQuirk(Quirk::UNFOLDED, false, monitor); } void LauncherIcon::Remove() { if (_quicklist && _quicklist->IsVisible()) _quicklist->Hide(); if (_tooltip && _tooltip->IsVisible()) _tooltip->Hide(); SetQuirk(Quirk::VISIBLE, false); EmitRemove(); // Disconnect all the callbacks that may interact with the icon data _source_manager.RemoveAll(); sigc::trackable::notify_callbacks(); removed = true; } void LauncherIcon::SetSortPriority(int priority) { _sort_priority = priority; } int LauncherIcon::SortPriority() { return _sort_priority; } void LauncherIcon::SetOrder(int order) { _order = order; } LauncherIcon::IconType LauncherIcon::GetIconType() const { return _icon_type; } bool LauncherIcon::GetQuirk(LauncherIcon::Quirk quirk, int monitor) const { if (monitor < 0) { for (unsigned i = 0; i < monitors::MAX; ++i) { if (!_quirks[i][unsigned(quirk)]) return false; } return true; } return _quirks[monitor][unsigned(quirk)]; } void LauncherIcon::SetQuirk(LauncherIcon::Quirk quirk, bool value, int monitor) { bool changed = false; if (monitor < 0) { for (unsigned i = 0; i < monitors::MAX; ++i) { if (_quirks[i][unsigned(quirk)] != value) { _quirks[i][unsigned(quirk)] = value; animation::StartOrReverseIf(GetQuirkAnimation(quirk, i), value); changed = true; } } } else { if (_quirks[monitor][unsigned(quirk)] != value) { _quirks[monitor][unsigned(quirk)] = value; animation::StartOrReverseIf(GetQuirkAnimation(quirk, monitor), value); changed = true; } } if (!changed) return; // Present on urgent and visible as a general policy if (value && (quirk == Quirk::URGENT || quirk == Quirk::VISIBLE)) { Present(0.5f, 1500, monitor); } if (quirk == Quirk::VISIBLE) visibility_changed.emit(monitor); quirks_changed.emit(quirk, monitor); } void LauncherIcon::FullyAnimateQuirkDelayed(guint ms, LauncherIcon::Quirk quirk, int monitor) { _source_manager.AddTimeout(ms, [this, quirk, monitor] { FullyAnimateQuirk(quirk, monitor); return false; }, QUIRK_DELAY_TIMEOUT + std::to_string(unsigned(quirk)) + std::to_string(monitor)); } void LauncherIcon::FullyAnimateQuirk(LauncherIcon::Quirk quirk, int monitor) { if (monitor < 0) { for (unsigned i = 0; i < monitors::MAX; ++i) animation::Start(GetQuirkAnimation(quirk, i), animation::Direction::FORWARD); } else { animation::Start(GetQuirkAnimation(quirk, monitor), animation::Direction::FORWARD); } } void LauncherIcon::SkipQuirkAnimation(LauncherIcon::Quirk quirk, int monitor) { if (monitor < 0) { for (unsigned i = 0; i < monitors::MAX; ++i) { animation::Skip(GetQuirkAnimation(quirk, i)); } } else { animation::Skip(GetQuirkAnimation(quirk, monitor)); } } float LauncherIcon::GetQuirkProgress(Quirk quirk, int monitor) const { return GetQuirkAnimation(quirk, monitor).GetCurrentValue(); } void LauncherIcon::SetQuirkDuration(Quirk quirk, unsigned duration, int monitor) { if (monitor < 0) { for (unsigned i = 0; i < monitors::MAX; ++i) GetQuirkAnimation(quirk, i).SetDuration(duration); } else { GetQuirkAnimation(quirk, monitor).SetDuration(duration); } } void LauncherIcon::SetProgress(float progress) { if (progress == _progress) return; _progress = progress; EmitNeedsRedraw(); } float LauncherIcon::GetProgress() { return _progress; } AbstractLauncherIcon::MenuItemsVector LauncherIcon::Menus() { return GetMenus(); } AbstractLauncherIcon::MenuItemsVector LauncherIcon::GetMenus() { MenuItemsVector result; return result; } nux::BaseTexture* LauncherIcon::Emblem() const { return _emblem.GetPointer(); } nux::BaseTexture* LauncherIcon::CountTexture(double scale) { int count = Count(); if (!count) return nullptr; auto it = _counts.find(scale); if (it != _counts.end()) return it->second.GetPointer(); auto const& texture = DrawCountTexture(count, scale); _counts[scale] = texture; return texture.GetPointer(); } unsigned LauncherIcon::Count() const { if (!_remote_entries.empty()) { auto const& remote = _remote_entries.front(); if (remote->CountVisible()) return remote->Count(); } return 0; } void LauncherIcon::SetEmblem(LauncherIcon::BaseTexturePtr const& emblem) { _emblem = emblem; EmitNeedsRedraw(); } void LauncherIcon::SetEmblemIconName(std::string const& name) { BaseTexturePtr emblem; if (name.at(0) == '/') emblem = TextureFromPath(name, 22, false); else emblem = TextureFromGtkTheme(name, 22, false); SetEmblem(emblem); // Ownership isn't taken, but shared, so we need to unref here. emblem->UnReference(); } void LauncherIcon::CleanCountTextures() { _counts.clear(); EmitNeedsRedraw(); } BaseTexturePtr LauncherIcon::DrawCountTexture(unsigned count, double scale) { glib::Object pango_ctx(gdk_pango_context_get()); glib::Object layout(pango_layout_new(pango_ctx)); auto const& font = theme::Settings::Get()->font(); std::shared_ptr desc(pango_font_description_from_string(font.c_str()), pango_font_description_free); int font_size = pango_units_from_double(Settings::Instance().font_scaling() * COUNT_FONT_SIZE); pango_font_description_set_absolute_size(desc.get(), font_size); pango_layout_set_font_description(layout, desc.get()); pango_layout_set_width(layout, pango_units_from_double(icon_size() * 0.75)); pango_layout_set_height(layout, -1); pango_layout_set_wrap(layout, PANGO_WRAP_CHAR); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_MIDDLE); pango_layout_set_text(layout, std::to_string(count).c_str(), -1); PangoRectangle ink_rect; pango_layout_get_pixel_extents(layout, &ink_rect, nullptr); /* DRAW OUTLINE */ const float height = ink_rect.height + COUNT_PADDING * 4; const float inset = height / 2.0; const float radius = inset - 1.0f; const float width = ink_rect.width + inset + COUNT_PADDING * 2; nux::CairoGraphics cg(CAIRO_FORMAT_ARGB32, std::round(width * scale), std::round(height * scale)); cairo_surface_set_device_scale(cg.GetSurface(), scale, scale); cairo_t* cr = cg.GetInternalContext(); cairo_move_to(cr, inset, height - 1.0f); cairo_arc(cr, inset, inset, radius, 0.5 * M_PI, 1.5 * M_PI); cairo_arc(cr, width - inset, inset, radius, 1.5 * M_PI, 0.5 * M_PI); cairo_line_to(cr, inset, height - 1.0f); cairo_set_source_rgba(cr, 0.35f, 0.35f, 0.35f, 1.0f); cairo_fill_preserve(cr); cairo_set_source_rgba(cr, 1.0f, 1.0f, 1.0f, 1.0f); cairo_set_line_width(cr, 2.0f); cairo_stroke(cr); cairo_set_line_width(cr, 1.0f); /* DRAW TEXT */ cairo_move_to(cr, (width - ink_rect.width) / 2.0 - ink_rect.x, (height - ink_rect.height) / 2.0 - ink_rect.y); pango_cairo_show_layout(cr, layout); return texture_ptr_from_cairo_graphics(cg); } void LauncherIcon::DeleteEmblem() { SetEmblem(BaseTexturePtr()); } void LauncherIcon::InsertEntryRemote(LauncherEntryRemote::Ptr const& remote) { if (!remote || std::find(_remote_entries.begin(), _remote_entries.end(), remote) != _remote_entries.end()) return; _remote_entries.push_back(remote); AddChild(remote.get()); SelectEntryRemote(remote); } void LauncherIcon::SelectEntryRemote(LauncherEntryRemote::Ptr const& remote) { if (!remote) return; auto& cm = _remote_connections; cm.Clear(); cm.Add(remote->emblem_changed.connect(sigc::mem_fun(this, &LauncherIcon::OnRemoteEmblemChanged))); cm.Add(remote->count_changed.connect(sigc::mem_fun(this, &LauncherIcon::OnRemoteCountChanged))); cm.Add(remote->progress_changed.connect(sigc::mem_fun(this, &LauncherIcon::OnRemoteProgressChanged))); cm.Add(remote->quicklist_changed.connect(sigc::mem_fun(this, &LauncherIcon::OnRemoteQuicklistChanged))); cm.Add(remote->emblem_visible_changed.connect(sigc::mem_fun(this, &LauncherIcon::OnRemoteEmblemVisibleChanged))); cm.Add(remote->count_visible_changed.connect(sigc::mem_fun(this, &LauncherIcon::OnRemoteCountVisibleChanged))); cm.Add(remote->progress_visible_changed.connect(sigc::mem_fun(this, &LauncherIcon::OnRemoteProgressVisibleChanged))); cm.Add(remote->urgent_changed.connect(sigc::mem_fun(this, &LauncherIcon::OnRemoteUrgentChanged))); if (remote->EmblemVisible()) OnRemoteEmblemVisibleChanged(remote.get()); if (remote->CountVisible()) OnRemoteCountVisibleChanged(remote.get()); if (remote->ProgressVisible()) OnRemoteProgressVisibleChanged(remote.get()); if (remote->Urgent()) OnRemoteUrgentChanged(remote.get()); OnRemoteQuicklistChanged(remote.get()); } void LauncherIcon::RemoveEntryRemote(LauncherEntryRemote::Ptr const& remote) { auto remote_it = std::find(_remote_entries.begin(), _remote_entries.end(), remote); if (remote_it == _remote_entries.end()) return; SetQuirk(Quirk::PROGRESS, false); if (remote->Urgent()) SetQuirk(Quirk::URGENT, false); _remote_entries.erase(remote_it); RemoveChild(remote.get()); DeleteEmblem(); _remote_menus = nullptr; if (!_remote_entries.empty()) SelectEntryRemote(_remote_entries.back()); } void LauncherIcon::OnRemoteUrgentChanged(LauncherEntryRemote* remote) { SetQuirk(Quirk::URGENT, remote->Urgent()); } void LauncherIcon::OnRemoteEmblemChanged(LauncherEntryRemote* remote) { if (!remote->EmblemVisible()) return; SetEmblemIconName(remote->Emblem()); } void LauncherIcon::OnRemoteCountChanged(LauncherEntryRemote* remote) { if (!remote->CountVisible()) return; CleanCountTextures(); } void LauncherIcon::OnRemoteProgressChanged(LauncherEntryRemote* remote) { if (!remote->ProgressVisible()) return; SetProgress(remote->Progress()); } void LauncherIcon::OnRemoteQuicklistChanged(LauncherEntryRemote* remote) { _remote_menus = remote->Quicklist(); } void LauncherIcon::OnRemoteEmblemVisibleChanged(LauncherEntryRemote* remote) { if (remote->EmblemVisible()) SetEmblemIconName(remote->Emblem()); else DeleteEmblem(); } void LauncherIcon::OnRemoteCountVisibleChanged(LauncherEntryRemote* remote) { CleanCountTextures(); } void LauncherIcon::OnRemoteProgressVisibleChanged(LauncherEntryRemote* remote) { SetQuirk(Quirk::PROGRESS, remote->ProgressVisible()); if (remote->ProgressVisible()) SetProgress(remote->Progress()); } glib::Object LauncherIcon::GetRemoteMenus() const { if (!_remote_menus.IsType(DBUSMENU_TYPE_CLIENT)) return glib::Object(); glib::Object root(dbusmenu_client_get_root(_remote_menus), glib::AddRef()); if (!root.IsType(DBUSMENU_TYPE_MENUITEM) || !dbusmenu_menuitem_property_get_bool(root, DBUSMENU_MENUITEM_PROP_VISIBLE)) { return glib::Object(); } return root; } void LauncherIcon::EmitNeedsRedraw(int monitor) { if (OwnsTheReference() && GetReferenceCount() > 0) { if (monitor < 0) { needs_redraw.emit(AbstractLauncherIcon::Ptr(this), monitor); } else { auto const& visibilty = GetQuirkAnimation(Quirk::VISIBLE, monitor); if (visibilty.GetCurrentValue() > 0.0f || visibilty.CurrentState() == na::Animation::State::Running) needs_redraw.emit(AbstractLauncherIcon::Ptr(this), monitor); } } } void LauncherIcon::EmitRemove() { if (OwnsTheReference() && GetReferenceCount() > 0) remove.emit(AbstractLauncherIcon::Ptr(this)); } void LauncherIcon::Stick(bool save) { if (_sticky && !save) return; _sticky = true; if (save) position_saved.emit(); SetQuirk(Quirk::VISIBLE, true); } void LauncherIcon::UnStick() { if (!_sticky) return; _sticky = false; position_forgot.emit(); SetQuirk(Quirk::VISIBLE, false); } void LauncherIcon::PerformScroll(ScrollDirection direction, Time timestamp) {} } // namespace launcher } // namespace unity