// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "mash/quick_launch/quick_launch.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/string16.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "mash/public/interfaces/launchable.mojom.h" #include "services/catalog/public/interfaces/catalog.mojom.h" #include "services/catalog/public/interfaces/constants.mojom.h" #include "services/service_manager/public/c/main.h" #include "services/service_manager/public/cpp/connector.h" #include "services/service_manager/public/cpp/service.h" #include "services/service_manager/public/cpp/service_context.h" #include "services/service_manager/public/cpp/service_runner.h" #include "ui/aura/window.h" #include "ui/aura/window_tree_host.h" #include "ui/views/background.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/controls/textfield/textfield_controller.h" #include "ui/views/mus/aura_init.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" #include "url/gurl.h" namespace mash { namespace quick_launch { class QuickLaunchUI : public views::WidgetDelegateView, public views::TextfieldController { public: QuickLaunchUI(QuickLaunch* quick_launch, service_manager::Connector* connector, catalog::mojom::CatalogPtr catalog) : quick_launch_(quick_launch), connector_(connector), prompt_(new views::Textfield), catalog_(std::move(catalog)) { set_background(views::Background::CreateStandardPanelBackground()); prompt_->set_controller(this); AddChildView(prompt_); UpdateEntries(); } ~QuickLaunchUI() override { quick_launch_->RemoveWindow(GetWidget()); } private: // Overridden from views::WidgetDelegate: base::string16 GetWindowTitle() const override { // TODO(beng): use resources. return base::ASCIIToUTF16("QuickLaunch"); } // Overridden from views::View: void Layout() override { gfx::Rect bounds = GetLocalBounds(); bounds.Inset(5, 5); prompt_->SetBoundsRect(bounds); } gfx::Size CalculatePreferredSize() const override { gfx::Size ps = prompt_->GetPreferredSize(); ps.Enlarge(500, 10); return ps; } // Overridden from views::TextFieldController: bool HandleKeyEvent(views::Textfield* sender, const ui::KeyEvent& key_event) override { if (key_event.type() != ui::ET_KEY_PRESSED) return false; // The user didn't like our suggestion, don't make another until they // type another character. suggestion_rejected_ = key_event.key_code() == ui::VKEY_BACK || key_event.key_code() == ui::VKEY_DELETE; if (key_event.key_code() == ui::VKEY_RETURN) { Launch(Canonicalize(prompt_->text()), key_event.IsControlDown()); prompt_->SetText(base::string16()); UpdateEntries(); } return false; } void ContentsChanged(views::Textfield* sender, const base::string16& new_contents) override { // Don't keep making a suggestion if the user didn't like what we offered. if (suggestion_rejected_) return; if (new_contents.empty()) return; // TODO(beng): it'd be nice if we persisted some history/scoring here. for (const auto& name : app_names_) { if (base::StartsWith(name, new_contents, base::CompareCase::INSENSITIVE_ASCII)) { base::string16 suffix = name; base::ReplaceSubstringsAfterOffset(&suffix, 0, new_contents, base::string16()); gfx::Range range(static_cast(new_contents.size()), static_cast(name.size())); prompt_->SetText(name); prompt_->SelectRange(range); break; } } } std::string Canonicalize(const base::string16& input) const { base::string16 working; base::TrimWhitespace(input, base::TRIM_ALL, &working); GURL url(working); if (url.scheme() != "service" && url.scheme() != "exe") working = base::ASCIIToUTF16("") + working; return base::UTF16ToUTF8(working); } void UpdateEntries() { catalog_->GetEntriesProvidingCapability( "mash:launchable", base::Bind(&QuickLaunchUI::OnGotCatalogEntries, base::Unretained(this))); } void OnGotCatalogEntries(std::vector entries) { for (const auto& entry : entries) app_names_.insert(base::UTF8ToUTF16(entry->name)); } void Launch(const std::string& name, bool new_window) { ::mash::mojom::LaunchablePtr launchable; connector_->BindInterface(name, &launchable); launchable->Launch(mojom::kWindow, new_window ? mojom::LaunchMode::MAKE_NEW : mojom::LaunchMode::REUSE); } QuickLaunch* quick_launch_; service_manager::Connector* connector_; views::Textfield* prompt_; catalog::mojom::CatalogPtr catalog_; std::set app_names_; bool suggestion_rejected_ = false; DISALLOW_COPY_AND_ASSIGN(QuickLaunchUI); }; QuickLaunch::QuickLaunch() { registry_.AddInterface<::mash::mojom::Launchable>( base::Bind(&QuickLaunch::Create, base::Unretained(this))); } QuickLaunch::~QuickLaunch() { while (!windows_.empty()) windows_.front()->CloseNow(); } void QuickLaunch::RemoveWindow(views::Widget* window) { auto it = std::find(windows_.begin(), windows_.end(), window); DCHECK(it != windows_.end()); windows_.erase(it); if (windows_.empty() && base::RunLoop::IsRunningOnCurrentThread()) base::MessageLoop::current()->QuitWhenIdle(); } void QuickLaunch::OnStart() { aura_init_ = base::MakeUnique( context()->connector(), context()->identity(), "views_mus_resources.pak", std::string(), nullptr, views::AuraInit::Mode::AURA_MUS); Launch(mojom::kWindow, mojom::LaunchMode::MAKE_NEW); } void QuickLaunch::OnBindInterface( const service_manager::BindSourceInfo& source_info, const std::string& interface_name, mojo::ScopedMessagePipeHandle interface_pipe) { registry_.BindInterface(source_info, interface_name, std::move(interface_pipe)); } void QuickLaunch::Launch(uint32_t what, mojom::LaunchMode how) { bool reuse = how == mojom::LaunchMode::REUSE || how == mojom::LaunchMode::DEFAULT; if (reuse && !windows_.empty()) { windows_.back()->Activate(); return; } catalog::mojom::CatalogPtr catalog; context()->connector()->BindInterface(catalog::mojom::kServiceName, &catalog); views::Widget* window = views::Widget::CreateWindowWithContextAndBounds( new QuickLaunchUI(this, context()->connector(), std::move(catalog)), nullptr, gfx::Rect(10, 640, 0, 0)); window->GetNativeWindow()->GetHost()->window()->SetName("QuickLaunch"); window->Show(); windows_.push_back(window); } void QuickLaunch::Create(const service_manager::BindSourceInfo& source_info, ::mash::mojom::LaunchableRequest request) { bindings_.AddBinding(this, std::move(request)); } } // namespace quick_launch } // namespace mash