// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- /* * Copyright (C) 2014 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: Marco Trevisan */ #include "DecorationsMenuLayout.h" #include "DecorationsMenuEntry.h" #include "DecorationsMenuDropdown.h" namespace unity { namespace decoration { namespace { const std::string MENUS_PANEL_NAME = "WindowLIM"; } using namespace indicator; MenuLayout::MenuLayout(menu::Manager::Ptr const& menu, CompWindow* win) : active(false) , show_now(false) , menu_manager_(menu) , win_(win) , dropdown_(std::make_shared(menu_manager_->Indicators(), win)) , menubar_id_(MENUS_PANEL_NAME + std::to_string(win_->id())) { visible = false; } void MenuLayout::Setup() { // This function is needed, because we can't use shared_from_this() in the ctor items_.clear(); if (!menu_manager_->HasAppMenu()) { Relayout(); return; } auto ownership_cb = sigc::mem_fun(this, &MenuLayout::OnEntryMouseOwnershipChanged); auto active_cb = sigc::mem_fun(this, &MenuLayout::OnEntryActiveChanged); auto show_now_cb = sigc::mem_fun(this, &MenuLayout::OnEntryShowNowChanged); dropdown_->mouse_owner.changed.connect(ownership_cb); dropdown_->active.changed.connect(active_cb); dropdown_->show_now.changed.connect(show_now_cb); for (auto const& entry : menu_manager_->AppMenu()->GetEntriesForWindow(win_->id())) { auto menu = std::make_shared(entry, win_); menu->mouse_owner.changed.connect(ownership_cb); menu->active.changed.connect(active_cb); menu->show_now.changed.connect(show_now_cb); menu->focused = focused(); menu->scale = scale(); menu->SetParent(shared_from_this()); if (items_.empty() || entry->priority() < 0) { items_.push_back(menu); } else { auto pos = items_.rbegin(); for (; pos != items_.rend(); ++pos) { auto menu = std::static_pointer_cast(*pos); if (entry->priority() >= menu->GetEntry()->priority()) break; } items_.insert(pos.base(), menu); } } if (!items_.empty()) Relayout(); } std::string const& MenuLayout::MenubarId() const { return menubar_id_; } bool MenuLayout::ActivateMenu(std::string const& entry_id) { MenuEntry::Ptr target; bool activated = false; for (auto const& item : items_) { auto const& menu_entry = std::static_pointer_cast(item); if (menu_entry->Id() == entry_id) { target = menu_entry; if (item->visible() && item->sensitive()) { menu_entry->ShowMenu(0); activated = true; } break; } } if (!activated) activated = dropdown_->ActivateChild(target); return activated; } bool MenuLayout::ActivateMenu(CompPoint const& pos) { if (!Geometry().contains(pos)) return false; for (auto const& item : items_) { if (!item->visible() || !item->sensitive()) continue; if (item->Geometry().contains(pos)) { std::static_pointer_cast(item)->ShowMenu(1); return true; } } return false; } void MenuLayout::OnEntryMouseOwnershipChanged(bool owner) { mouse_owner = owner; } void MenuLayout::OnEntryShowNowChanged(bool show) { if (!show) { show_now_timeout_.reset(); show_now = false; } else { show_now_timeout_.reset(new glib::Timeout(menu_manager_->show_menus_wait())); show_now_timeout_->Run([this] { show_now = true; show_now_timeout_.reset(); return false; }); } } void MenuLayout::OnEntryActiveChanged(bool actived) { active = actived; if (active && items_.size() > 1) { menu_manager_->RegisterTracker(menubar_id_, (sigc::track_obj([this] (int x, int y, double speed) { ActivateMenu(CompPoint(x, y)); }, *this))); } else if (!active) { menu_manager_->UnregisterTracker(menubar_id_); } } void MenuLayout::ChildrenGeometries(EntryLocationMap& map) const { for (auto const& item : items_) { if (item->visible()) { auto const& entry = std::static_pointer_cast(item); auto const& geo = item->Geometry(); map.insert({entry->Id(), {geo.x(), geo.y(), geo.width(), geo.height()}}); } } } void MenuLayout::DoRelayout() { float scale = this->scale(); int inner_padding = this->inner_padding().CP(scale); int left_padding = this->left_padding().CP(scale); int right_padding = this->right_padding().CP(scale); int dropdown_width = dropdown_->GetNaturalWidth(); int accumolated_width = dropdown_width + left_padding + right_padding - inner_padding; int max_width = max_.width; std::list to_hide; bool is_visible = visible(); for (auto const& item : items_) { if (!item->visible() || item == dropdown_) continue; is_visible = true; accumolated_width += item->GetNaturalWidth() + inner_padding; if (accumolated_width > max_width) to_hide.push_front(std::static_pointer_cast(item)); } // No need to hide an item if there's space that we considered for the dropdown if (dropdown_->Empty() && to_hide.size() == 1) { if (accumolated_width - dropdown_width < max_width) to_hide.clear(); } // There's just one hidden entry, it might fit in the space we have if (to_hide.empty() && dropdown_->Size() == 1) accumolated_width -= dropdown_width; if (accumolated_width < max_width) { while (!dropdown_->Empty() && dropdown_->Top()->GetNaturalWidth() < (max_width - accumolated_width)) dropdown_->Pop(); if (dropdown_->Empty()) Remove(dropdown_); } else if (!to_hide.empty()) { if (dropdown_->Empty()) Append(dropdown_); for (auto const& hidden : to_hide) dropdown_->Push(hidden); } visible = is_visible; Layout::DoRelayout(); } } // decoration namespace } // unity namespace