| // Copyright 2014 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 "base/bind.h" |
| #include "base/macros.h" |
| #include "examples/window_manager/debug_panel_host.mojom.h" |
| #include "examples/window_manager/window_manager.mojom.h" |
| #include "mojo/application/application_runner_chromium.h" |
| #include "mojo/common/binding_set.h" |
| #include "mojo/converters/geometry/geometry_type_converters.h" |
| #include "mojo/converters/input_events/input_events_type_converters.h" |
| #include "mojo/public/c/system/main.h" |
| #include "mojo/public/cpp/application/application_connection.h" |
| #include "mojo/public/cpp/application/application_delegate.h" |
| #include "mojo/public/cpp/application/application_impl.h" |
| #include "mojo/public/cpp/application/service_provider_impl.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "mojo/services/input_events/interfaces/input_events.mojom.h" |
| #include "mojo/services/navigation/interfaces/navigation.mojom.h" |
| #include "mojo/services/view_manager/cpp/view.h" |
| #include "mojo/services/view_manager/cpp/view_manager.h" |
| #include "mojo/services/view_manager/cpp/view_manager_delegate.h" |
| #include "mojo/services/view_manager/cpp/view_observer.h" |
| #include "services/window_manager/basic_focus_rules.h" |
| #include "services/window_manager/view_target.h" |
| #include "services/window_manager/window_manager_app.h" |
| #include "services/window_manager/window_manager_delegate.h" |
| #include "services/window_manager/window_manager_root.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_constants.h" |
| #include "url/gurl.h" |
| |
| #if defined CreateWindow |
| #undef CreateWindow |
| #endif |
| |
| namespace mojo { |
| namespace examples { |
| |
| class WindowManagerController; |
| |
| namespace { |
| |
| const int kBorderInset = 25; |
| const int kControlPanelWidth = 200; |
| const int kTextfieldHeight = 39; |
| |
| } // namespace |
| |
| class WindowManagerConnection : public ::examples::IWindowManager { |
| public: |
| WindowManagerConnection(WindowManagerController* window_manager, |
| InterfaceRequest<::examples::IWindowManager> request) |
| : window_manager_(window_manager), binding_(this, request.Pass()) {} |
| ~WindowManagerConnection() override {} |
| |
| private: |
| // Overridden from ::examples::IWindowManager: |
| void CloseWindow(Id view_id) override; |
| |
| WindowManagerController* window_manager_; |
| StrongBinding<::examples::IWindowManager> binding_; |
| |
| DISALLOW_COPY_AND_ASSIGN(WindowManagerConnection); |
| }; |
| |
| class NavigatorHostImpl : public NavigatorHost { |
| public: |
| NavigatorHostImpl(WindowManagerController* window_manager, Id view_id) |
| : window_manager_(window_manager), |
| view_id_(view_id), |
| current_index_(-1) {} |
| ~NavigatorHostImpl() override {} |
| |
| void Bind(InterfaceRequest<NavigatorHost> request) { |
| bindings_.AddBinding(this, request.Pass()); |
| } |
| |
| void RecordNavigation(const std::string& url); |
| |
| private: |
| void DidNavigateLocally(const mojo::String& url) override; |
| void RequestNavigate(Target target, URLRequestPtr request) override; |
| void RequestNavigateHistory(int32_t delta) override; |
| |
| WindowManagerController* window_manager_; |
| Id view_id_; |
| std::vector<std::string> history_; |
| int32_t current_index_; |
| |
| BindingSet<NavigatorHost> bindings_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NavigatorHostImpl); |
| }; |
| |
| class RootLayoutManager : public ViewObserver { |
| public: |
| RootLayoutManager(ViewManager* view_manager, |
| View* root, |
| Id content_view_id, |
| Id launcher_ui_view_id, |
| Id control_panel_view_id) |
| : root_(root), |
| view_manager_(view_manager), |
| content_view_id_(content_view_id), |
| launcher_ui_view_id_(launcher_ui_view_id), |
| control_panel_view_id_(control_panel_view_id) {} |
| ~RootLayoutManager() override { |
| if (root_) |
| root_->RemoveObserver(this); |
| } |
| |
| private: |
| // Overridden from ViewObserver: |
| void OnViewBoundsChanged(View* view, |
| const Rect& old_bounds, |
| const Rect& new_bounds) override { |
| DCHECK_EQ(view, root_); |
| |
| View* content_view = view_manager_->GetViewById(content_view_id_); |
| content_view->SetBounds(new_bounds); |
| |
| int delta_width = new_bounds.width - old_bounds.width; |
| int delta_height = new_bounds.height - old_bounds.height; |
| |
| View* launcher_ui_view = view_manager_->GetViewById(launcher_ui_view_id_); |
| Rect launcher_ui_bounds(launcher_ui_view->bounds()); |
| launcher_ui_bounds.width += delta_width; |
| launcher_ui_view->SetBounds(launcher_ui_bounds); |
| |
| View* control_panel_view = |
| view_manager_->GetViewById(control_panel_view_id_); |
| Rect control_panel_bounds(control_panel_view->bounds()); |
| control_panel_bounds.x += delta_width; |
| control_panel_view->SetBounds(control_panel_bounds); |
| |
| const View::Children& content_views = content_view->children(); |
| View::Children::const_iterator iter = content_views.begin(); |
| for (; iter != content_views.end(); ++iter) { |
| View* view = *iter; |
| if (view->id() == control_panel_view->id() || |
| view->id() == launcher_ui_view->id()) |
| continue; |
| Rect view_bounds(view->bounds()); |
| view_bounds.width += delta_width; |
| view_bounds.height += delta_height; |
| view->SetBounds(view_bounds); |
| } |
| } |
| void OnViewDestroyed(View* view) override { |
| DCHECK_EQ(view, root_); |
| root_->RemoveObserver(this); |
| root_ = NULL; |
| } |
| |
| View* root_; |
| ViewManager* view_manager_; |
| const Id content_view_id_; |
| const Id launcher_ui_view_id_; |
| const Id control_panel_view_id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RootLayoutManager); |
| }; |
| |
| class Window : public InterfaceFactory<NavigatorHost> { |
| public: |
| Window(WindowManagerController* window_manager, View* view) |
| : window_manager_(window_manager), |
| view_(view), |
| navigator_host_(window_manager_, view_->id()) { |
| exposed_services_impl_.AddService<NavigatorHost>(this); |
| } |
| |
| ~Window() override {} |
| |
| View* view() const { return view_; } |
| |
| NavigatorHost* navigator_host() { return &navigator_host_; } |
| |
| void Embed(const std::string& url) { |
| // TODO: Support embedding multiple times? |
| ServiceProviderPtr exposed_services; |
| exposed_services_impl_.Bind(GetProxy(&exposed_services)); |
| view_->Embed(url, nullptr, exposed_services.Pass()); |
| navigator_host_.RecordNavigation(url); |
| } |
| |
| private: |
| // InterfaceFactory<NavigatorHost> |
| void Create(ApplicationConnection* connection, |
| InterfaceRequest<NavigatorHost> request) override { |
| navigator_host_.Bind(request.Pass()); |
| } |
| |
| WindowManagerController* window_manager_; |
| View* view_; |
| ServiceProviderImpl exposed_services_impl_; |
| NavigatorHostImpl navigator_host_; |
| }; |
| |
| class WindowManagerController |
| : public examples::DebugPanelHost, |
| public window_manager::WindowManagerController, |
| public ui::EventHandler, |
| public ui::AcceleratorTarget, |
| public mojo::InterfaceFactory<examples::DebugPanelHost>, |
| public InterfaceFactory<::examples::IWindowManager> { |
| public: |
| WindowManagerController(Shell* shell, |
| ApplicationImpl* app, |
| ApplicationConnection* connection, |
| window_manager::WindowManagerRoot* wm_root) |
| : shell_(shell), |
| launcher_ui_(NULL), |
| view_manager_(NULL), |
| window_manager_root_(wm_root), |
| navigation_target_(Target::DEFAULT), |
| app_(app), |
| binding_(this) { |
| connection->AddService<::examples::IWindowManager>(this); |
| } |
| |
| ~WindowManagerController() override { |
| // host() may be destroyed by the time we get here. |
| // TODO: figure out a way to always cleanly remove handler. |
| |
| // TODO(erg): In the aura version, we removed ourselves from the |
| // PreTargetHandler list here. We may need to do something analogous when |
| // we get event handling without aura working. |
| } |
| |
| void CloseWindow(Id view_id) { |
| WindowVector::iterator iter = GetWindowByViewId(view_id); |
| DCHECK(iter != windows_.end()); |
| Window* window = *iter; |
| windows_.erase(iter); |
| window->view()->Destroy(); |
| } |
| |
| void DidNavigateLocally(uint32 source_view_id, const mojo::String& url) { |
| LOG(ERROR) << "DidNavigateLocally: source_view_id: " << source_view_id |
| << " url: " << url.To<std::string>(); |
| } |
| |
| void RequestNavigate(uint32 source_view_id, |
| Target target, |
| const mojo::String& url) { |
| OnLaunch(source_view_id, target, url); |
| } |
| |
| // Overridden from mojo::DebugPanelHost: |
| void CloseTopWindow() override { |
| if (!windows_.empty()) |
| CloseWindow(windows_.back()->view()->id()); |
| } |
| |
| void NavigateTo(const String& url) override { |
| OnLaunch(control_panel_id_, Target::NEW_NODE, url); |
| } |
| |
| void SetNavigationTarget(Target t) override { navigation_target_ = t; } |
| |
| // mojo::InterfaceFactory<examples::DebugPanelHost> implementation. |
| void Create( |
| mojo::ApplicationConnection* connection, |
| mojo::InterfaceRequest<examples::DebugPanelHost> request) override { |
| binding_.Bind(request.Pass()); |
| } |
| |
| // mojo::InterfaceFactory<::examples::IWindowManager> implementation. |
| void Create( |
| mojo::ApplicationConnection* connection, |
| mojo::InterfaceRequest<::examples::IWindowManager> request) override { |
| new WindowManagerConnection(this, request.Pass()); |
| } |
| |
| private: |
| typedef std::vector<Window*> WindowVector; |
| |
| // Overridden from ViewManagerDelegate: |
| void OnEmbed(View* root, |
| InterfaceRequest<ServiceProvider> services, |
| ServiceProviderPtr exposed_services) override { |
| DCHECK(!view_manager_); |
| view_manager_ = root->view_manager(); |
| |
| View* view = view_manager_->CreateView(); |
| root->AddChild(view); |
| Rect rect; |
| rect.width = root->bounds().width; |
| rect.height = root->bounds().height; |
| view->SetBounds(rect); |
| view->SetVisible(true); |
| content_view_id_ = view->id(); |
| |
| Id launcher_ui_id = CreateLauncherUI(); |
| control_panel_id_ = CreateControlPanel(view); |
| |
| root_layout_manager_.reset( |
| new RootLayoutManager(view_manager_, root, content_view_id_, |
| launcher_ui_id, control_panel_id_)); |
| root->AddObserver(root_layout_manager_.get()); |
| |
| // TODO(erg): In the aura version, we explicitly added ourselves as a |
| // PreTargetHandler to the window() here. We probably have to do something |
| // analogous here. |
| |
| window_manager_root_->InitFocus( |
| make_scoped_ptr(new window_manager::BasicFocusRules(root))); |
| window_manager_root_->accelerator_manager()->Register( |
| ui::Accelerator(ui::VKEY_BROWSER_BACK, 0), |
| ui::AcceleratorManager::kNormalPriority, this); |
| } |
| void OnViewManagerDisconnected(ViewManager* view_manager) override { |
| DCHECK_EQ(view_manager_, view_manager); |
| view_manager_ = NULL; |
| base::MessageLoop::current()->Quit(); |
| } |
| |
| // Overridden from WindowManagerDelegate: |
| void Embed(const String& url, |
| InterfaceRequest<ServiceProvider> services, |
| ServiceProviderPtr exposed_services) override { |
| const Id kInvalidSourceViewId = 0; |
| OnLaunch(kInvalidSourceViewId, Target::DEFAULT, url); |
| } |
| |
| // Overridden from ui::EventHandler: |
| void OnEvent(ui::Event* event) override { |
| View* view = |
| static_cast<window_manager::ViewTarget*>(event->target())->view(); |
| if (event->type() == ui::ET_MOUSE_PRESSED) |
| view->SetFocus(); |
| } |
| |
| // Overriden from ui::AcceleratorTarget: |
| bool AcceleratorPressed(const ui::Accelerator& accelerator, |
| mojo::View* view) override { |
| if (accelerator.key_code() != ui::VKEY_BROWSER_BACK) |
| return false; |
| |
| WindowVector::iterator iter = GetWindowByViewId(view->id()); |
| DCHECK(iter != windows_.end()); |
| Window* window = *iter; |
| window->navigator_host()->RequestNavigateHistory(-1); |
| return true; |
| } |
| |
| // Overriden from ui::AcceleratorTarget: |
| bool CanHandleAccelerators() const override { return true; } |
| |
| void OnLaunch(uint32 source_view_id, |
| Target requested_target, |
| const mojo::String& url) { |
| Target target = navigation_target_; |
| if (target == Target::DEFAULT) { |
| if (requested_target != Target::DEFAULT) { |
| target = requested_target; |
| } else { |
| // TODO(aa): Should be Target::NEW_NODE if source origin and dest origin |
| // are different? |
| target = Target::SOURCE_NODE; |
| } |
| } |
| |
| Window* dest_view = NULL; |
| if (target == Target::SOURCE_NODE) { |
| WindowVector::iterator source_view = GetWindowByViewId(source_view_id); |
| bool app_initiated = source_view != windows_.end(); |
| if (app_initiated) |
| dest_view = *source_view; |
| else if (!windows_.empty()) |
| dest_view = windows_.back(); |
| } |
| |
| if (!dest_view) { |
| dest_view = CreateWindow(); |
| windows_.push_back(dest_view); |
| } |
| |
| dest_view->Embed(url); |
| } |
| |
| // TODO(beng): proper layout manager!! |
| Id CreateLauncherUI() { |
| View* view = view_manager_->GetViewById(content_view_id_); |
| Rect bounds = view->bounds(); |
| bounds.x += kBorderInset; |
| bounds.y += kBorderInset; |
| bounds.width -= 2 * kBorderInset; |
| bounds.height = kTextfieldHeight; |
| launcher_ui_ = CreateWindow(bounds); |
| launcher_ui_->Embed("mojo:browser"); |
| return launcher_ui_->view()->id(); |
| } |
| |
| Window* CreateWindow() { |
| View* view = view_manager_->GetViewById(content_view_id_); |
| Rect bounds; |
| bounds.x = kBorderInset; |
| bounds.y = 2 * kBorderInset + kTextfieldHeight; |
| bounds.width = view->bounds().width - 3 * kBorderInset - kControlPanelWidth; |
| bounds.height = |
| view->bounds().height - (3 * kBorderInset + kTextfieldHeight); |
| if (!windows_.empty()) { |
| bounds.x = windows_.back()->view()->bounds().x + 35; |
| bounds.y = windows_.back()->view()->bounds().y + 35; |
| } |
| return CreateWindow(bounds); |
| } |
| |
| Window* CreateWindow(const Rect& bounds) { |
| View* content = view_manager_->GetViewById(content_view_id_); |
| View* view = view_manager_->CreateView(); |
| content->AddChild(view); |
| view->SetBounds(bounds); |
| view->SetVisible(true); |
| view->SetFocus(); |
| return new Window(this, view); |
| } |
| |
| Id CreateControlPanel(View* root) { |
| View* view = view_manager_->CreateView(); |
| root->AddChild(view); |
| |
| Rect bounds; |
| bounds.x = root->bounds().width - kControlPanelWidth - kBorderInset; |
| bounds.y = kBorderInset * 2 + kTextfieldHeight; |
| bounds.width = kControlPanelWidth; |
| bounds.height = root->bounds().height - kBorderInset * 3 - kTextfieldHeight; |
| view->SetBounds(bounds); |
| view->SetVisible(true); |
| |
| ServiceProviderPtr exposed_services; |
| control_panel_exposed_services_impl_.Bind(GetProxy(&exposed_services)); |
| control_panel_exposed_services_impl_.AddService<::examples::IWindowManager>( |
| this); |
| |
| GURL frame_url = url_.Resolve("/examples/window_manager/debug_panel.sky"); |
| view->Embed(frame_url.spec(), nullptr, exposed_services.Pass()); |
| |
| return view->id(); |
| } |
| |
| WindowVector::iterator GetWindowByViewId(Id view_id) { |
| for (std::vector<Window*>::iterator iter = windows_.begin(); |
| iter != windows_.end(); ++iter) { |
| if ((*iter)->view()->id() == view_id) { |
| return iter; |
| } |
| } |
| return windows_.end(); |
| } |
| |
| Shell* shell_; |
| |
| Window* launcher_ui_; |
| WindowVector windows_; |
| ViewManager* view_manager_; |
| scoped_ptr<RootLayoutManager> root_layout_manager_; |
| ServiceProviderImpl control_panel_exposed_services_impl_; |
| |
| window_manager::WindowManagerRoot* window_manager_root_; |
| |
| // Id of the view most content is added to. |
| Id content_view_id_; |
| |
| // Id of the debug panel. |
| Id control_panel_id_; |
| |
| GURL url_; |
| Target navigation_target_; |
| |
| ApplicationImpl* app_; |
| |
| mojo::Binding<examples::DebugPanelHost> binding_; |
| |
| DISALLOW_COPY_AND_ASSIGN(WindowManagerController); |
| }; |
| |
| class WindowManager : public ApplicationDelegate, |
| public window_manager::WindowManagerControllerFactory { |
| public: |
| WindowManager() |
| : window_manager_app_(new window_manager::WindowManagerApp(this)) {} |
| |
| scoped_ptr<window_manager::WindowManagerController> |
| CreateWindowManagerController( |
| ApplicationConnection* connection, |
| window_manager::WindowManagerRoot* wm_root) override { |
| return scoped_ptr<WindowManagerController>( |
| new WindowManagerController(shell_, app_, connection, wm_root)); |
| } |
| |
| private: |
| // Overridden from ApplicationDelegate: |
| void Initialize(ApplicationImpl* app) override { |
| window_manager_app_.reset(new window_manager::WindowManagerApp(this)); |
| shell_ = app->shell(); |
| app_ = app; |
| // FIXME: Mojo applications don't know their URLs yet: |
| // https://docs.google.com/a/chromium.org/document/d/1AQ2y6ekzvbdaMF5WrUQmneyXJnke-MnYYL4Gz1AKDos |
| url_ = GURL(app->args()[1]); |
| window_manager_app_->Initialize(app); |
| } |
| |
| bool ConfigureIncomingConnection(ApplicationConnection* connection) override { |
| window_manager_app_->ConfigureIncomingConnection(connection); |
| return true; |
| } |
| |
| ApplicationImpl* app_; |
| Shell* shell_; |
| GURL url_; |
| |
| scoped_ptr<window_manager::WindowManagerApp> window_manager_app_; |
| DISALLOW_COPY_AND_ASSIGN(WindowManager); |
| }; |
| |
| void WindowManagerConnection::CloseWindow(Id view_id) { |
| window_manager_->CloseWindow(view_id); |
| } |
| |
| void NavigatorHostImpl::DidNavigateLocally(const mojo::String& url) { |
| window_manager_->DidNavigateLocally(view_id_, url); |
| RecordNavigation(url); |
| } |
| |
| void NavigatorHostImpl::RequestNavigate(Target target, URLRequestPtr request) { |
| window_manager_->RequestNavigate(view_id_, target, request->url); |
| } |
| |
| void NavigatorHostImpl::RequestNavigateHistory(int32_t delta) { |
| if (history_.empty()) |
| return; |
| current_index_ = |
| std::max(0, std::min(current_index_ + delta, |
| static_cast<int32_t>(history_.size()) - 1)); |
| window_manager_->RequestNavigate(view_id_, Target::SOURCE_NODE, |
| history_[current_index_]); |
| } |
| |
| void NavigatorHostImpl::RecordNavigation(const std::string& url) { |
| if (current_index_ >= 0 && history_[current_index_] == url) { |
| // This is a navigation to the current entry, ignore. |
| return; |
| } |
| history_.erase(history_.begin() + (current_index_ + 1), history_.end()); |
| history_.push_back(url); |
| ++current_index_; |
| } |
| |
| } // namespace examples |
| } // namespace mojo |
| |
| MojoResult MojoMain(MojoHandle application_request) { |
| mojo::ApplicationRunnerChromium runner(new mojo::examples::WindowManager); |
| return runner.Run(application_request); |
| } |